角色标签 列表数据调整

This commit is contained in:
mh 2025-11-10 15:25:25 +08:00
parent beb0d3518a
commit 626c4d6fb8
9 changed files with 237 additions and 27 deletions

View File

@ -109,7 +109,7 @@ class SessionController: CLBaseViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
setupUI() setupUI()
setupData() // setupData()
setupEvent() setupEvent()
} }

View File

@ -18,7 +18,7 @@ struct RoleTagModel: Codable {
} }
struct RoleTagItem: Codable { struct RoleTagItem: Codable {
var id: Int = 0 var id: String = ""
var name: String = "" var name: String = ""
} }

View File

@ -15,28 +15,33 @@ struct RoleListRequest: Codable {
var index = 1 var index = 1
var limit = 20 var limit = 20
var name: String = "" var name: String = ""
var sourceId = 0 var sourceId: String = ""
var tagId: Int? var tagId: [String]?
} }
// model // model
struct RoleListModel: Codable { struct RoleListModel: Codable {
var total: Int = 0 var total: Int = 0
var index: Int = -1 var index: Int = -1
var limit: Int = 0
var rows: [RoleItem] = [] var rows: [RoleItem] = []
} }
struct RoleItem: Codable { struct RoleItem: Codable {
var id: Int = 0 var id: String = ""
var name: String = "" var name: String = ""
var coverImage: String = "" var coverImage: String = ""
var score: Double?
var description: String = ""
var updateTime: String = "" var updateTime: String = ""
var sourceId: Int = 0 var sourceId: String = ""
var sourceType: Int = 0
var commonCount: Int?
} }
class ChatRoleViewModel { 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() var req = RoleListRequest()
req.index = index req.index = index
req.limit = limit req.limit = limit

View File

@ -9,7 +9,7 @@ import Foundation
struct CLRoleTagsModel: Codable { struct CLRoleTagsModel: Codable {
var name: String = "" var name: String = ""
var id: Int = 0 var id: String = ""
var isSelected: Bool = false var isSelected: Bool = false
} }

View File

@ -15,6 +15,7 @@ class RolesRootPageController: CLTabRootController<RolesRootPageView> {
var page: Int = 1 var page: Int = 1
var viewModel: ChatRoleViewModel = ChatRoleViewModel() var viewModel: ChatRoleViewModel = ChatRoleViewModel()
var tagViewModel: ChatRoleTagViewModel = ChatRoleTagViewModel() var tagViewModel: ChatRoleTagViewModel = ChatRoleTagViewModel()
var currentTagIds: [String]? = nil // IDnil
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -31,7 +32,30 @@ class RolesRootPageController: CLTabRootController<RolesRootPageView> {
setupViews() setupViews()
setupEvent() 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() loadTags()
} }
@ -54,14 +78,42 @@ class RolesRootPageController: CLTabRootController<RolesRootPageView> {
.store(in: &cancellables) .store(in: &cancellables)
} }
func loadRoles(page: Int) { func loadRoles(page: Int, name: String = "", tagIds: [String]? = nil, isLoadMore: Bool = false) {
viewModel.loadRoles(index: page) { [weak self] datas in viewModel.loadRoles(index: page, name: name, tagId: tagIds) { [weak self] datas in
self?.container.config(datas?.rows, isFirstPage: page == 1) 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() { 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) self.container.configureTags(datas?.rows)
} }
} }

View File

@ -10,6 +10,9 @@ import UIKit
class CLRoleTagsView: UIView { class CLRoleTagsView: UIView {
// let tags = ["#", "#", "#", "#", "#this is good", "#", "#", "#a", "#a"] // let tags = ["#", "#", "#", "#", "#this is good", "#", "#", "#a", "#a"]
//
private var isUpdatingTagModels = false
var tagModels: [CLRoleTagsModel] = [ var tagModels: [CLRoleTagsModel] = [
// CLRoleTagsModel(name: "#", isSelected: false), // CLRoleTagsModel(name: "#", isSelected: false),
// CLRoleTagsModel(name: "#", isSelected: false), // CLRoleTagsModel(name: "#", isSelected: false),
@ -22,10 +25,47 @@ class CLRoleTagsView: UIView {
// CLRoleTagsModel(name: "#", isSelected: false), // CLRoleTagsModel(name: "#", isSelected: false),
] { ] {
didSet { didSet {
//
guard !isUpdatingTagModels else { return }
// 0""
ensureAllTagAtFirst()
self.collectionView.reloadData() self.collectionView.reloadData()
} }
} }
// IDnil
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 = { lazy var collectionView: AutoHeightCollectionView = {
let layout = TagFlowLayout() let layout = TagFlowLayout()
layout.interItemSpacing = 10 // layout.interItemSpacing = 10 //
@ -86,11 +126,55 @@ extension CLRoleTagsView: UICollectionViewDataSource, UICollectionViewDelegate,
} }
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("点击了标签:\(tagModels[indexPath.item].name)") let selectedModel = tagModels[indexPath.item]
tagModels[indexPath.item].isSelected.toggle() print("点击了标签:\(selectedModel.name)")
// collectionView.reloadItems(at: [indexPath])
collectionView.reloadData()
// ""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 // size

View File

@ -26,12 +26,19 @@ final class CLWaterfallLayout: UICollectionViewLayout {
override func prepare() { override func prepare() {
super.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 contentHeight = sectionInset.top
colHeight = .init(repeating: sectionInset.top, count: columnCount) colHeight = .init(repeating: sectionInset.top, count: columnCount)
let itemCount = cv.numberOfItems(inSection: 0)
let totalWidth = cv.bounds.width - sectionInset.left - sectionInset.right let totalWidth = cv.bounds.width - sectionInset.left - sectionInset.right
let cellWidth = (totalWidth - CGFloat(columnCount - 1) * columnSpacing) / CGFloat(columnCount) let cellWidth = (totalWidth - CGFloat(columnCount - 1) * columnSpacing) / CGFloat(columnCount)
@ -56,6 +63,14 @@ final class CLWaterfallLayout: UICollectionViewLayout {
contentHeight = colHeight.max()! + sectionInset.bottom contentHeight = colHeight.max()! + sectionInset.bottom
} }
override func invalidateLayout() {
super.invalidateLayout()
//
cache.removeAll()
contentHeight = 0
colHeight.removeAll()
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
cache.filter { $0.frame.intersects(rect) } cache.filter { $0.frame.intersects(rect) }
} }

View File

@ -18,8 +18,12 @@ class RolesRootPageView: CLContainer {
// private lazy var pagingView = JXPagingListRefreshView(delegate: self) // private lazy var pagingView = JXPagingListRefreshView(delegate: self)
// lazy var headerView: // lazy var headerView:
//
var onRefresh: (() -> Void)?
var onLoadMore: (() -> Void)?
var data: [RoleItem] = [] var data: [RoleItem] = []
var hasMoreData: Bool = true //
let remind: [String] = [ let remind: [String] = [
"[The Lsat Oracle of Kael]", "[The Lsat Oracle of Kael]",
@ -73,7 +77,7 @@ class RolesRootPageView: CLContainer {
super.init(frame: frame) super.init(frame: frame)
setupViews() setupViews()
setupDatas() setupRefresh()
} }
required init?(coder: NSCoder) { 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){ func config(_ data: [RoleItem]?, isFirstPage: Bool){
let rows = data ?? []
if isFirstPage { if isFirstPage {
self.data = data ?? [] //
self.data = rows
//
collectionView.mj_footer?.resetNoMoreData()
} else { } 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() self.collectionView.reloadData()
} }
//
func endRefreshing() {
collectionView.mj_header?.endRefreshing()
collectionView.mj_footer?.endRefreshing()
}
func configureTags(_ data: [RoleTagItem]?) { func configureTags(_ data: [RoleTagItem]?) {
guard let tags = data else { guard let tags = data else {
self.tagsView.tagModels = [] self.tagsView.tagModels = []
@ -132,19 +171,31 @@ extension RolesRootPageView: UICollectionViewDelegate, UICollectionViewDataSourc
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: CLRoleCollectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CLRoleCollectionCell", for: indexPath) as! CLRoleCollectionCell let cell: CLRoleCollectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CLRoleCollectionCell", for: indexPath) as! CLRoleCollectionCell
// 使 indexPath.item indexPath.rowUICollectionView 使 item
guard indexPath.item < data.count else {
return cell
}
cell.setupData(item: data[indexPath.item]) cell.setupData(item: data[indexPath.item])
return cell return cell
} }
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if data.count > indexPath.item {
let item = data[indexPath.item]
let sessionId = "439217670979585@r@t" let sessionId = "439217670979585@r@t"
AppRouter.goChatVC(conversationId: sessionId, title: "Character · 18", complete: nil) AppRouter.goChatVC(conversationId: sessionId, title: item.name, complete: nil)
}
} }
func collectionView(_ collectionView: UICollectionView, func collectionView(_ collectionView: UICollectionView,
layout: CLWaterfallLayout, layout: CLWaterfallLayout,
heightForItemAt indexPath: IndexPath) -> CGFloat { heightForItemAt indexPath: IndexPath) -> CGFloat {
//
guard indexPath.item < data.count else {
return 0
}
let model = data[indexPath.item] let model = data[indexPath.item]
let coverH = itemWidth * (4.0 / 3.0) let coverH = itemWidth * (4.0 / 3.0)
@ -164,7 +215,10 @@ extension RolesRootPageView: UICollectionViewDelegate, UICollectionViewDataSourc
// 3 3 3 // 3 3 3
let textH = min(textSize.height, maxHeight) 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), with: CGSize(width: itemWidth - 20.0, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin, options: .usesLineFragmentOrigin,
attributes: [.font: UIFont.boldSystemFont(ofSize: 10)], attributes: [.font: UIFont.boldSystemFont(ofSize: 10)],

View File

@ -36,7 +36,7 @@ extension AppRouter{
} }
static func goChatVC(conversationId: String?, title: String? = nil, complete: (() -> Void)? = nil) { 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} guard let sessionId = conversationId else{return}
let vc = SessionController(conversationId: sessionId, title: title) let vc = SessionController(conversationId: sessionId, title: title)