角色标签 列表数据调整
This commit is contained in:
parent
beb0d3518a
commit
626c4d6fb8
|
|
@ -109,7 +109,7 @@ class SessionController: CLBaseViewController {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setupUI()
|
||||
setupData()
|
||||
// setupData()
|
||||
setupEvent()
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ struct RoleTagModel: Codable {
|
|||
}
|
||||
|
||||
struct RoleTagItem: Codable {
|
||||
var id: Int = 0
|
||||
var id: String = ""
|
||||
var name: String = ""
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import Foundation
|
|||
|
||||
struct CLRoleTagsModel: Codable {
|
||||
var name: String = ""
|
||||
var id: Int = 0
|
||||
var id: String = ""
|
||||
var isSelected: Bool = false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class RolesRootPageController: CLTabRootController<RolesRootPageView> {
|
|||
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<RolesRootPageView> {
|
|||
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<RolesRootPageView> {
|
|||
.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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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..<tagModels.count {
|
||||
tagModels[i].isSelected = false
|
||||
}
|
||||
// 选中"全部"标签
|
||||
tagModels[0].isSelected = true
|
||||
collectionView.reloadData()
|
||||
// 通知外部,选中全部标签(tagId 为 nil)
|
||||
onTagSelected?(nil)
|
||||
} else {
|
||||
// 点击的是其他标签,支持多选
|
||||
tagModels[indexPath.item].isSelected.toggle()
|
||||
|
||||
if tagModels[indexPath.item].isSelected {
|
||||
// 选中了其他标签,取消"全部"标签的选中状态
|
||||
tagModels[0].isSelected = false
|
||||
|
||||
// 获取所有选中的非 All 标签的 ID
|
||||
let selectedIds = tagModels.dropFirst().filter { $0.isSelected }.map { $0.id }
|
||||
collectionView.reloadData()
|
||||
// 通知外部,传递选中的标签ID数组
|
||||
onTagSelected?(selectedIds.isEmpty ? nil : selectedIds)
|
||||
} else {
|
||||
// 取消选中其他标签
|
||||
// 获取所有选中的非 All 标签的 ID
|
||||
let selectedIds = tagModels.dropFirst().filter { $0.isSelected }.map { $0.id }
|
||||
|
||||
if selectedIds.isEmpty {
|
||||
// 如果没有其他标签选中,默认选中"全部"标签
|
||||
tagModels[0].isSelected = true
|
||||
collectionView.reloadData()
|
||||
// 通知外部,选中全部标签(tagId 为 nil)
|
||||
onTagSelected?(nil)
|
||||
} else {
|
||||
collectionView.reloadData()
|
||||
// 通知外部,传递剩余的选中标签ID数组
|
||||
onTagSelected?(selectedIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据文字计算 size
|
||||
|
|
|
|||
|
|
@ -26,12 +26,19 @@ final class CLWaterfallLayout: UICollectionViewLayout {
|
|||
|
||||
override func prepare() {
|
||||
super.prepare()
|
||||
guard cache.isEmpty, let cv = collectionView else { return }
|
||||
guard let cv = collectionView else { return }
|
||||
|
||||
// 如果缓存不为空,且 item 数量没有变化,则不需要重新计算
|
||||
let itemCount = cv.numberOfItems(inSection: 0)
|
||||
if !cache.isEmpty && cache.count == itemCount {
|
||||
return
|
||||
}
|
||||
|
||||
// 清除旧缓存,重新计算布局
|
||||
cache.removeAll()
|
||||
contentHeight = sectionInset.top
|
||||
colHeight = .init(repeating: sectionInset.top, count: columnCount)
|
||||
|
||||
let itemCount = cv.numberOfItems(inSection: 0)
|
||||
let totalWidth = cv.bounds.width - sectionInset.left - sectionInset.right
|
||||
let cellWidth = (totalWidth - CGFloat(columnCount - 1) * columnSpacing) / CGFloat(columnCount)
|
||||
|
||||
|
|
@ -56,6 +63,14 @@ final class CLWaterfallLayout: UICollectionViewLayout {
|
|||
contentHeight = colHeight.max()! + sectionInset.bottom
|
||||
}
|
||||
|
||||
override func invalidateLayout() {
|
||||
super.invalidateLayout()
|
||||
// 清除缓存,强制重新计算布局
|
||||
cache.removeAll()
|
||||
contentHeight = 0
|
||||
colHeight.removeAll()
|
||||
}
|
||||
|
||||
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
|
||||
cache.filter { $0.frame.intersects(rect) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)],
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue