diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_type_play.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_type_play.imageset/Contents.json new file mode 100644 index 0000000..8bb8318 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_type_play.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_type_play@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_type_play@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_type_play.imageset/role_type_play@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_type_play.imageset/role_type_play@2x.png new file mode 100644 index 0000000..f93bb85 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_type_play.imageset/role_type_play@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_type_play.imageset/role_type_play@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_type_play.imageset/role_type_play@3x.png new file mode 100644 index 0000000..05b1eb1 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_type_play.imageset/role_type_play@3x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_type_read.imageset/Contents.json b/Visual_Novel_iOS/Assets.xcassets/Role/role_type_read.imageset/Contents.json new file mode 100644 index 0000000..8555513 --- /dev/null +++ b/Visual_Novel_iOS/Assets.xcassets/Role/role_type_read.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "role_type_read@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "role_type_read@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_type_read.imageset/role_type_read@2x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_type_read.imageset/role_type_read@2x.png new file mode 100644 index 0000000..961f3b0 Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_type_read.imageset/role_type_read@2x.png differ diff --git a/Visual_Novel_iOS/Assets.xcassets/Role/role_type_read.imageset/role_type_read@3x.png b/Visual_Novel_iOS/Assets.xcassets/Role/role_type_read.imageset/role_type_read@3x.png new file mode 100644 index 0000000..316abcc Binary files /dev/null and b/Visual_Novel_iOS/Assets.xcassets/Role/role_type_read.imageset/role_type_read@3x.png differ diff --git a/Visual_Novel_iOS/Src/API/RoleTagApi.swift b/Visual_Novel_iOS/Src/API/RoleTagApi.swift new file mode 100644 index 0000000..1bfd365 --- /dev/null +++ b/Visual_Novel_iOS/Src/API/RoleTagApi.swift @@ -0,0 +1,67 @@ +// +// RoleTagApi.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/11/7. +// + +import Moya + +let RoleTagProvider = APIConfig.useMock && UserAPI.useMock +? MoyaProvider(endpointClosure: myEndpointClosure, stubClosure: { target in + let data = target.sampleData + if(data.count > 0){ + return .delayed(seconds: 0.5) + }else{ + return .never + } +}) +: MoyaProvider(requestClosure: myRequestClosure) + +enum RoleTagApi { + static let useMock: Bool = false + + case tagList(params: [String: Any]) +} + +extension RoleTagApi: TargetType { + var baseURL: URL { + // 确保 URL 格式正确,避免强制解包导致的崩溃 + guard let url = URL(string: APIConfig.role) else { + fatalError("Invalid baseURL: \(APIConfig.role)") + } + return url + } + + var path: String { + switch self { + case .tagList: + return "/tag/list" + } + } + + var method: Moya.Method { + return .post + } + + var task: Task { + var mParams = [String: Any]() + switch self { + case .tagList(let params): + // 将传入的参数赋值给 mParams + mParams = params + } + return .requestParameters(parameters: mParams, encoding: JSONEncoding.default) + } + + var headers: [String : String]? { + return APIConfig.apiHeaders() + } + + var sampleData: Data { + switch self { + case .tagList: + return Data() + } + } +} diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Setting/Model/ChatRoleTagViewModel.swift b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Model/ChatRoleTagViewModel.swift new file mode 100644 index 0000000..701c5ac --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Model/ChatRoleTagViewModel.swift @@ -0,0 +1,49 @@ +// +// ChatRoleTagViewModel.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/11/7. +// + +import Foundation + +struct RoleTagRequest: Codable { + var limit = 20 + var name: String = "" +} + +struct RoleTagModel: Codable { + var total: Int = 0 + var rows: [RoleTagItem] = [] +} + +struct RoleTagItem: Codable { + var id: Int = 0 + var name: String = "" +} + +class ChatRoleTagViewModel { + + func loadRoleTags(name: String, limit: Int = 20, completion: ((_ datas: RoleTagModel?) -> Void)?) { + var req = RoleTagRequest() + req.limit = limit + req.name = name + + let params = req.toNonNilDictionary() + + RoleTagProvider.request(.tagList(params: params), modelType: RoleTagModel.self) { result in + switch result { + case .success(let model): + // 如果返回的是单个 RoleListModel,将其包装成数组 + if let model = model { + completion?(model) + } else { + completion?(nil) + } + case .failure(let failure): + dlog("⛔️ 加载角色标签失败: \(failure)") + completion?(nil) + } + } + } +} diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Setting/Model/ChatRoleViewModel.swift b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Model/ChatRoleViewModel.swift index 92fe652..4a3cc54 100644 --- a/Visual_Novel_iOS/Src/Modules/Chat/Setting/Model/ChatRoleViewModel.swift +++ b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Model/ChatRoleViewModel.swift @@ -8,6 +8,8 @@ import Foundation import CodableWrappers +// 角色列表相关 + // request struct RoleListRequest: Codable { var index = 1 @@ -34,7 +36,7 @@ struct RoleItem: Codable { class ChatRoleViewModel { - func loadRoles(index: Int, limit: Int = 20, name: String = "", sourceId: Int = -1, tagId: Int? = nil, completion: ((_ datas: [RoleListModel]?) -> Void)?) { + func loadRoles(index: Int, limit: Int = 20, name: String = "", sourceId: Int = -1, tagId: Int? = nil, completion: ((_ datas: RoleListModel?) -> Void)?) { var req = RoleListRequest() req.index = index req.limit = limit @@ -51,7 +53,7 @@ class ChatRoleViewModel { case .success(let model): // 如果返回的是单个 RoleListModel,将其包装成数组 if let model = model { - completion?([model]) + completion?(model) } else { completion?(nil) } diff --git a/Visual_Novel_iOS/Src/Modules/Home/HomePageRootController.swift b/Visual_Novel_iOS/Src/Modules/Home/HomePageRootController.swift index cf415f2..230c8f2 100644 --- a/Visual_Novel_iOS/Src/Modules/Home/HomePageRootController.swift +++ b/Visual_Novel_iOS/Src/Modules/Home/HomePageRootController.swift @@ -94,10 +94,10 @@ class HomePageRootController: CLTabRootController { } private func setupDatas() { - setupOrResetFilterModel() - - // viewModel.loadCards() - loadFirstCards() +// setupOrResetFilterModel() +// +// // viewModel.loadCards() +// loadFirstCards() } private func setupOrResetFilterModel(loadNewData: Bool = false){ diff --git a/Visual_Novel_iOS/Src/Modules/Roles/Model/CLRoleTagsModel.swift b/Visual_Novel_iOS/Src/Modules/Roles/Model/CLRoleTagsModel.swift index 44e6634..e9311a9 100644 --- a/Visual_Novel_iOS/Src/Modules/Roles/Model/CLRoleTagsModel.swift +++ b/Visual_Novel_iOS/Src/Modules/Roles/Model/CLRoleTagsModel.swift @@ -9,5 +9,14 @@ import Foundation struct CLRoleTagsModel: Codable { var name: String = "" + var id: Int = 0 var isSelected: Bool = false } + +extension CLRoleTagsModel { + init(_ item: RoleTagItem) { + self.id = item.id + self.name = item.name + self.isSelected = false + } +} diff --git a/Visual_Novel_iOS/Src/Modules/Roles/RolesRootPageController.swift b/Visual_Novel_iOS/Src/Modules/Roles/RolesRootPageController.swift index 544b194..a38a480 100644 --- a/Visual_Novel_iOS/Src/Modules/Roles/RolesRootPageController.swift +++ b/Visual_Novel_iOS/Src/Modules/Roles/RolesRootPageController.swift @@ -14,7 +14,8 @@ class RolesRootPageController: CLTabRootController { private var cancellables = Set() var page: Int = 1 var viewModel: ChatRoleViewModel = ChatRoleViewModel() - + var tagViewModel: ChatRoleTagViewModel = ChatRoleTagViewModel() + override func viewDidLoad() { super.viewDidLoad() @@ -29,11 +30,13 @@ class RolesRootPageController: CLTabRootController { setupViews() setupEvent() + + loadRoles(page: 1) + loadTags() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - loadData() } private func setupViews() { @@ -51,15 +54,15 @@ class RolesRootPageController: CLTabRootController { .store(in: &cancellables) } - func loadData() { -// if page == 1{ -//// loadedAiIds.removeAll() -// } - - viewModel.loadRoles(index: page) { datas in - print(datas) - print("11111") - + func loadRoles(page: Int) { + viewModel.loadRoles(index: page) { [weak self] datas in + self?.container.config(datas?.rows, isFirstPage: page == 1) + } + } + + func loadTags() { + tagViewModel.loadRoleTags(name: "") { datas in + self.container.configureTags(datas?.rows) } } diff --git a/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleCollectionCell.swift b/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleCollectionCell.swift index 3e09780..2d4f85c 100644 --- a/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleCollectionCell.swift +++ b/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleCollectionCell.swift @@ -6,6 +6,7 @@ // import UIKit +import SDWebImage class CLRoleCollectionCell: UICollectionViewCell { @@ -17,12 +18,38 @@ class CLRoleCollectionCell: UICollectionViewCell { return imgView }() + lazy var bookImgView: UIImageView = { + let imgView = UIImageView() + imgView.backgroundColor = .blue + imgView.cornerRadius = 7 + return imgView + }() + lazy var fromImgView: UIImageView = { let imgView = UIImageView(image: UIImage(named: "role_from")) imgView.contentMode = .scaleAspectFill return imgView }() + lazy var playImgView: UIImageView = { + let imgView = UIImageView(image: UIImage(named: "role_type_play")) + imgView.isHidden = true + return imgView + }() + + lazy var readImgView: UIImageView = { + let imgView = UIImageView(image: UIImage(named: "role_type_read")) + return imgView + }() + + lazy var typeStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [playImgView, readImgView]) + stackView.spacing = 0 + stackView.distribution = .fill + stackView.alignment = .fill + return stackView + }() + lazy var coverImgView: UIImageView = { let imgView = UIImageView() imgView.contentMode = .scaleAspectFill @@ -105,8 +132,11 @@ class CLRoleCollectionCell: UICollectionViewCell { } // MARK: data - func setupData(desc: String) { - descLab.text = desc + func setupData(item: RoleItem) { + descLab.text = item.name + nameLab.text = item.name + coverImgView.sd_setImage(with: URL(string: item.coverImage), placeholderImage: nil) +// coverImgView. } // MARK: subviews @@ -117,7 +147,9 @@ class CLRoleCollectionCell: UICollectionViewCell { contentView.addSubview(bottomShadowImgView) contentView.addSubview(bookBgImgView) - contentView.addSubview(fromImgView) + bookBgImgView.addSubview(bookImgView) +// contentView.addSubview(fromImgView) + bookImgView.addSubview(typeStackView) contentView.addSubview(starImgView) contentView.addSubview(sourceLab) @@ -148,9 +180,19 @@ class CLRoleCollectionCell: UICollectionViewCell { bookBgImgView.snp.makeConstraints { make in make.top.left.equalToSuperview() } +// +// fromImgView.snp.makeConstraints { make in +// make.top.left.equalToSuperview() +// } - fromImgView.snp.makeConstraints { make in - make.top.left.equalToSuperview() + bookImgView.snp.makeConstraints { make in + make.left.top.equalToSuperview().inset(3) + make.right.equalToSuperview().inset(4) + make.bottom.equalToSuperview().inset(10) + } + + typeStackView.snp.makeConstraints { make in + make.bottom.right.equalToSuperview().inset(0) } sourceLab.snp.makeConstraints { make in diff --git a/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleTagsView.swift b/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleTagsView.swift index 109e942..9a48b5d 100644 --- a/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleTagsView.swift +++ b/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleTagsView.swift @@ -11,16 +11,20 @@ class CLRoleTagsView: UIView { // let tags = ["#浪漫", "#温柔", "#多愁善感", "#深情", "#this is good", "#沙瓦迪", "#科技哈", "#等好a", "#a"] var tagModels: [CLRoleTagsModel] = [ - CLRoleTagsModel(name: "#浪漫浪漫浪漫浪漫浪漫浪漫浪漫浪漫浪漫漫浪漫浪漫浪漫漫浪漫浪漫浪漫漫浪漫浪漫浪漫", isSelected: false), - CLRoleTagsModel(name: "#温柔", isSelected: false), - CLRoleTagsModel(name: "#多愁善感", isSelected: false), - CLRoleTagsModel(name: "#this is good", isSelected: false), - CLRoleTagsModel(name: "#沙瓦迪", isSelected: false), - CLRoleTagsModel(name: "#科技哈", isSelected: false), - CLRoleTagsModel(name: "#等好a", isSelected: false), - CLRoleTagsModel(name: "#a", isSelected: false), - CLRoleTagsModel(name: "#浪漫", isSelected: false), - ] +// CLRoleTagsModel(name: "#浪漫浪漫浪漫浪漫浪漫浪漫浪漫浪漫浪漫漫浪漫浪漫浪漫漫浪漫浪漫浪漫漫浪漫浪漫浪漫", isSelected: false), +// CLRoleTagsModel(name: "#温柔", isSelected: false), +// CLRoleTagsModel(name: "#多愁善感", isSelected: false), +// CLRoleTagsModel(name: "#this is good", isSelected: false), +// CLRoleTagsModel(name: "#沙瓦迪", isSelected: false), +// CLRoleTagsModel(name: "#科技哈", isSelected: false), +// CLRoleTagsModel(name: "#等好a", isSelected: false), +// CLRoleTagsModel(name: "#a", isSelected: false), +// CLRoleTagsModel(name: "#浪漫", isSelected: false), + ] { + didSet { + self.collectionView.reloadData() + } + } lazy var collectionView: AutoHeightCollectionView = { let layout = TagFlowLayout() diff --git a/Visual_Novel_iOS/Src/Modules/Roles/View/RolesRootPageView.swift b/Visual_Novel_iOS/Src/Modules/Roles/View/RolesRootPageView.swift index 29a22a7..85167a1 100644 --- a/Visual_Novel_iOS/Src/Modules/Roles/View/RolesRootPageView.swift +++ b/Visual_Novel_iOS/Src/Modules/Roles/View/RolesRootPageView.swift @@ -15,24 +15,23 @@ class RolesRootPageView: CLContainer { let itemWidth: CGFloat = (UIScreen.width - 30.0) / 2.0 var jumpPublisher: AnyPublisher { topView.jumpPublisher } -// private lazy var pagingView = JXPagingListRefreshView(delegate: self) -// lazy var headerView: - - - let data: [String] = [ - "Once a prodigy, Lin Feng had his cultivation shattered and was cast out Once a prodigy, Lin Feng had his cultivation shattered and was cast", - "Once a prodigy, Lin Feng had his cultivation", - "Once a prodigy, Lin Feng had his cultivation shattered and was cast out Once a prodigy", - "Once a prodigy", - "Once a prodigy, Lin Feng had his cultivation shattered and was cast out Once a prodigy, Lin Feng had his cultivation shattered and was cast Once a prodigy, Lin Feng had his cultivation shattered and was cast out Once a prodigy, Lin Feng had his cultivation shattered and was cast", - "Once a prodigy, Lin Feng had his cultivation shattered and was cast out Once a prodigy", - "Once a prodigy, Lin Feng had his cultivation shattered and was cast out Once a prodigy", - "Once a prodigy", - "Once a prodigy, Lin Feng had his cultivation shattered and was cast out Once a prodigy, Lin Feng had his cultivation shattered and was cast", - "Once a prodigy" - ] + // private lazy var pagingView = JXPagingListRefreshView(delegate: self) + // lazy var headerView: + + + var data: [RoleItem] = [] let remind: [String] = [ + "[The Lsat Oracle of Kael]", + "[The Lsat Oracle of Kael]", + "[The Lsat Oracle of Kael]", + "[The Lsat Oracle of Kael]", + "[The Lsat Oracle of Kael]", + "[The Lsat Oracle of Kael]", + "[The Lsat Oracle of Kael]", + "[The Lsat Oracle of Kael]", + "[The Lsat Oracle of Kael]", + "[The Lsat Oracle of Kael]", "[The Lsat Oracle of Kael]", "[The Lsat Oracle of Kael]", "[The Lsat Oracle of Kael]", @@ -83,7 +82,7 @@ class RolesRootPageView: CLContainer { private func setupViews() { addSubview(self.topView) -// addSubview(tagsChooseView) + // addSubview(tagsChooseView) addSubview(tagsView) addSubview(collectionView) @@ -106,6 +105,23 @@ class RolesRootPageView: CLContainer { private func setupDatas() { } + + func config(_ data: [RoleItem]?, isFirstPage: Bool){ + if isFirstPage { + self.data = data ?? [] + } else { + self.data.append(contentsOf: data ?? []) + } + self.collectionView.reloadData() + } + + func configureTags(_ data: [RoleTagItem]?) { + guard let tags = data else { + self.tagsView.tagModels = [] + return + } + self.tagsView.tagModels = tags.map(CLRoleTagsModel.init) + } } extension RolesRootPageView: UICollectionViewDelegate, UICollectionViewDataSource, WaterfallLayoutDelegate { @@ -116,7 +132,7 @@ extension RolesRootPageView: UICollectionViewDelegate, UICollectionViewDataSourc func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell: CLRoleCollectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CLRoleCollectionCell", for: indexPath) as! CLRoleCollectionCell - cell.setupData(desc: data[indexPath.item]) + cell.setupData(item: data[indexPath.item]) return cell } @@ -139,7 +155,7 @@ extension RolesRootPageView: UICollectionViewDelegate, UICollectionViewDataSourc let maxHeight = lineHeight * CGFloat(maxLines) // 真实文本高度(不会超过 maxHeight) - let textSize = model.boundingRect( + let textSize = model.name.boundingRect( with: CGSize(width: itemWidth - 20, height: maxHeight), // 高度封顶 options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [.font: font],