diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController.swift b/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController.swift index 3e8989e..d28e29a 100755 --- a/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController.swift +++ b/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController.swift @@ -109,7 +109,7 @@ class SessionController: CLBaseViewController { override func viewDidLoad() { super.viewDidLoad() setupUI() - setupData() +// setupData() setupEvent() } diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Setting/Model/ChatRoleTagViewModel.swift b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Model/ChatRoleTagViewModel.swift index 701c5ac..7196276 100644 --- a/Visual_Novel_iOS/Src/Modules/Chat/Setting/Model/ChatRoleTagViewModel.swift +++ b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Model/ChatRoleTagViewModel.swift @@ -18,7 +18,7 @@ struct RoleTagModel: Codable { } struct RoleTagItem: Codable { - var id: Int = 0 + var id: String = "" var name: String = "" } 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 4a3cc54..3c6aa1d 100644 --- a/Visual_Novel_iOS/Src/Modules/Chat/Setting/Model/ChatRoleViewModel.swift +++ b/Visual_Novel_iOS/Src/Modules/Chat/Setting/Model/ChatRoleViewModel.swift @@ -15,28 +15,33 @@ struct RoleListRequest: Codable { var index = 1 var limit = 20 var name: String = "" - var sourceId = 0 - var tagId: Int? + var sourceId: String = "" + var tagId: [String]? } // model struct RoleListModel: Codable { var total: Int = 0 var index: Int = -1 + var limit: Int = 0 var rows: [RoleItem] = [] } struct RoleItem: Codable { - var id: Int = 0 + var id: String = "" var name: String = "" var coverImage: String = "" + var score: Double? + var description: String = "" var updateTime: String = "" - var sourceId: Int = 0 + var sourceId: String = "" + var sourceType: Int = 0 + var commonCount: Int? } 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 = 10, name: String = "", sourceId: String = "", tagId: [String]? = nil, completion: ((_ datas: RoleListModel?) -> Void)?) { var req = RoleListRequest() req.index = index req.limit = limit diff --git a/Visual_Novel_iOS/Src/Modules/Roles/Model/CLRoleTagsModel.swift b/Visual_Novel_iOS/Src/Modules/Roles/Model/CLRoleTagsModel.swift index e9311a9..af2f6ab 100644 --- a/Visual_Novel_iOS/Src/Modules/Roles/Model/CLRoleTagsModel.swift +++ b/Visual_Novel_iOS/Src/Modules/Roles/Model/CLRoleTagsModel.swift @@ -9,7 +9,7 @@ import Foundation struct CLRoleTagsModel: Codable { var name: String = "" - var id: Int = 0 + var id: String = "" var isSelected: Bool = false } diff --git a/Visual_Novel_iOS/Src/Modules/Roles/RolesRootPageController.swift b/Visual_Novel_iOS/Src/Modules/Roles/RolesRootPageController.swift index a38a480..5163b38 100644 --- a/Visual_Novel_iOS/Src/Modules/Roles/RolesRootPageController.swift +++ b/Visual_Novel_iOS/Src/Modules/Roles/RolesRootPageController.swift @@ -15,6 +15,7 @@ class RolesRootPageController: CLTabRootController { var page: Int = 1 var viewModel: ChatRoleViewModel = ChatRoleViewModel() var tagViewModel: ChatRoleTagViewModel = ChatRoleTagViewModel() + var currentTagIds: [String]? = nil // 当前选中的标签ID数组,nil表示全部 override func viewDidLoad() { super.viewDidLoad() @@ -31,7 +32,30 @@ class RolesRootPageController: CLTabRootController { setupViews() setupEvent() - loadRoles(page: 1) + // 设置刷新和加载回调 + container.onRefresh = { [weak self] in + guard let self = self else { return } + self.page = 1 + self.loadRoles(page: 1, tagIds: self.currentTagIds) + } + + container.onLoadMore = { [weak self] in + guard let self = self else { return } + // 上拉加载时,先增加 page,然后加载数据 + // 如果返回的数据为空,page 会在 loadRoles 中回退 + let nextPage = self.page + 1 + self.loadRoles(page: nextPage, tagIds: self.currentTagIds, isLoadMore: true) + } + + // 设置标签选择回调 + container.tagsView.onTagSelected = { [weak self] tagIds in + guard let self = self else { return } + self.currentTagIds = tagIds + self.page = 1 + self.loadRoles(page: 1, tagIds: tagIds) + } + + loadRoles(page: 1, tagIds: nil) loadTags() } @@ -54,14 +78,42 @@ class RolesRootPageController: CLTabRootController { .store(in: &cancellables) } - func loadRoles(page: Int) { - viewModel.loadRoles(index: page) { [weak self] datas in - self?.container.config(datas?.rows, isFirstPage: page == 1) + func loadRoles(page: Int, name: String = "", tagIds: [String]? = nil, isLoadMore: Bool = false) { + viewModel.loadRoles(index: page, name: name, tagId: tagIds) { [weak self] datas in + guard let self = self else { return } + + // 确保在主线程更新 UI + DispatchQueue.main.async { + let isFirstPage = page == 1 + let rows = datas?.rows ?? [] + let hasData = !rows.isEmpty + + if isLoadMore { + // 上拉加载:只有当返回数据时才更新 page,否则保持原值 + if hasData { + // 有数据,更新 page + self.page = page + // 追加数据 + self.container.config(rows, isFirstPage: false) + } else { + // 没有数据,page 保持不变,显示"没有更多数据" + self.container.collectionView.mj_footer?.endRefreshingWithNoMoreData() + } + } else { + // 下拉刷新或首次加载:直接更新 page + self.page = page + self.container.config(rows, isFirstPage: isFirstPage) + } + + // 结束刷新动画 + self.container.endRefreshing() + } } } func loadTags() { - tagViewModel.loadRoleTags(name: "") { datas in + tagViewModel.loadRoleTags(name: "") { [weak self] datas in + guard let self = self else { return } self.container.configureTags(datas?.rows) } } diff --git a/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleTagsView.swift b/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleTagsView.swift index 9a48b5d..0db2cf7 100644 --- a/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleTagsView.swift +++ b/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleTagsView.swift @@ -10,6 +10,9 @@ import UIKit class CLRoleTagsView: UIView { // let tags = ["#浪漫", "#温柔", "#多愁善感", "#深情", "#this is good", "#沙瓦迪", "#科技哈", "#等好a", "#a"] + // 防止死循环的标志位 + private var isUpdatingTagModels = false + var tagModels: [CLRoleTagsModel] = [ // CLRoleTagsModel(name: "#浪漫浪漫浪漫浪漫浪漫浪漫浪漫浪漫浪漫漫浪漫浪漫浪漫漫浪漫浪漫浪漫漫浪漫浪漫浪漫", isSelected: false), // CLRoleTagsModel(name: "#温柔", isSelected: false), @@ -22,10 +25,47 @@ class CLRoleTagsView: UIView { // CLRoleTagsModel(name: "#浪漫", isSelected: false), ] { didSet { + // 防止死循环:如果正在更新,不执行 + guard !isUpdatingTagModels else { return } + + // 确保第0个位置是"全部"标签 + ensureAllTagAtFirst() self.collectionView.reloadData() } } + // 标签选择回调:返回选中的标签ID数组,nil表示全部标签 + var onTagSelected: (([String]?) -> Void)? + + // 确保第0个位置是"全部"标签 + private func ensureAllTagAtFirst() { + // 如果 tagModels 为空,添加"全部"标签 + if tagModels.isEmpty { + isUpdatingTagModels = true + tagModels = [CLRoleTagsModel(name: "All", id: "0", isSelected: true)] + isUpdatingTagModels = false + return + } + + // 检查第0个位置是否是"全部"标签 + let allTagName = "All" + if tagModels.first?.name != allTagName { + // 如果第0个位置不是"全部"标签,插入到第0个位置 + let allTag = CLRoleTagsModel(name: allTagName, id: "0", isSelected: true) + isUpdatingTagModels = true + tagModels.insert(allTag, at: 0) + isUpdatingTagModels = false + } else { + // 如果第0个位置是"全部"标签,确保它是选中状态(如果没有其他标签选中) + let hasOtherSelected = tagModels.dropFirst().contains { $0.isSelected } + if !hasOtherSelected && !tagModels[0].isSelected { + isUpdatingTagModels = true + tagModels[0].isSelected = true + isUpdatingTagModels = false + } + } + } + lazy var collectionView: AutoHeightCollectionView = { let layout = TagFlowLayout() layout.interItemSpacing = 10 // 横向间距 @@ -86,11 +126,55 @@ extension CLRoleTagsView: UICollectionViewDataSource, UICollectionViewDelegate, } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - print("点击了标签:\(tagModels[indexPath.item].name)") - tagModels[indexPath.item].isSelected.toggle() -// collectionView.reloadItems(at: [indexPath]) - collectionView.reloadData() + let selectedModel = tagModels[indexPath.item] + print("点击了标签:\(selectedModel.name)") + // 如果点击的是"全部"标签(第0个位置) + if indexPath.item == 0 { + // 如果"全部"标签已选中,不做任何操作 + if selectedModel.isSelected { + return + } + // 取消所有其他标签的选中状态 + for i in 1.. [UICollectionViewLayoutAttributes]? { cache.filter { $0.frame.intersects(rect) } } diff --git a/Visual_Novel_iOS/Src/Modules/Roles/View/RolesRootPageView.swift b/Visual_Novel_iOS/Src/Modules/Roles/View/RolesRootPageView.swift index 85167a1..12dfacf 100644 --- a/Visual_Novel_iOS/Src/Modules/Roles/View/RolesRootPageView.swift +++ b/Visual_Novel_iOS/Src/Modules/Roles/View/RolesRootPageView.swift @@ -18,8 +18,12 @@ class RolesRootPageView: CLContainer { // private lazy var pagingView = JXPagingListRefreshView(delegate: self) // lazy var headerView: + // 刷新和加载回调 + var onRefresh: (() -> Void)? + var onLoadMore: (() -> Void)? var data: [RoleItem] = [] + var hasMoreData: Bool = true // 是否还有更多数据 let remind: [String] = [ "[The Lsat Oracle of Kael]", @@ -73,7 +77,7 @@ class RolesRootPageView: CLContainer { super.init(frame: frame) setupViews() - setupDatas() + setupRefresh() } required init?(coder: NSCoder) { @@ -102,19 +106,54 @@ class RolesRootPageView: CLContainer { } } - private func setupDatas() { + // 设置下拉刷新和上拉加载 + private func setupRefresh() { + // 下拉刷新 + let header = RefreshHeaderAnimator(refreshingBlock: { [weak self] in + self?.onRefresh?() + }) + // 设置忽略的 contentInset,确保下拉刷新动画正常显示 +// header.ignoredScrollViewContentInsetTop = collectionView.contentInset.top + collectionView.mj_header = header + // 上拉加载 + let footer = RefreshFooterAnimator(refreshingBlock: { [weak self] in + self?.onLoadMore?() + }) + collectionView.mj_footer = footer } func config(_ data: [RoleItem]?, isFirstPage: Bool){ + let rows = data ?? [] + if isFirstPage { - self.data = data ?? [] + // 下拉刷新或首次加载:重置数据 + self.data = rows + // 重置上拉加载状态 + collectionView.mj_footer?.resetNoMoreData() } else { - self.data.append(contentsOf: data ?? []) + // 上拉加载:追加数据 + if !rows.isEmpty { + self.data.append(contentsOf: rows) + } + } + + // 判断是否还有更多数据 + // 如果返回的数据为空,或者数据量小于每页数量(10),说明没有更多数据了 + if rows.isEmpty || rows.count < 10 { + hasMoreData = false + } else { + hasMoreData = true } self.collectionView.reloadData() } + // 结束刷新动画 + func endRefreshing() { + collectionView.mj_header?.endRefreshing() + collectionView.mj_footer?.endRefreshing() + } + func configureTags(_ data: [RoleTagItem]?) { guard let tags = data else { self.tagsView.tagModels = [] @@ -132,19 +171,31 @@ extension RolesRootPageView: UICollectionViewDelegate, UICollectionViewDataSourc func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell: CLRoleCollectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CLRoleCollectionCell", for: indexPath) as! CLRoleCollectionCell + // 使用 indexPath.item 而不是 indexPath.row(UICollectionView 应该使用 item) + guard indexPath.item < data.count else { + return cell + } cell.setupData(item: data[indexPath.item]) return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - let sessionId = "439217670979585@r@t" - AppRouter.goChatVC(conversationId: sessionId, title: "Character · 18", complete: nil) + if data.count > indexPath.item { + let item = data[indexPath.item] + let sessionId = "439217670979585@r@t" + AppRouter.goChatVC(conversationId: sessionId, title: item.name, complete: nil) + } } func collectionView(_ collectionView: UICollectionView, layout: CLWaterfallLayout, heightForItemAt indexPath: IndexPath) -> CGFloat { + // 防止数组越界 + guard indexPath.item < data.count else { + return 0 + } + let model = data[indexPath.item] let coverH = itemWidth * (4.0 / 3.0) @@ -164,7 +215,10 @@ extension RolesRootPageView: UICollectionViewDelegate, UICollectionViewDataSourc // 取最小值,保证不足 3 行时按实际算,超过 3 行时截断在 3 行高 let textH = min(textSize.height, maxHeight) - let remindH = remind[indexPath.item].boundingRect( + // 防止 remind 数组越界,使用模运算或默认值 + let remindIndex = indexPath.item % remind.count + let remindText = remind[remindIndex] + let remindH = remindText.boundingRect( with: CGSize(width: itemWidth - 20.0, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [.font: UIFont.boldSystemFont(ofSize: 10)], diff --git a/Visual_Novel_iOS/Src/Utils/Router/AppRouterChat.swift b/Visual_Novel_iOS/Src/Utils/Router/AppRouterChat.swift index 9b4132e..74ddd13 100644 --- a/Visual_Novel_iOS/Src/Utils/Router/AppRouterChat.swift +++ b/Visual_Novel_iOS/Src/Utils/Router/AppRouterChat.swift @@ -36,7 +36,7 @@ extension AppRouter{ } static func goChatVC(conversationId: String?, title: String? = nil, complete: (() -> Void)? = nil) { - guard UserCore.shared.checkUserLoginIfNotPushUserToLogin() else{return} +// guard UserCore.shared.checkUserLoginIfNotPushUserToLogin() else{return} guard let sessionId = conversationId else{return} let vc = SessionController(conversationId: sessionId, title: title)