Commit 1d915dfa authored by Steven杜宇's avatar Steven杜宇

Merge branch 'plan' into 'develop'

合并plan代码到dev

See merge request !7
parents e714338c 8b79a9dc
...@@ -33,11 +33,10 @@ platform :ios do ...@@ -33,11 +33,10 @@ platform :ios do
sc_105_branch = "sc-1.0.5" sc_105_branch = "sc-1.0.5"
main_fix = "main-fix" main_fix = "main-fix"
jiaofei = "jiaofei" jiaofei = "jiaofei"
lint = "swiftLint-develop" plan = "plan"
#打包正使用的分支 #打包正使用的分支
myPack_branch = develop_branch myPack_branch = plan
# 打adhoc包 执行命令 fastlane galaxyTest # 打adhoc包 执行命令 fastlane galaxyTest
......
This diff is collapsed.
...@@ -146,6 +146,56 @@ class AppDelegate: UIResponder, UIApplicationDelegate, WXApiDelegate { ...@@ -146,6 +146,56 @@ class AppDelegate: UIResponder, UIApplicationDelegate, WXApiDelegate {
UIViewController.current?.navigationController?.pushViewController(vc, animated: true) UIViewController.current?.navigationController?.pushViewController(vc, animated: true)
} }
} }
} else if iType == 5 {
// 跳转商品详情
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
if !YHLoginManager.shared.isLogin() {
printLog("需要登录")
return
}
let surveyViewModel = YHSurveyViewModel()
// 跳转分析
surveyViewModel.getRenewalPlanGenerated { state, error in
guard let state = state else {
if let msg = error?.errorMsg, !msg.isEmpty {
YHHUD.flash(message: msg)
}
return
}
if state.isGeneratePlan == true {
let vc = YHPlanTestViewController()
UIViewController.current?.navigationController?.pushViewController(vc)
} else {
let vc = YHCustomerInformationQuestionnaireVC()
UIViewController.current?.navigationController?.pushViewController(vc)
}
}
}
} else if iType == 6 {
// 跳转方案商品
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
// - 获取它对应的参数
if !YHLoginManager.shared.isLogin() {
printLog("需要登录")
return
}
let surveyViewModel = YHSurveyViewModel()
surveyViewModel.getRenewalPlanGenerated { state, error in
guard let state = state else {
if let msg = error?.errorMsg, !msg.isEmpty {
YHHUD.flash(message: msg)
}
return
}
if state.isGeneratePlan == true {
let vc = YHMakePlanViewController()
UIViewController.current?.navigationController?.pushViewController(vc, animated: true)
} else {
let vc = YHCustomerInformationQuestionnaireVC()
UIViewController.current?.navigationController?.pushViewController(vc)
}
}
}
} else { } else {
printLog("未处理的类型 \(iType)") printLog("未处理的类型 \(iType)")
} }
......
...@@ -20,6 +20,8 @@ let kEmptyOrderBgName = "no_data_bg_order" ...@@ -20,6 +20,8 @@ let kEmptyOrderBgName = "no_data_bg_order"
let kNotNetWorkBgName = "no_network_bg" let kNotNetWorkBgName = "no_network_bg"
// 人脉无好友空视图 // 人脉无好友空视图
let kNoFriendsBgName = "people_no_friend_bg" let kNoFriendsBgName = "people_no_friend_bg"
// 方案空
let kNoPlanBgName = "no_data_bg_plan"
class YHEmptyDataView: UIView { class YHEmptyDataView: UIView {
......
...@@ -42,7 +42,8 @@ class YHHomeBannerView: UIView { ...@@ -42,7 +42,8 @@ class YHHomeBannerView: UIView {
} }
} }
private let viewModel = YHLiveSalesViewModel() private lazy var viewModel = YHLiveSalesViewModel()
private lazy var surveyViewModel = YHSurveyViewModel()
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
...@@ -113,6 +114,30 @@ private extension YHHomeBannerView { ...@@ -113,6 +114,30 @@ private extension YHHomeBannerView {
YHOneKeyLoginManager.shared.oneKeyLogin() YHOneKeyLoginManager.shared.oneKeyLogin()
} }
} else if tabBarName.contains("renewal_plan", caseSensitive: false) {
if YHLoginManager.shared.isLogin() {
YHHUD.show(.progress(message: "加载中..."))
surveyViewModel.getRenewalPlanGenerated { state, error in
YHHUD.hide()
guard let state = state else {
if let msg = error?.errorMsg, !msg.isEmpty {
YHHUD.flash(message: msg)
}
return
}
if state.isGeneratePlan == true {
let vc = YHPlanTestViewController()
UIViewController.current?.navigationController?.pushViewController(vc)
} else {
let vc = YHCustomerInformationQuestionnaireVC()
UIViewController.current?.navigationController?.pushViewController(vc)
}
}
} else {
YHOneKeyLoginManager.shared.oneKeyLogin()
}
} else { } else {
} }
...@@ -163,22 +188,6 @@ extension YHHomeBannerView: FSPagerViewDataSource, FSPagerViewDelegate { ...@@ -163,22 +188,6 @@ extension YHHomeBannerView: FSPagerViewDataSource, FSPagerViewDelegate {
YHHUD.flash(message: "error:skip_url不能为空") YHHUD.flash(message: "error:skip_url不能为空")
return return
} }
// for test hjl 建明机器IP
// var url = "http://192.168.23.66:10300/superAppBridge.html#/schoolEvaluation"
// for test hjl 梁辉机器IP
// model.skip_url = "http://192.168.23.75:10300/superAppBridge.html#/double11-home"
// for test hjl steve机器IP
// model.skip_url = "http://192.168.52.158:10300/superAppBridge.html#/double11-home"
// for test hjl 测试环境
// model.skip_url = "https://test-hkdiy-h5.galaxy-immi.com/superAppBridge.html#/double11-home"
// for test hjl 贤宇IP
// model.skip_url = "http://192.168.23.35:10300/signatureQrcode/EcqtQYs%2Bxey7t6jLbn6JkQ%3D%3D"
// model.skip_url = "http://192.168.23.71:10300/superAppBridge.html#/double11-home"
// 1.增加app token // 1.增加app token
var url = "" var url = ""
......
...@@ -285,9 +285,9 @@ class YHSelectLookTableViewCell: UITableViewCell { ...@@ -285,9 +285,9 @@ class YHSelectLookTableViewCell: UITableViewCell {
if dataSource.catAttr == 2 { if dataSource.catAttr == 2 {
flagLabel.isHidden = false flagLabel.isHidden = false
} }
let a: ASAttributedString = .init("¥", .font(UIFont(name: "DINAlternate-Bold", size: 14) ?? UIFont()), .foreground(UIColor.mainTextColor)) let a: ASAttributedString = .init("¥", .font(UIFont(name: "D-DIN-PRO-Bold", size: 14) ?? UIFont()), .foreground(UIColor.mainTextColor))
let b: ASAttributedString = .init("\(dataSource.linePrice) ", .font(UIFont(name: "DINAlternate-Bold", size: 20) ?? UIFont()), .foreground(UIColor.mainTextColor)) let b: ASAttributedString = .init("\(dataSource.linePrice.formattedPrice()) ", .font(UIFont(name: "D-DIN-PRO-Bold", size: 20) ?? UIFont()), .foreground(UIColor.mainTextColor))
let c: ASAttributedString = .init(\(dataSource.price)", .font(UIFont(name: "DINAlternate-Bold", size: 14) ?? UIFont()), .foreground(UIColor(hex: 0x8993a2)), .strikethrough(.single)) let c: ASAttributedString = .init(\(dataSource.price.formattedPrice())", .font(UIFont(name: "D-DIN-PRO-Bold", size: 14) ?? UIFont()), .foreground(UIColor(hex: 0x8993a2)), .strikethrough(.single))
if dataSource.linePrice == dataSource.price { if dataSource.linePrice == dataSource.price {
self.subTitleLabel.attributed.text = a + b self.subTitleLabel.attributed.text = a + b
} else { } else {
......
//
// YHPictureBrowserViewController.swift
// galaxy
//
// Created by Dufet on 2025/4/8.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
import JXPhotoBrowser
import Photos
import PhotosUI
class YHPictureBrowserViewController: JXPhotoBrowser {
var getImgUrlBlock: ((Int) -> (String))?
lazy var navBar: UIView = {
let v = UIView()
let backBtn = UIButton()
backBtn.setImage(UIImage(named: "nav_back_white"), for: .normal)
backBtn.addTarget(self, action: #selector(didBackBtnClicked), for: .touchUpInside)
v.addSubview(backBtn)
let saveBtn = UIButton()
let img = UIImage(named: "photo_brower_save")
let templateImage = img?.withRenderingMode(.alwaysTemplate)
saveBtn.setImage(templateImage, for: .normal)
saveBtn.imageView?.tintColor = .white
saveBtn.addTarget(self, action: #selector(didSaveBtnClicked), for: .touchUpInside)
v.addSubview(saveBtn)
backBtn.snp.makeConstraints { make in
make.width.height.equalTo(44)
make.left.equalToSuperview()
make.bottom.equalToSuperview()
}
saveBtn.snp.makeConstraints { make in
make.width.height.equalTo(44)
make.right.equalToSuperview()
make.bottom.equalToSuperview()
}
return v
}()
@objc func didBackBtnClicked() {
dismiss()
}
@objc func didSaveBtnClicked() {
let index = self.browserView.pageIndex
if let block = self.getImgUrlBlock {
let url = block(self.pageIndex)
saveLocalPictureSyn(url)
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(navBar)
navBar.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
make.height.equalTo(k_Height_NavigationtBarAndStatuBar)
}
}
func saveLocalPictureSyn(_ picUrl: String) {
let imageView = UIImageView()
imageView.kf.setImage(with: URL(string: picUrl)) { result in
switch result {
case .success(let value):
self.saveImage(value.image)
case .failure:
YHHUD.flash(message: "保存失败")
}
}
}
func saveImage(_ image: UIImage) {
// 确保应用有权访问相册
PHPhotoLibrary.requestAuthorization { status in
if status == .authorized {
// 保存图片到相册
DispatchQueue.main.async {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
YHHUD.flash(message: "保存成功")
}
} else {
DispatchQueue.main.async {
YHHUD.flash(message: "保存失败,请检查系统权限")
}
}
}
}
}
...@@ -30,7 +30,7 @@ extension YHPictureReviewManager { ...@@ -30,7 +30,7 @@ extension YHPictureReviewManager {
self.curIndex = curIndex self.curIndex = curIndex
self.arrPics = arrPicturs self.arrPics = arrPicturs
let browser = JXPhotoBrowser() let browser = YHPictureBrowserViewController()
browser.numberOfItems = { browser.numberOfItems = {
self.arrPics.count self.arrPics.count
} }
...@@ -54,6 +54,14 @@ extension YHPictureReviewManager { ...@@ -54,6 +54,14 @@ extension YHPictureReviewManager {
} }
} }
browser.getImgUrlBlock = { [weak self] index in
guard let self = self else { return "" }
if 0 <= index, index < self.arrPics.count {
return self.arrPics[index]
}
return ""
}
// 数字样式的页码指示器 // 数字样式的页码指示器
browser.pageIndicator = JXPhotoBrowserNumberPageIndicator() browser.pageIndicator = JXPhotoBrowserNumberPageIndicator()
browser.pageIndex = self.curIndex browser.pageIndex = self.curIndex
......
//
// YHPlanPolicyDetailViewController.swift
// galaxy
//
// Created by Dufet on 2025/3/29.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
import Photos
import VisualEffectView
class YHPlanPolicyImageInfo {
var model: YHPlanCaseModel = YHPlanCaseModel()
var imgView: UIImageView = UIImageView()
let height: CGFloat = 520.0
var width: CGFloat = 520.0
}
class YHPlanPolicyDetailViewController: YHBaseViewController {
var firstAppear: Bool = true
var arr: [YHPlanCaseModel] = [] {
didSet {
imgs.removeAll()
for item in arr {
let model = YHPlanPolicyImageInfo()
model.model = item
imgs.append(model)
}
self.collectionView.reloadData()
}
}
var currentIndex: Int = 0
var imgs: [YHPlanPolicyImageInfo] = []
lazy var bgImgView: UIImageView = {
let v = UIImageView()
v.clipsToBounds = true
return v
}()
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0.0
layout.minimumLineSpacing = 0.0
layout.scrollDirection = .horizontal
let collectView = UICollectionView(frame: CGRect(x: 0, y: k_Height_NavigationtBarAndStatuBar+24.0, width: KScreenWidth, height: 520), collectionViewLayout: layout)
collectView.backgroundColor = .clear
collectView.delegate = self
collectView.dataSource = self
collectView.register(YHPlanShareImageCell.self, forCellWithReuseIdentifier: YHPlanShareImageCell.cellReuseIdentifier)
collectView.isPagingEnabled = true
collectView.showsHorizontalScrollIndicator = false
return collectView
}()
lazy var blurView: VisualEffectView = {
let blurView = VisualEffectView()
blurView.colorTint = UIColor(hex: 0xAFAFAF).withAlphaComponent(0.15)
blurView.blurRadius = 16
blurView.scale = 1
return blurView
}()
lazy var shareBtn: UIButton = {
let btn = UIButton()
btn.backgroundColor = .white
btn.setTitle("分享", for: .normal)
btn.setTitleColor(.mainTextColor, for: .normal)
btn.titleLabel?.font = .PFSC_M(ofSize: 16)
btn.layer.cornerRadius = 3.0
btn.addTarget(self, action: #selector(didShareBtnClicked), for: .touchUpInside)
return btn
}()
@objc func didShareBtnClicked() {
let view = YHPlanLinkShareView.alertView()
view.clickBlock = { [weak self] type in
guard let self = self else { return }
if 0 <= currentIndex, currentIndex < arr.count {
let model = arr[currentIndex]
if type == .wechat {
sendImageToWechat(model.image_poster)
} else if type == .copyLink {
let pasteBoard = UIPasteboard.general
pasteBoard.string = model.image_poster
YHHUD.flash(message: "复制成功")
} else if type == .saveImg {
saveImgFromUrl(model.image_poster)
}
}
}
view.show()
}
override func viewDidLoad() {
super.viewDidLoad()
gk_backImage = UIImage(named: "nav_back_white")
gk_navTitle = "\(currentIndex+1)/\(arr.count)"
gk_navTitleColor = .white
view.backgroundColor = .init(hex: 0x000000, alpha: 1.0)
view.addSubview(bgImgView)
view.addSubview(blurView)
view.addSubview(collectionView)
view.addSubview(shareBtn)
bgImgView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
blurView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
collectionView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.equalTo(k_Height_NavigationtBarAndStatuBar+24.0)
make.height.equalTo(520)
}
shareBtn.snp.makeConstraints { make in
make.left.equalTo(20)
make.right.equalTo(-20)
make.height.equalTo(46)
make.top.equalTo(collectionView.snp.bottom).offset(32)
}
loadImgs()
showBgImage(index: currentIndex)
}
func showBgImage(index: Int) {
if 0 <= index, index < arr.count {
let model = arr[index]
if let url = URL(string: model.image_poster) {
bgImgView.kf.setImage(with: url)
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if firstAppear {
collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: .top, animated: false)
firstAppear = false
}
}
func loadImgs() {
let ossGroup = DispatchGroup()
for item in imgs {
ossGroup.enter()
item.imgView.kf.setImage(with: URL(string: item.model.image_poster)) { result in
switch result {
case let .success(value):
let size = value.image.size
let scale = size.height > 0 ? Double(size.width / size.height) : 1.0
if scale > 0 {
item.width = scale * item.height
printLog("IMG_SIZE: \(item.width), \(item.height)")
}
case let .failure(error):
print("Job failed: \(error.localizedDescription)")
}
ossGroup.leave()
}
}
ossGroup.notify(queue: .main) {
self.collectionView.reloadData()
}
}
}
extension YHPlanPolicyDetailViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
// 返回单元格数量
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imgs.count
}
// 返回每个单元格的大小
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: KScreenWidth, height: 520)
}
// 返回自定义单元格
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: YHPlanShareImageCell.cellReuseIdentifier, for: indexPath) as? YHPlanShareImageCell else {
return UICollectionViewCell()
}
if 0 <= indexPath.item && indexPath.item < imgs.count {
let model = imgs[indexPath.item]
cell.model = model
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if 0 <= indexPath.item && indexPath.item < imgs.count {
let model = imgs[indexPath.item]
YHPictureReviewManager.shared.showNetWorkPicturs(curIndex: 0, arrPicturs: [model.model.image_poster])
}
}
}
extension YHPlanPolicyDetailViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
currentIndex = Int(scrollView.contentOffset.x/KScreenWidth)
gk_navTitle = "\(currentIndex+1)/\(arr.count)"
showBgImage(index: currentIndex)
}
}
extension YHPlanPolicyDetailViewController {
func saveImgFromUrl(_ urlString: String) {
guard let url = URL(string: urlString) else {
YHHUD.flash(message: "保存失败")
return
}
YHHUD.show(.progress(message: "下载中..."))
let task = URLSession.shared.dataTask(with: url) { data, _, error in
DispatchQueue.main.async {
YHHUD.hide()
guard let data = data, error == nil else { return YHHUD.flash(message: "保存成功") }
let image = UIImage(data: data)
if let image = image {
// 保存图片到图库
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAsset(from: image)
}, completionHandler: { success, error in
if success {
// 图片保存成功
DispatchQueue.main.async {
YHHUD.flash(message: "保存成功")
}
} else if let error = error {
// 保存失败
DispatchQueue.main.async {
YHHUD.flash(message: "保存失败")
}
print("保存图片出错: \(error.localizedDescription)")
}
})
}
}
}
task.resume()
}
func sendImageToWechat(_ urlString: String) {
guard let url = URL(string: urlString) else {
return
}
let task = URLSession.shared.dataTask(with: url) { data, _, error in
DispatchQueue.main.async {
guard let data = data, error == nil else { return }
let image = UIImage(data: data)
if let image = image {
YHShareManager.shared.sendImageContent(image)
}
}
}
task.resume()
}
}
//
// YHSurveyMatchResultViewController.swift
// galaxy
//
// Created by alexzzw on 2025/3/27.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import Lottie
import UIKit
class YHSurveyMatchResultViewController: YHBaseViewController {
enum MatchResult {
case failure
case success
func iconTitle() -> String {
switch self {
case .failure:
return "plan_survey_match_failure"
case .success:
return "plan_survey_match_success"
}
}
func title() -> String {
switch self {
case .failure:
return "匹配失败"
case .success:
return "匹配完成"
}
}
func content() -> String {
switch self {
case .failure:
return "出了点问题,重试一下吧"
case .success:
return "请您查阅【香港身份续签至永居方案】,\n如需进一步咨询,请联系专属顾问和生活管家"
}
}
func buttonTitle() -> String {
switch self {
case .failure:
return "重新匹配"
case .success:
return "香港身份续签至永居方案"
}
}
}
private lazy var topImageView: UIImageView = {
let view = UIImageView()
return view
}()
private lazy var infoTitleLabel: UILabel = {
let label = UILabel()
label.textColor = .mainTextColor
label.font = .PFSC_B(ofSize: 26)
label.textAlignment = .center
return label
}()
private lazy var infoContentLabel: UILabel = {
let label = UILabel()
label.textColor = UIColor(hex: 0x6A7586)
label.font = .PFSC_R(ofSize: 15)
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
private lazy var matchButton: UIButton = {
let button = UIButton(type: .custom)
button.addTarget(self, action: #selector(matchButtonClicked), for: .touchUpInside)
button.backgroundColor = .mainTextColor
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = .PFSC_M(ofSize: 16)
button.layer.cornerRadius = 3
button.clipsToBounds = true
return button
}()
var matchAgainEvent: (() -> Void)?
private let pageType: MatchResult
init(_ pageType: MatchResult) {
self.pageType = pageType
super.init(nibName: nil, bundle: nil)
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func backItemClick(_ sender: Any) {
backEvent()
}
/// 是否可以返回,包括点击返回和手势返回,默认YES
override func navigationShouldPop() -> Bool {
backEvent()
return false
}
private func backEvent() {
if pageType == .success {
navigationController?.popToRootViewController(animated: true)
} else {
matchAgainEvent?()
navigationController?.popViewController()
}
}
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private func setupUI() {
gk_navTitle = "香港身份方案评估"
gk_navBarAlpha = 1.0
gk_navBackgroundColor = .white
view.backgroundColor = UIColor.white
view.addSubview(topImageView)
view.addSubview(infoTitleLabel)
view.addSubview(infoContentLabel)
view.addSubview(matchButton)
topImageView.snp.makeConstraints { make in
make.width.equalTo(200)
make.height.equalTo(200)
make.top.equalToSuperview().offset(90 + k_Height_NavigationtBarAndStatuBar)
make.centerX.equalToSuperview()
}
infoTitleLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(topImageView.snp.bottom).offset(8)
}
infoContentLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(infoTitleLabel.snp.bottom).offset(8)
make.left.greaterThanOrEqualToSuperview().offset(16)
make.right.lessThanOrEqualToSuperview().offset(-16)
}
matchButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(38)
make.right.equalToSuperview().offset(-38)
make.bottom.equalTo(view.layoutMarginsGuide.snp.bottom).offset(-68)
make.height.equalTo(48)
}
topImageView.image = UIImage(named: pageType.iconTitle())
infoTitleLabel.text = pageType.title()
infoContentLabel.text = pageType.content()
matchButton.setTitle(pageType.buttonTitle(), for: .normal)
if pageType == .success {
playMatchSuccessAnimation()
}
}
@objc private func matchButtonClicked() {
if pageType == .success {
guard let navigationController = self.navigationController else {
return
}
let ctl = YHPlanTestViewController()
ctl.hidesBottomBarWhenPushed = true
var viewControllers = Array(navigationController.viewControllers.prefix(1))
viewControllers.append(ctl)
navigationController.setViewControllers(viewControllers, animated: true)
} else {
matchAgainEvent?()
navigationController?.popViewController()
}
}
/// 播放匹配成功
private func playMatchSuccessAnimation() {
let aniView = LottieAnimationView(name: "survey_match_success")
aniView.isUserInteractionEnabled = false
aniView.frame = CGRect(x: 0, y: 0, width: KScreenWidth, height: KScreenWidth)
aniView.contentMode = .scaleAspectFit
aniView.loopMode = .playOnce
view.addSubview(aniView)
aniView.play(completion: { _ in
aniView.removeFromSuperview()
})
}
}
//
// YHSurveyMatchingViewController.swift
// galaxy
//
// Created by alexzzw on 2025/3/27.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import Lottie
import UIKit
class YHSurveyMatchingViewController: YHBaseViewController {
private lazy var viewModel = YHSurveyViewModel()
private var timer: Timer?
private var startTime: Date?
private var isRequestCompleted = false
private lazy var lottieView: LottieAnimationView = {
let aniView = LottieAnimationView(name: "survey_file_scan")
aniView.isUserInteractionEnabled = false
aniView.frame = CGRect(x: 0, y: 0, width: KScreenWidth, height: KScreenWidth)
aniView.contentMode = .scaleAspectFit
aniView.loopMode = .loop
return aniView
}()
private lazy var infoTitleLabel: UILabel = {
let label = UILabel()
label.text = "智能匹配中"
label.textColor = .mainTextColor
label.font = .PFSC_B(ofSize: 26)
label.textAlignment = .center
return label
}()
private lazy var infoContentLabel: UILabel = {
let label = UILabel()
label.text = "正在为您制定香港身份续签至永居方案"
label.textColor = UIColor(hex: 0x6A7586)
label.font = .PFSC_R(ofSize: 15)
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
private lazy var progressView: UIProgressView = {
let view = UIProgressView()
view.progressTintColor = .mainTextColor
view.trackTintColor = UIColor(hexString: "#F5F6F8")
view.progress = 0.6
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
startLoading()
}
private func setupUI() {
gk_navTitle = "香港身份方案评估"
gk_navBarAlpha = 1.0
gk_navBackgroundColor = .white
view.backgroundColor = UIColor.white
view.addSubview(lottieView)
view.addSubview(infoTitleLabel)
view.addSubview(infoContentLabel)
view.addSubview(progressView)
lottieView.snp.makeConstraints { make in
make.width.equalTo(248)
make.height.equalTo(248)
make.top.equalToSuperview().offset(32 + k_Height_NavigationtBarAndStatuBar)
make.centerX.equalToSuperview()
}
infoTitleLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(lottieView.snp.bottom).offset(18)
}
infoContentLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(infoTitleLabel.snp.bottom).offset(8)
make.left.greaterThanOrEqualToSuperview().offset(16)
make.right.lessThanOrEqualToSuperview().offset(-16)
}
progressView.snp.makeConstraints { make in
make.width.equalTo(220)
make.height.equalTo(4)
make.centerX.equalToSuperview()
make.bottom.equalTo(view.layoutMarginsGuide.snp.bottom).offset(-90)
}
}
private func startLoading() {
startAnimation()
// 重置状态
progressView.progress = 0
isRequestCompleted = false
startTime = Date()
timer = Timer.scheduledTimer(withTimeInterval: 0.016, repeats: true, block: { [weak self] _ in
guard let self = self else { return }
guard let startTime = self.startTime else { return }
let elapsed = Date().timeIntervalSince(startTime)
let totalDuration: TimeInterval = 4.0 // 总时长4秒
if elapsed < totalDuration {
// 前4秒内,进度条从0%到90%
let progress = Float(elapsed / totalDuration) * 0.9
self.progressView.progress = progress
} else {
// 4秒后
self.timer?.invalidate()
self.timer = nil
if self.isRequestCompleted {
// 如果请求已完成,直接到100%
self.progressView.progress = 1.0
self.loadingCompleted()
} else {
// 如果请求未完成,保持90%,等待请求完成
self.progressView.progress = 0.9
}
self.requestState()
}
})
// requestState()
}
private func requestState() {
viewModel.getRenewalPlanState { [weak self] _, _ in
guard let self = self else {
return
}
self.isRequestCompleted = true
if self.timer == nil {
// 如果计时器已经结束(超过4秒),直接完成进度条
self.progressView.progress = 1.0
self.loadingCompleted()
}
}
}
private func loadingCompleted() {
stopAnimation()
if let state = viewModel.planState, state.isGeneratePlan {
gotoMatchResultVC(true)
} else {
gotoMatchResultVC(false)
}
}
private func gotoMatchResultVC(_ isSucess: Bool) {
let ctl = YHSurveyMatchResultViewController(isSucess ? .success : .failure)
if !isSucess {
ctl.matchAgainEvent = { [weak self] in
self?.startLoading()
}
}
navigationController?.pushViewController(ctl)
}
private func startAnimation() {
lottieView.play(completion: { _ in
//
})
}
private func stopAnimation() {
lottieView.stop()
}
}
//
// YHSurveySubmitDoneViewController.swift
// galaxy
//
// Created by alexzzw on 2025/3/26.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
class YHSurveySubmitDoneViewController: YHBaseViewController {
private lazy var topImageView: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "plan_question_submitted")
return view
}()
private lazy var infoTitleLabel: UILabel = {
let label = UILabel()
label.text = "问卷提交成功"
label.textColor = .mainTextColor
label.font = .PFSC_B(ofSize: 17)
label.textAlignment = .center
return label
}()
private lazy var infoContentLabel: UILabel = {
let label = UILabel()
label.text = "专属顾问将在24小时内与您联系,\n为您提供详细方案"
label.textColor = UIColor(hex: 0x6A7586)
label.font = .PFSC_R(ofSize: 14)
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
private lazy var backHomeButton: UIButton = {
let button = UIButton(type: .custom)
button.setTitle("返回首页", for: .normal)
button.addTarget(self, action: #selector(backHomeClicked), for: .touchUpInside)
button.backgroundColor = .mainTextColor
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = .PFSC_R(ofSize: 14)
button.layer.cornerRadius = 3
button.clipsToBounds = true
return button
}()
override func backItemClick(_ sender: Any) {
backEvent()
}
/// 是否可以返回,包括点击返回和手势返回,默认YES
override func navigationShouldPop() -> Bool {
backEvent()
return false
}
private func backEvent() {
navigationController?.popToRootViewController(animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private func setupUI() {
gk_navTitle = "香港身份方案评估"
gk_navBarAlpha = 1.0
gk_navBackgroundColor = .white
view.backgroundColor = UIColor.white
view.addSubview(topImageView)
view.addSubview(infoTitleLabel)
view.addSubview(infoContentLabel)
view.addSubview(backHomeButton)
topImageView.snp.makeConstraints { make in
make.width.equalTo(127)
make.height.equalTo(127)
make.top.equalToSuperview().offset(129 + k_Height_NavigationtBarAndStatuBar)
make.centerX.equalToSuperview()
}
infoTitleLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(topImageView.snp.bottom).offset(8)
}
infoContentLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(infoTitleLabel.snp.bottom).offset(8)
make.left.greaterThanOrEqualToSuperview().offset(16)
make.right.lessThanOrEqualToSuperview().offset(-16)
}
backHomeButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(infoContentLabel.snp.bottom).offset(24)
make.width.equalTo(100)
make.height.equalTo(40)
}
}
@objc private func backHomeClicked() {
UIViewController.current?.navigationController?.popToRootViewController(animated: false)
goTabBarBy(tabType: .home)
}
}
//
// YHPlanModel.swift
// galaxy
//
// Created by Dufet on 2025/3/25.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
import SmartCodable
class YHPlanListModel: SmartCodable {
var basic: YHPlanValueChartInfo = YHPlanValueChartInfo()
var list: [YHPlanAnalyzeModel] = []
var plan_article_case: [YHPlanCaseModel] = []
var plan_article_policy: [YHPlanCaseModel] = []
var code: String = ""
var share_url: String = ""
var pdf_url: String = ""
required init() {
}
}
class YHPlanCaseModel: SmartCodable {
var title: String = ""
var img_url: String = ""
var image_poster: String = ""
required init() {
}
}
class YHPlanValueChartInfo: SmartCodable {
var work_score: String = ""
var live_score: String = ""
var invest_score: String = ""
var stay_time_score: String = ""
var result: String = ""
var level_name: String = ""
required init() {
}
}
class YHPlanProductModel: SmartCodable {
var title: String = ""
var description: String = ""
var img_url: String = ""
var recommend_product_ids: [Int] = []
required init() {
}
}
class YHPlanAnalyzeModel: SmartCodable {
var name: String = ""
var score: String = "0.0"
var current_state: String = ""
var suggest: String = ""
var hong_kong_policy: String = ""
var solution_plan: [YHPlanProductModel] = []
func getTitle() -> String {
if name == "work" {
return "工作"
}
if name == "live" {
return "生活"
}
if name == "invest" {
return "投资"
}
if name == "stay" {
return "逗留"
}
return ""
}
required init() {
}
}
//
// YHSurveyContentModel.swift
// galaxy
//
// Created by alexzzw on 2025/3/28.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import Foundation
import SmartCodable
// MARK: - YHSurveyContainerModel
class YHSurveyContainerModel: SmartCodable {
var surveyContent: YHSurveyContentModel = YHSurveyContentModel()
// 0老用户1新用户
var type: Int = 0
var defaultAnswers: [YHSurveyDefaultAnswers] = []
var isNewCustomer: Bool {
return type == 1
}
enum CodingKeys: String, CodingKey {
case surveyContent = "survey_content"
case type
case defaultAnswers = "default_answers"
}
required init() {
}
}
// MARK: - YHSurveyDefaultAnswers
class YHSurveyDefaultAnswers: SmartCodable {
var questionKey: String = ""
var optionKey: [String] = []
enum CodingKeys: String, CodingKey {
case questionKey = "question_key"
case optionKey = "option_key"
}
required init() {
}
}
// MARK: - YHSurveyContentModel
class YHSurveyContentModel: SmartCodable {
/// 问卷ID
var id: String = ""
/// 问题列表
var questions: [YHSurveyQuestionItem] = []
/// 问卷备注
var remark: String = ""
/// 问卷标题
var title: String = ""
required init() {
}
}
// MARK: - YHSurveyQuestionItem
class YHSurveyQuestionItem: SmartCodable {
/// 业务key,唯一标识一个问券的一个问题
var businessKey: String = ""
/// 问题ID
var id: String = ""
/// 是否必须回答 0:否 1:是
var isRequired: Int = 0
/// 最多允许上传的文件数量 仅对文件上传题有效
var maxFileCount: Int = 0
/// 问题序号
var number: Int = 0
/// repeated article.AnswerItem show_when = 8 [json_name = "show_when"]; //
/// 旧版show_when,即将废弃,请转用show_when_v1 仅当某个或某几个问题答案为XXX时才展示 不同项之间是且关系,选项列表
var options: [YHSurveyQuestionOptionItem] = []
/// 问题备注
var remark: String = ""
var showWhenV1: YHSurveyQuestionItemShowWhenV1 = YHSurveyQuestionItemShowWhenV1()
/// 问题标题
var title: String = ""
/// 问题类型:1:单选 2:多选 3:文本输入 4:文件上传
var type: Int = 0
enum CodingKeys: String, CodingKey {
case id
case number
case options
case remark
case title
case type
case isRequired = "is_required"
case maxFileCount = "max_file_count"
case showWhenV1 = "show_when_v1"
case businessKey = "business_key"
}
required init() {
}
}
// MARK: - YHSurveyQuestionOptionItem
class YHSurveyQuestionOptionItem: SmartCodable {
/// 业务key,唯一标识一个问题的一个选项
var businessKey: String = ""
/// 选项ID
var id: String = ""
/// 跳转的目标问题id,仅当 jump_type=2 时有效
var jumpQuestionId: String = ""
/// 跳转逻辑 0:无需特殊跳转 1:结束问卷 2:跳到某题
var jumpType: Int = 0
/// 选中该选项后是否需要额外填写说明 0:无需 1:选填 2:必填
var needExtra: Int = 0
/// 选项序号
var number: Int = 0
/// 选项备注
var remark: String = ""
/// 该选项对应得分,业务有需要时可用
var score: Int = 0
/// 选项标题
var title: String = ""
enum CodingKeys: String, CodingKey {
case id
case number
case remark
case score
case title
case needExtra = "need_extra"
case jumpType = "jump_type"
case jumpQuestionId = "jump_question_id"
case businessKey = "business_key"
}
required init() {
}
}
// MARK: - YHSurveyQuestionItemShowWhenV1
class YHSurveyQuestionItemShowWhenV1: SmartCodable {
var conditions: [YHSurveyQuestionConditionItem] = []
/// 关系 and/or
var relation: String = ""
var orArray: [YHSurveyQuestionAndOptions] = []
var andArray: [YHSurveyQuestionAndOptions] = []
required init() {
}
func getConitionArray() -> [YHSurveyQuestionAndOptions] {
var questionAndOptions: [YHSurveyQuestionAndOptions] = []
conditions.forEach { conditionItem in
var optionIds: Set<String> = []
let options = conditionItem.options
/// 选项为空,任意选一个即可
if conditionItem.isAnyAnswerAllowed {
let questionAndOption = YHSurveyQuestionAndOptions.init(questionsId: conditionItem.questionId, optionIds: [])
questionAndOptions.append(questionAndOption)
} else {
if options.relation == "and" {
optionIds.formUnion(options.list.compactMap {
$0.optionId
})
let questionAndOption = YHSurveyQuestionAndOptions.init(questionsId: conditionItem.questionId, optionIds: optionIds)
questionAndOptions.append(questionAndOption)
} else if options.relation == "or" {
questionAndOptions.append(contentsOf: options.list.compactMap {
return YHSurveyQuestionAndOptions.init(questionsId: conditionItem.questionId, optionIds: [$0.optionId])
})
}
}
}
return questionAndOptions
}
func didFinishMapping() {
if relation == "or" {
orArray = getConitionArray()
} else if relation == "and" {
andArray = getConitionArray()
}
}
}
// MARK: - YHSurveyQuestionConditionItem
class YHSurveyQuestionConditionItem: SmartCodable {
/// 选中的选项ID与额外说明 仅对单选/多选有效且必需,单选时此项长度为1 兼容旧版 todo 去除
var optionExtra: [YHSurveyConditionOptionExtra] = []
var options: YHSurveyConditionOptions = YHSurveyConditionOptions()
/// 问题ID
var questionId: String = ""
/// 问题类型:1:单选 2:多选 3:文本输入 4:文件上传
var questionType: Int = 0
var isAnyAnswerAllowed: Bool = false
enum CodingKeys: String, CodingKey {
case options
case optionExtra = "option_extra"
case questionId = "question_id"
case questionType = "question_type"
case isAnyAnswerAllowed = "is_any_answer_allowed"
}
required init() {
}
}
// MARK: - YHSurveyConditionOptionExtra
class YHSurveyConditionOptionExtra: SmartCodable {
/// 问题选项额外说明 选中该选项后必需填写额外说明时必需
var extra: String = ""
/// 选项ID 必需
var optionId: String = ""
/// 选项标题 可选 提交答案时,建议设置该字段,会作为提交记录留底;show_when场景下,忽略该字段
var title: String = ""
/// 选项key 必需
var optionKey: String = ""
init(extra: String, optionId: String, title: String, optionKey: String) {
self.extra = extra
self.optionId = optionId
self.title = title
self.optionKey = optionKey
}
enum CodingKeys: String, CodingKey {
case extra
case title
case optionId = "option_id"
case optionKey = "option_key"
}
required init() {
}
}
// MARK: - YHSurveyConditionOptions
class YHSurveyConditionOptions: SmartCodable {
/// 选项ID与额外说明的列表 单选时此项长度为1
var list: [YHSurveyConditionOptionsItem] = []
/// 关系 and/or
var relation: String = ""
required init() {
}
}
// MARK: - YHSurveyConditionOptionsItem
class YHSurveyConditionOptionsItem: SmartCodable {
var extra: YHSurveyOptionsItemExtra = YHSurveyOptionsItemExtra()
/// 选项ID
var optionId: String = ""
enum CodingKeys: String, CodingKey {
case extra
case optionId = "option_id"
}
required init() {
}
}
// MARK: - YHSurveyOptionsItemExtra
class YHSurveyOptionsItemExtra: SmartCodable {
/// 选项额外说明的内容 仅当 额外说明的判断方式为2时才有效
var content: String = ""
/// 选项额外说明的判断方式 0:无需判断 1:必须非空,不管具体值 2:必须为特定值
var verifyType: Int = 0
enum CodingKeys: String, CodingKey {
case content
case verifyType = "verify_type"
}
required init() {
}
}
struct YHSurveyQuestionAndOptions: SmartCodable, Equatable {
var questionsId: String = ""
var optionIds: Set<String> = []
static func == (lhs: YHSurveyQuestionAndOptions, rhs: YHSurveyQuestionAndOptions) -> Bool {
return lhs.questionsId == rhs.questionsId && lhs.optionIds == rhs.optionIds
}
}
//
// YHSurveyQuestionType.swift
// galaxy
//
// Created by alexzzw on 2025/3/26.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
enum YHSurveyQuestionType: String, CaseIterable {
case hasHKId = "是否香港身份"
case hasPR = "是否有永居服务"
case isEmployed = "是否受雇香港"
case hasHKCompany = "是否有香港公司"
case isHKCompanyOpen = "香港公司是否运营"
case isMainlandCompanyToHK = "是否有内地公司业务转到香港"
case isHKOffice = "是否香港办公室"
case hkResidence = "在港居住情况"
case hasChildrenEduPlan = "是否子女在港读书计划"
case hasHKInsurance = "是否香港保险"
case hasPRLicense = "是否香港驾照"
case isHKHolder = "是否香港个户"
case hkStayRate = "在港逗留频率"
func selections() -> [String] {
switch self {
case .hasHKId:
return ["是", "否"]
case .hasPR:
return ["是", "否"]
case .isEmployed:
return ["是", "否"]
case .hasHKCompany:
return ["是", "否"]
case .isHKCompanyOpen:
return ["是", "否"]
case .isMainlandCompanyToHK:
return ["是", "否"]
case .isHKOffice:
return ["是", "否"]
case .hkResidence:
return ["枚举值已购房", "已租房", "考虑购房", "考虑租房", "暂不考虑"]
case .hasChildrenEduPlan:
return ["是", "否"]
case .hasHKInsurance:
return ["是", "否"]
case .hasPRLicense:
return ["是", "否"]
case .isHKHolder:
return ["是", "否"]
case .hkStayRate:
return ["枚举值长期在港", "每周赴港至少一次", "每月去一次", "每季度去一次", "半年去一次", "超过半年去一次"]
}
}
}
//
// YHSurveyRenewalPlanState.swift
// galaxy
//
// Created by alexzzw on 2025/3/31.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import Foundation
import SmartCodable
// MARK: - YHSurveyRenewalPlanState
class YHSurveyRenewalPlanState: SmartCodable {
var isGeneratePlan: Bool = false
enum CodingKeys: String, CodingKey {
case isGeneratePlan = "is_generate_plan"
}
required init() {
}
}
//
// YHSurveyResponseModel.swift
// galaxy
//
// Created by alexzzw on 2025/3/31.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import Foundation
import SmartCodable
// MARK: - YHSurveyResponseModel
class YHSurveyResponseModel: SmartCodable {
var hasHkIdentity: Bool = false
enum CodingKeys: String, CodingKey {
case hasHkIdentity = "has_hk_identity"
}
required init() {
}
}
//
// YHSurveySubmitRequestModel.swift
// galaxy
//
// Created by alexzzw on 2025/3/31.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import Foundation
// MARK: - YHSurveySubmitRequestModel
class YHSurveySubmitRequestModel {
/// 答案详情
var answerDetails: [YHSurveyArticleAnswerItem] = []
/// 问卷id
var surveyId: String = ""
init(answerDetails: [YHSurveyArticleAnswerItem], surveyId: String) {
self.answerDetails = answerDetails
self.surveyId = surveyId
}
}
// MARK: - YHSurveyArticleAnswerItem
class YHSurveyArticleAnswerItem {
/// 文件列表 仅对文件上传题有效且必需
var fileList: [YHSurveyAnswerItemFileItem] = []
/// 选中的选项ID与额外说明 仅对单选/多选有效且必需,单选时此项长度为1
var optionExtra: [YHSurveyAnswerItemOptionExtra] = []
/// 问题ID 必需
var questionId: String = ""
/// 问题key 可选 提交答案时,建议设置该字段,会作为提交记录留底;show_when场景下,忽略该字段
var questionKey: String = ""
/// 问题类型:1:单选 2:多选 3:文本输入 4:文件上传 必需
var questionType: Int = 0
/// 文本输入内容 仅对文本输入题有效且必需
var text: String = ""
/// 问题标题 可选 提交答案时,建议设置该字段,会作为提交记录留底;show_when场景下,忽略该字段
var title: String = ""
init(fileList: [YHSurveyAnswerItemFileItem], optionExtra: [YHSurveyAnswerItemOptionExtra], questionId: String, questionKey: String, questionType: Int, text: String, title: String) {
self.fileList = fileList
self.optionExtra = optionExtra
self.questionId = questionId
self.questionKey = questionKey
self.questionType = questionType
self.text = text
self.title = title
}
}
// MARK: - YHSurveyAnswerItemFileItem
class YHSurveyAnswerItemFileItem {
/// 文件名称 可选 提交答案时,建议设置该字段,会作为提交记录留底;show_when场景下,忽略该字段
var name: String = ""
/// 文件大小 可选 提交答案时,建议设置该字段,会作为提交记录留底;show_when场景下,忽略该字段
var size: Int = 0
/// 文件类型 可选 提交答案时,建议设置该字段,会作为提交记录留底;show_when场景下,忽略该字段
var type: String = ""
/// 上传时间(格式:YYYY-MM-DD HH:mm:ss) 可选 可选 提交答案时,建议设置该字段,会作为提交记录留底;show_when场景下,忽略该字段
var uploadAt: String = ""
/// 文件url 必需
var url: String = ""
init(name: String, size: Int, type: String, uploadAt: String, url: String) {
self.name = name
self.size = size
self.type = type
self.uploadAt = uploadAt
self.url = url
}
}
// MARK: - YHSurveyAnswerItemOptionExtra
class YHSurveyAnswerItemOptionExtra {
/// 问题选项额外说明 选中该选项后必需填写额外说明时必需
var extra: String = ""
/// 选项ID 必需
var optionId: String = ""
/// 选项key 可选 提交答案时,建议设置该字段,会作为提交记录留底;show_when场景下,忽略该字段
var optionKey: String = ""
/// 选项标题 可选 提交答案时,建议设置该字段,会作为提交记录留底;show_when场景下,忽略该字段
var title: String = ""
init(extra: String, optionId: String, optionKey: String, title: String) {
self.extra = extra
self.optionId = optionId
self.optionKey = optionKey
self.title = title
}
}
//
// YHMakePlanModel.swift
// galaxy
//
// Created by EDY on 2025/4/1.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
import SmartCodable
struct YHMakePlanModel: SmartCodable {
var basic: [YHMakePlanMainModel] = []
var work: [YHMakePlanMainModel] = []
var live: [YHMakePlanMainModel] = []
var invest: [YHMakePlanMainModel] = []
// 修改所有子元素的 is_add_cart 值
mutating func updateAddCartStatus() {
basic = basic.map { $0.updatedModel() }
work = work.map { $0.updatedModel() }
live = live.map { $0.updatedModel() }
invest = invest.map { $0.updatedModel() }
}
}
struct YHMakePlanMainModel: SmartCodable {
var product_id: Int = -1
var cover_img: String = ""
var product_name: String = ""
var product_desc: String = ""
var price: String = ""
var is_add_cart: Bool = true
var sub_product: [YHMakePlanSubModel] = []
// 更新当前模型及其子模型的 is_add_cart 状态
func updatedModel() -> YHMakePlanMainModel {
var updatedModel = self
updatedModel.is_add_cart = true
// 处理子产品,找到最便宜的一个
if !sub_product.isEmpty {
// 找到最便宜的子产品
let cheapestSubProduct = sub_product
.sorted { ($0.price.toDouble() ?? 0) < ($1.price.toDouble() ?? 0) }
.first
// 更新所有子产品的 is_add_cart 状态
updatedModel.sub_product = sub_product.map {
var sub = $0
sub.is_add_cart = ($0.product_id == cheapestSubProduct?.product_id)
return sub
}
}
return updatedModel
}
}
struct YHMakePlanSubModel: SmartCodable {
var product_id: Int = -1
var cover_img: String = ""
var product_name: String = ""
var product_desc: String = ""
var price: String = ""
var is_add_cart: Bool = true
}
// 扩展 String 用于价格转换
extension String {
func toDouble() -> Double? {
// 移除可能存在的货币符号、千位分隔符等
let numericString = self
.replacingOccurrences(of: "$", with: "")
.replacingOccurrences(of: ",", with: "")
.trimmingCharacters(in: .whitespacesAndNewlines)
return Double(numericString)
}
}
extension YHMakePlanModel {
mutating func toggleAddCartStatus(for productId: Int) {
var localBasic = basic
var localWork = work
var localLive = live
var localInvest = invest
for i in 0..<localBasic.count {
if localBasic[i].product_id != 0 && localBasic[i].product_id == productId {
localBasic[i].is_add_cart = !localBasic[i].is_add_cart
updateSubProducts(for: &localBasic[i])
// Update the original arrays and return
basic = localBasic
work = localWork
live = localLive
invest = localInvest
return
}
}
for i in 0..<localWork.count {
if localWork[i].product_id != 0 && localWork[i].product_id == productId {
localWork[i].is_add_cart = !localWork[i].is_add_cart
updateSubProducts(for: &localWork[i])
// Update the original arrays and return
basic = localBasic
work = localWork
live = localLive
invest = localInvest
return
}
}
for i in 0..<localLive.count {
if localLive[i].product_id != 0 && localLive[i].product_id == productId {
localLive[i].is_add_cart = !localLive[i].is_add_cart
updateSubProducts(for: &localLive[i])
// Update the original arrays and return
basic = localBasic
work = localWork
live = localLive
invest = localInvest
return
}
}
for i in 0..<localInvest.count {
if localInvest[i].product_id != 0 && localInvest[i].product_id == productId {
localInvest[i].is_add_cart = !localInvest[i].is_add_cart
updateSubProducts(for: &localInvest[i])
// Update the original arrays and return
basic = localBasic
work = localWork
live = localLive
invest = localInvest
return
}
}
// If we get here, we need to check sub_products (product_id == 0 cases)
checkSubProducts(in: &localBasic, for: productId)
checkSubProducts(in: &localWork, for: productId)
checkSubProducts(in: &localLive, for: productId)
checkSubProducts(in: &localInvest, for: productId)
basic = localBasic
work = localWork
live = localLive
invest = localInvest
}
private mutating func updateSubProducts(for mainModel: inout YHMakePlanMainModel) {
if mainModel.is_add_cart {
// Find the subproduct with the lowest price
if let cheapestIndex = mainModel.sub_product.enumerated().min(by: {
let price1 = Double($0.element.price) ?? 0
let price2 = Double($1.element.price) ?? 0
return price1 < price2
})?.offset {
// Set only the cheapest to true, others to false
for i in 0..<mainModel.sub_product.count {
mainModel.sub_product[i].is_add_cart = (i == cheapestIndex)
}
}
} else {
// If main model is not in cart, set all subproducts to false
for i in 0..<mainModel.sub_product.count {
mainModel.sub_product[i].is_add_cart = false
}
}
}
private mutating func checkSubProducts(in models: inout [YHMakePlanMainModel], for productId: Int) {
for i in 0..<models.count {
var hasFind = false
for j in 0..<models[i].sub_product.count {
if models[i].sub_product[j].product_id == productId {
if models[i].sub_product[j].is_add_cart {
models[i].sub_product[j].is_add_cart = false
models[i].is_add_cart = false
} else {
models[i].sub_product[j].is_add_cart = true
models[i].is_add_cart = true
}
hasFind = true
}
}
if hasFind {
for j in 0..<models[i].sub_product.count {
if models[i].sub_product[j].product_id == productId {
} else {
models[i].sub_product[j].is_add_cart = false
}
}
}
}
}
func calculateTotalPrice() -> String {
let allCategories = [basic, work, live, invest]
var total: Double = 0
for category in allCategories {
for mainModel in category {
// Check main product
if mainModel.is_add_cart, let price = Double(mainModel.price), mainModel.sub_product.count == 0 {
total += price
}
// Check sub products
for subModel in mainModel.sub_product {
if subModel.is_add_cart, let price = Double(subModel.price) {
total += price
}
}
}
}
return "\(total)"
}
}
extension YHMakePlanMainModel {
/// Returns a filtered version of the model, keeping only `is_add_cart == true` for itself and its sub-products.
func filteredByCartStatus() -> YHMakePlanMainModel? {
guard is_add_cart else { return nil }
var filteredModel = self
filteredModel.sub_product = sub_product.filter { $0.is_add_cart }
return filteredModel
}
}
extension Array where Element == YHMakePlanMainModel {
/// Filters out models where `is_add_cart == false` and also filters their sub-products.
func filteredByCartStatus() -> [YHMakePlanMainModel] {
self.compactMap { $0.filteredByCartStatus() }
}
}
extension Array where Element == YHMakePlanSubModel {
/// Filters out sub-models where `is_add_cart == false`.
func filteredByCartStatus() -> [YHMakePlanSubModel] {
self.filter { $0.is_add_cart }
}
}
extension Array where Element == YHMakePlanSubModel {
func indexOfLowestPrice() -> Int? {
return self.enumerated().reduce(into: (index: 0, price: Double.infinity)) { result, current in
let (currentIndex, model) = current
if let currentPrice = Double(model.price), currentPrice < result.price {
result = (currentIndex, currentPrice)
}
}.index
}
}
//
// YHMakePlanScoreModel.swift
// galaxy
//
// Created by EDY on 2025/4/2.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
import SmartCodable
struct YHMakePlanScoreModel: SmartCodable {
var year: Int = -1
var pass_rate: String = ""
var share_url: String = ""
}
//
// YHMakePlanBottomView.swift
// galaxy
//
// Created by EDY on 2025/3/28.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
import AttributedString
class YHMakePlanBottomView: UIView {
// MARK: - UI Elements
private lazy var priceLabel: UILabel = {
let label = UILabel()
let a: ASAttributedString = .init("¥", .font(UIFont.PFSC_M(ofSize: 20)), .foreground(UIColor.mainTextColor))
let b: ASAttributedString = .init("58999.00", .font(UIFont.PFSC_M(ofSize: 28)), .foreground(UIColor.mainTextColor))
label.attributed.text = a + b
return label
}()
lazy var priceButton: UIButton = {
let label = UIButton()
label.setImage(UIImage(named: "make_plan_up"), for: .normal)
label.addTarget(self, action: #selector(handleAction), for: .touchUpInside)
return label
}()
private lazy var actionButton: YHImageTextButton = {
let button = YHImageTextButton(title: "去办理", image: UIImage(named: "make_plan_next"))
button.setTitleColor(UIColor(hex: 0xffffff), for: .normal)
button.titleLabel?.font = UIFont.PFSC_M(ofSize: 16)
button.layer.cornerRadius = 2
button.backgroundColor = UIColor.mainTextColor
button.addTarget(self, action: #selector(nextAction), for: .touchUpInside)
return button
}()
// MARK: - Properties
var price: String = "" {
didSet {
let a: ASAttributedString = .init("¥", .font(UIFont(name: "D-DIN-PRO-Bold", size: 20) ?? UIFont()), .foreground(UIColor.mainTextColor))
let b: ASAttributedString = .init("\(price.formattedPrice())", .font(UIFont(name: "D-DIN-PRO-Bold", size: 28) ?? UIFont()), .foreground(UIColor.mainTextColor))
priceLabel.attributed.text = a + b
}
}
var actionHandler: (() -> Void)?
var nextHandler: (() -> Void)?
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
setupConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Setup
private func setupUI() {
backgroundColor = .white
addSubview(priceLabel)
addSubview(priceButton)
addSubview(actionButton)
}
private func setupConstraints() {
priceLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(20)
make.left.equalToSuperview().offset(16)
make.height.equalTo(28)
}
priceButton.snp.makeConstraints { make in
make.left.equalTo(priceLabel.snp.right)
make.width.height.equalTo(16)
make.centerY.equalTo(priceLabel.snp.centerY)
}
actionButton.snp.makeConstraints { make in
make.top.equalToSuperview().offset(9)
make.right.equalToSuperview().offset(-16)
make.width.equalTo(120)
make.height.equalTo(46)
}
priceButton.YH_clickEdgeInsets = UIEdgeInsets(top: 10, left: 100, bottom: 10, right: 10)
}
// MARK: - Action
@objc private func handleAction() {
actionHandler?()
}
@objc private func nextAction() {
nextHandler?()
}
// MARK: - Configuration
func configure(price: String, actionText: String? = nil, actionHandler: (() -> Void)? = nil, nextHandler: (() -> Void)? = nil) {
self.price = price
if let actionText = actionText {
actionButton.setTitle(actionText, for: .normal)
}
self.actionHandler = actionHandler
self.nextHandler = nextHandler
}
}
//
// YHMakePlanCell.swift
// galaxy
//
// Created by EDY on 2025/3/28.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
class YHMakePlanCell: UITableViewCell {
var actionHandler: ((Int) -> Void)?
var centerView: UIView!
var titleLabel: UILabel!
var mainItemView: UIView!
var country: String = ""
var index: Int = 0 {
didSet {
if index == 0 {
noDataView.tipsLabel.text = "您已满足续签至永居服务~"
} else if index == 1 {
noDataView.tipsLabel.text = "暂未推荐香港工作相关产品,详情请咨询您的专属顾问"
} else if index == 2 {
noDataView.tipsLabel.text = "暂未推荐香港工作相关产品,详情请咨询您的专属顾问"
} else if index == 3 {
noDataView.tipsLabel.text = "暂未推荐投资相关产品,详情请咨询您的专属顾问"
}
}
}
lazy var noDataView: YHEmptyDataView = {
let view = YHEmptyDataView.createView("您已满足续签至永居服务~", kNoPlanBgName)
view.frame = CGRect(x: 0, y: 36, width: KScreenWidth - 32, height: 116)
view.backgroundColor = .clear
view.isHidden = true
view.emptyBgImgV.snp.remakeConstraints { make in
make.top.equalToSuperview().offset(0)
make.width.height.equalTo(96)
make.centerX.equalTo(view)
}
view.tipsLabel.snp.remakeConstraints { make in
make.top.equalTo(view.emptyBgImgV.snp.bottom)
make.left.equalTo(20)
make.right.equalTo(-20)
}
view.tipsLabel.numberOfLines = 2
return view
}()
var dataSource: [YHMakePlanMainModel]? {
didSet {
updateAllViews()
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
setupUI()
}
func setupUI() {
contentView.backgroundColor = .clear
backgroundColor = .clear
centerView = {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = kCornerRadius6
view.layer.masksToBounds = true
return view
}()
contentView.addSubview(centerView)
centerView.snp.makeConstraints { make in
make.left.equalTo(16)
make.right.equalTo(-16)
make.top.equalTo(7)
make.bottom.equalTo(-7)
}
centerView.addSubview(noDataView)
titleLabel = {
let label = UILabel()
label.font = UIFont.PFSC_M(ofSize: 15)
label.textColor = UIColor.mainTextColor
return label
}()
centerView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.left.equalTo(18)
make.top.equalTo(16)
make.height.equalTo(20)
make.right.equalTo(-18)
}
mainItemView = {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = kCornerRadius6
return view
}()
centerView.addSubview(mainItemView)
mainItemView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(36)
}
}
func updateAllViews() {
mainItemView.removeSubviews()
if dataSource?.count == 0 {
noDataView.isHidden = false
mainItemView.isHidden = true
} else {
noDataView.isHidden = true
mainItemView.isHidden = false
}
var y = 0
for i in 0 ..< (dataSource?.count ?? 0) {
if i != 0 || i != (dataSource?.count ?? 0) - 1 {
let line = UIView()
line.backgroundColor = UIColor.separatorColor
mainItemView.addSubview(line)
line.snp.makeConstraints { make in
make.left.equalTo(18)
make.top.equalTo(y)
make.height.equalTo(1)
make.right.equalTo(-18)
}
y += 1
}
var h = 121
let itemView = YHMakePlanCardView()
itemView.actionHandler = { product in
self.actionHandler?(product)
}
itemView.configure(with: dataSource?[i] ?? YHMakePlanMainModel())
mainItemView.addSubview(itemView)
itemView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.equalTo(y)
make.height.equalTo(h)
}
y += h
}
}
}
...@@ -123,4 +123,11 @@ extension UIColor { ...@@ -123,4 +123,11 @@ extension UIColor {
UIGraphicsEndImageContext() UIGraphicsEndImageContext()
return image! return image!
} }
static func randomColor() -> UIColor {
let red = CGFloat(arc4random_uniform(256)) / 255.0
let green = CGFloat(arc4random_uniform(256)) / 255.0
let blue = CGFloat(arc4random_uniform(256)) / 255.0
return UIColor(red: red, green: green, blue: blue, alpha: 1.0)
}
} }
...@@ -752,4 +752,20 @@ class YHAllApiName { ...@@ -752,4 +752,20 @@ class YHAllApiName {
} }
// 续签方案
struct ResignPlan {
static let planlistApi = "super-app/renewal/advice/get-renewal-plan"
static let productListApi = "super-app/renewal/advice/get-renewal-product-list"
static let getRate = "super-app/renewal/advice/get-year-and-pass-rate"
static let store = "super-app/presale/app/shop-cart/store"
}
// 问卷
struct Survey {
static let surveyContent = "super-app/renewal/advice/survey-content"
static let submitSurveyAnswer = "super-app/renewal/advice/submit-survey-answer"
static let getRenewalPlanState = "super-app/renewal/advice/get-renewal-plan-is-generate"
static let getRenewalPlanGenerated = "super-app/renewal/advice/get-renewal-plan-is-generated"
}
} }
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment