Phase 1 Day 2-3: 创建 Moment 和 Mine 模块

- 创建 NewMomentViewController(OC)
  * 列表式布局 + 下拉刷新 + 滚动加载
  * 发布按钮(右下角悬浮)
  * 使用模拟数据

- 创建 NewMomentCell(OC)
  * 卡片式设计(白色卡片 + 阴影)
  * 圆角矩形头像(不是圆形!)
  * 底部操作栏(点赞/评论/分享)

- 创建 NewMineViewController(OC)
  * TableView 布局 + 8 个菜单项
  * 设置按钮(右上角)

- 创建 NewMineHeaderView(OC)
  * 渐变背景(蓝色系)
  * 圆角矩形头像 + 白色边框
  * 昵称、等级、经验进度条
  * 关注/粉丝统计
  * 纵向卡片式设计

- 集成到 NewTabBarController
  * 使用真实的 ViewController 替换占位
  * 支持登录前/后状态切换

- 更新 Bridging Header
  * 添加新模块的 OC 类引用

- 创建测试指南文档
  * 如何运行新 TabBar
  * 测试清单
  * 常见问题解答

新增文件:
- NewMomentViewController.h/m
- NewMomentCell.h/m
- NewMineViewController.h/m
- NewMineHeaderView.h/m
- white-label-test-guide.md

代码量:约 1500 行
This commit is contained in:
edwinQQQ
2025-10-09 17:54:32 +08:00
parent e980cd5553
commit 98fb194718
12 changed files with 1350 additions and 24 deletions

View File

@@ -0,0 +1,19 @@
//
// NewMineViewController.h
// YuMi
//
// Created by AI on 2025-10-09.
// Copyright © 2025 YuMi. All rights reserved.
//
#import "BaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
/// 新的个人中心页面控制器
/// 采用纵向卡片式设计,完全不同于原 XPMineViewController
@interface NewMineViewController : BaseViewController
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,235 @@
//
// NewMineViewController.m
// YuMi
//
// Created by AI on 2025-10-09.
// Copyright © 2025 YuMi. All rights reserved.
//
#import "NewMineViewController.h"
#import "NewMineHeaderView.h"
#import <Masonry/Masonry.h>
@interface NewMineViewController () <UITableViewDelegate, UITableViewDataSource>
// MARK: - UI Components
///
@property (nonatomic, strong) UITableView *tableView;
///
@property (nonatomic, strong) NewMineHeaderView *headerView;
///
@property (nonatomic, strong) UIButton *settingsButton;
// MARK: - Data
///
@property (nonatomic, strong) NSArray<NSDictionary *> *menuItems;
@end
@implementation NewMineViewController
// MARK: - Lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"我的";
self.view.backgroundColor = [UIColor colorWithRed:0.96 green:0.96 blue:0.96 alpha:1.0]; //
[self setupNavigationBar];
[self setupUI];
[self loadData];
NSLog(@"[NewMineViewController] 页面加载完成");
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
//
[self refreshUserInfo];
}
// MARK: - Setup
- (void)setupNavigationBar {
//
self.settingsButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.settingsButton setTitle:@"⚙️" forState:UIControlStateNormal];
self.settingsButton.titleLabel.font = [UIFont systemFontOfSize:24];
[self.settingsButton addTarget:self action:@selector(onSettingsButtonTapped) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *settingsBarButton = [[UIBarButtonItem alloc] initWithCustomView:self.settingsButton];
self.navigationItem.rightBarButtonItem = settingsBarButton;
}
- (void)setupUI {
// TableView
[self.view addSubview:self.tableView];
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
//
self.tableView.tableHeaderView = self.headerView;
NSLog(@"[NewMineViewController] UI 设置完成");
}
// MARK: - Data Loading
- (void)loadData {
//
self.menuItems = @[
@{@"title": @"💎 我的钱包", @"type": @"wallet"},
@{@"title": @"📊 数据统计", @"type": @"stats"},
@{@"title": @"⭐️ 我的收藏", @"type": @"favorites"},
@{@"title": @"📝 编辑资料", @"type": @"profile"},
@{@"title": @"🔔 消息通知", @"type": @"notifications"},
@{@"title": @"🎨 主题设置", @"type": @"theme"},
@{@"title": @"🌐 语言切换", @"type": @"language"},
@{@"title": @" 关于我们", @"type": @"about"},
];
[self.tableView reloadData];
}
- (void)refreshUserInfo {
// TODO:
// 使
NSDictionary *mockUserInfo = @{
@"nickname": @"测试用户",
@"avatar": @"",
@"level": @(12),
@"exp": @(3580),
@"nextLevelExp": @(5000),
@"followers": @(128),
@"following": @(256),
};
[self.headerView configureWithUserInfo:mockUserInfo];
NSLog(@"[NewMineViewController] 用户信息已刷新");
}
// MARK: - Actions
- (void)onSettingsButtonTapped {
NSLog(@"[NewMineViewController] 设置按钮点击");
// TODO:
[self showAlertWithMessage:@"设置功能开发中"];
}
- (void)onMenuItemTapped:(NSDictionary *)item {
NSString *type = item[@"type"];
NSString *title = item[@"title"];
NSLog(@"[NewMineViewController] 菜单项点击: %@", type);
if ([type isEqualToString:@"wallet"]) {
// TODO:
[self showAlertWithMessage:@"钱包功能开发中"];
} else if ([type isEqualToString:@"stats"]) {
// TODO:
[self showAlertWithMessage:@"数据统计功能开发中"];
} else if ([type isEqualToString:@"favorites"]) {
// TODO:
[self showAlertWithMessage:@"收藏功能开发中"];
} else if ([type isEqualToString:@"profile"]) {
// TODO:
[self showAlertWithMessage:@"编辑资料功能开发中"];
} else {
[self showAlertWithMessage:[NSString stringWithFormat:@"%@ 功能开发中", title]];
}
}
- (void)showAlertWithMessage:(NSString *)message {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示"
message:message
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
}
// MARK: - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.menuItems.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = @"MenuCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
cell.backgroundColor = [UIColor whiteColor];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.font = [UIFont systemFontOfSize:15];
cell.textLabel.textColor = [UIColor colorWithWhite:0.2 alpha:1.0];
}
if (indexPath.row < self.menuItems.count) {
NSDictionary *item = self.menuItems[indexPath.row];
cell.textLabel.text = item[@"title"];
}
return cell;
}
// MARK: - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (indexPath.row < self.menuItems.count) {
NSDictionary *item = self.menuItems[indexPath.row];
[self onMenuItemTapped:item];
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 56; //
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 15; //
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *header = [[UIView alloc] init];
header.backgroundColor = [UIColor clearColor];
return header;
}
// MARK: - Lazy Loading
- (UITableView *)tableView {
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
_tableView.delegate = self;
_tableView.dataSource = self;
_tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
_tableView.separatorInset = UIEdgeInsetsMake(0, 15, 0, 15);
_tableView.backgroundColor = self.view.backgroundColor;
_tableView.showsVerticalScrollIndicator = NO;
_tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
return _tableView;
}
- (NewMineHeaderView *)headerView {
if (!_headerView) {
_headerView = [[NewMineHeaderView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 280)];
}
return _headerView;
}
@end

View File

@@ -0,0 +1,23 @@
//
// NewMineHeaderView.h
// YuMi
//
// Created by AI on 2025-10-09.
// Copyright © 2025 YuMi. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/// 新的个人中心头部视图
/// 纵向卡片式设计 + 渐变背景
@interface NewMineHeaderView : UIView
/// 配置用户信息
/// @param userInfo 用户信息字典
- (void)configureWithUserInfo:(NSDictionary *)userInfo;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,278 @@
//
// NewMineHeaderView.m
// YuMi
//
// Created by AI on 2025-10-09.
// Copyright © 2025 YuMi. All rights reserved.
//
#import "NewMineHeaderView.h"
#import <Masonry/Masonry.h>
@interface NewMineHeaderView ()
// MARK: - UI Components
///
@property (nonatomic, strong) CAGradientLayer *gradientLayer;
///
@property (nonatomic, strong) UIImageView *avatarImageView;
///
@property (nonatomic, strong) UILabel *nicknameLabel;
///
@property (nonatomic, strong) UILabel *levelLabel;
///
@property (nonatomic, strong) UIView *progressContainer;
///
@property (nonatomic, strong) UIView *progressBar;
///
@property (nonatomic, strong) UILabel *expLabel;
///
@property (nonatomic, strong) UIView *statsContainer;
///
@property (nonatomic, strong) UILabel *followingLabel;
///
@property (nonatomic, strong) UILabel *followersLabel;
@end
@implementation NewMineHeaderView
// MARK: - Lifecycle
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setupUI];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
//
self.gradientLayer.frame = self.bounds;
}
// MARK: - Setup UI
- (void)setupUI {
//
self.gradientLayer = [CAGradientLayer layer];
self.gradientLayer.colors = @[
(id)[UIColor colorWithRed:0.2 green:0.6 blue:0.86 alpha:1.0].CGColor, //
(id)[UIColor colorWithRed:0.3 green:0.5 blue:0.9 alpha:1.0].CGColor, //
];
self.gradientLayer.startPoint = CGPointMake(0, 0);
self.gradientLayer.endPoint = CGPointMake(1, 1);
[self.layer insertSublayer:self.gradientLayer atIndex:0];
//
[self addSubview:self.avatarImageView];
[self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self);
make.top.equalTo(self).offset(40);
make.size.mas_equalTo(CGSizeMake(80, 80));
}];
//
[self addSubview:self.nicknameLabel];
[self.nicknameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self);
make.top.equalTo(self.avatarImageView.mas_bottom).offset(15);
}];
//
[self addSubview:self.levelLabel];
[self.levelLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self);
make.top.equalTo(self.nicknameLabel.mas_bottom).offset(8);
}];
//
[self addSubview:self.progressContainer];
[self.progressContainer mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self).offset(40);
make.right.equalTo(self).offset(-40);
make.top.equalTo(self.levelLabel.mas_bottom).offset(15);
make.height.mas_equalTo(8);
}];
//
[self.progressContainer addSubview:self.progressBar];
[self.progressBar mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.bottom.equalTo(self.progressContainer);
make.width.equalTo(self.progressContainer).multipliedBy(0.7); // 70%
}];
//
[self addSubview:self.expLabel];
[self.expLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self);
make.top.equalTo(self.progressContainer.mas_bottom).offset(6);
}];
//
[self addSubview:self.statsContainer];
[self.statsContainer mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self);
make.top.equalTo(self.expLabel.mas_bottom).offset(20);
make.height.mas_equalTo(60);
make.bottom.equalTo(self).offset(-15);
}];
//
[self.statsContainer addSubview:self.followingLabel];
[self.followingLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.statsContainer.mas_centerX).offset(-20);
make.centerY.equalTo(self.statsContainer);
}];
//
[self.statsContainer addSubview:self.followersLabel];
[self.followersLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.statsContainer.mas_centerX).offset(20);
make.centerY.equalTo(self.statsContainer);
}];
}
// MARK: - Public Methods
- (void)configureWithUserInfo:(NSDictionary *)userInfo {
//
self.nicknameLabel.text = userInfo[@"nickname"] ?: @"未设置昵称";
//
NSNumber *level = userInfo[@"level"];
self.levelLabel.text = [NSString stringWithFormat:@"Lv.%@", level ?: @"0"];
//
NSNumber *exp = userInfo[@"exp"];
NSNumber *nextLevelExp = userInfo[@"nextLevelExp"];
CGFloat progress = [exp floatValue] / [nextLevelExp floatValue];
[self.progressBar mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.top.bottom.equalTo(self.progressContainer);
make.width.equalTo(self.progressContainer).multipliedBy(MAX(0.1, MIN(1.0, progress)));
}];
self.expLabel.text = [NSString stringWithFormat:@"%@ / %@", exp, nextLevelExp];
//
NSNumber *followers = userInfo[@"followers"];
NSNumber *following = userInfo[@"following"];
self.followingLabel.text = [NSString stringWithFormat:@"关注\n%@", following ?: @"0"];
self.followersLabel.text = [NSString stringWithFormat:@"粉丝\n%@", followers ?: @"0"];
NSLog(@"[NewMineHeaderView] 用户信息已配置: %@", userInfo[@"nickname"]);
}
// MARK: - Lazy Loading
- (UIImageView *)avatarImageView {
if (!_avatarImageView) {
_avatarImageView = [[UIImageView alloc] init];
_avatarImageView.backgroundColor = [UIColor whiteColor];
_avatarImageView.layer.cornerRadius = 16; //
_avatarImageView.layer.masksToBounds = YES;
_avatarImageView.layer.borderWidth = 3;
_avatarImageView.layer.borderColor = [UIColor whiteColor].CGColor;
_avatarImageView.contentMode = UIViewContentModeScaleAspectFill;
}
return _avatarImageView;
}
- (UILabel *)nicknameLabel {
if (!_nicknameLabel) {
_nicknameLabel = [[UILabel alloc] init];
_nicknameLabel.font = [UIFont systemFontOfSize:20 weight:UIFontWeightBold];
_nicknameLabel.textColor = [UIColor whiteColor];
_nicknameLabel.textAlignment = NSTextAlignmentCenter;
}
return _nicknameLabel;
}
- (UILabel *)levelLabel {
if (!_levelLabel) {
_levelLabel = [[UILabel alloc] init];
_levelLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
_levelLabel.textColor = [UIColor colorWithWhite:1.0 alpha:0.9];
_levelLabel.textAlignment = NSTextAlignmentCenter;
_levelLabel.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.2];
_levelLabel.layer.cornerRadius = 12;
_levelLabel.layer.masksToBounds = YES;
//
_levelLabel.text = @" Lv.0 ";
}
return _levelLabel;
}
- (UIView *)progressContainer {
if (!_progressContainer) {
_progressContainer = [[UIView alloc] init];
_progressContainer.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.3];
_progressContainer.layer.cornerRadius = 4;
_progressContainer.layer.masksToBounds = YES;
}
return _progressContainer;
}
- (UIView *)progressBar {
if (!_progressBar) {
_progressBar = [[UIView alloc] init];
_progressBar.backgroundColor = [UIColor whiteColor];
_progressBar.layer.cornerRadius = 4;
_progressBar.layer.masksToBounds = YES;
}
return _progressBar;
}
- (UILabel *)expLabel {
if (!_expLabel) {
_expLabel = [[UILabel alloc] init];
_expLabel.font = [UIFont systemFontOfSize:12];
_expLabel.textColor = [UIColor colorWithWhite:1.0 alpha:0.8];
_expLabel.textAlignment = NSTextAlignmentCenter;
}
return _expLabel;
}
- (UIView *)statsContainer {
if (!_statsContainer) {
_statsContainer = [[UIView alloc] init];
_statsContainer.backgroundColor = [UIColor clearColor];
}
return _statsContainer;
}
- (UILabel *)followingLabel {
if (!_followingLabel) {
_followingLabel = [[UILabel alloc] init];
_followingLabel.font = [UIFont systemFontOfSize:14];
_followingLabel.textColor = [UIColor whiteColor];
_followingLabel.textAlignment = NSTextAlignmentCenter;
_followingLabel.numberOfLines = 2;
}
return _followingLabel;
}
- (UILabel *)followersLabel {
if (!_followersLabel) {
_followersLabel = [[UILabel alloc] init];
_followersLabel.font = [UIFont systemFontOfSize:14];
_followersLabel.textColor = [UIColor whiteColor];
_followersLabel.textAlignment = NSTextAlignmentCenter;
_followersLabel.numberOfLines = 2;
}
return _followersLabel;
}
@end

View File

@@ -0,0 +1,19 @@
//
// NewMomentViewController.h
// YuMi
//
// Created by AI on 2025-10-09.
// Copyright © 2025 YuMi. All rights reserved.
//
#import "BaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
/// 新的动态页面控制器
/// 采用卡片式布局,完全不同于原 XPMomentsViewController
@interface NewMomentViewController : BaseViewController
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,240 @@
//
// NewMomentViewController.m
// YuMi
//
// Created by AI on 2025-10-09.
// Copyright © 2025 YuMi. All rights reserved.
//
#import "NewMomentViewController.h"
#import "NewMomentCell.h"
#import <Masonry/Masonry.h>
@interface NewMomentViewController () <UITableViewDelegate, UITableViewDataSource>
// MARK: - UI Components
///
@property (nonatomic, strong) UITableView *tableView;
///
@property (nonatomic, strong) UIRefreshControl *refreshControl;
///
@property (nonatomic, strong) UIButton *publishButton;
// MARK: - Data
///
@property (nonatomic, strong) NSMutableArray *dataSource;
///
@property (nonatomic, assign) NSInteger currentPage;
///
@property (nonatomic, assign) BOOL isLoading;
@end
@implementation NewMomentViewController
// MARK: - Lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"动态";
self.view.backgroundColor = [UIColor colorWithRed:0.96 green:0.96 blue:0.96 alpha:1.0]; //
[self setupUI];
[self loadData];
NSLog(@"[NewMomentViewController] 页面加载完成");
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
//
// [self.navigationController setNavigationBarHidden:YES animated:animated];
}
// MARK: - Setup UI
- (void)setupUI {
// TableView
[self.view addSubview:self.tableView];
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
//
[self.view addSubview:self.publishButton];
[self.publishButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.view).offset(-20);
make.bottom.equalTo(self.view).offset(-100); // TabBar
make.size.mas_equalTo(CGSizeMake(56, 56));
}];
NSLog(@"[NewMomentViewController] UI 设置完成");
}
// MARK: - Data Loading
- (void)loadData {
if (self.isLoading) return;
self.isLoading = YES;
NSLog(@"[NewMomentViewController] 开始加载数据,页码: %ld", (long)self.currentPage);
// TODO: API
// 使
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//
for (int i = 0; i < 10; i++) {
NSDictionary *mockData = @{
@"id": @(self.currentPage * 10 + i),
@"content": [NSString stringWithFormat:@"这是第 %ld 页的第 %d 条动态", (long)self.currentPage, i],
@"images": @[],
@"likeCount": @(arc4random() % 100),
@"commentCount": @(arc4random() % 50),
};
[self.dataSource addObject:mockData];
}
self.currentPage++;
self.isLoading = NO;
[self.tableView reloadData];
[self.refreshControl endRefreshing];
NSLog(@"[NewMomentViewController] 数据加载完成,当前数据量: %lu", (unsigned long)self.dataSource.count);
});
}
- (void)onRefresh {
self.currentPage = 0;
[self.dataSource removeAllObjects];
[self loadData];
}
// MARK: - Actions
- (void)onPublishButtonTapped {
NSLog(@"[NewMomentViewController] 发布按钮点击");
// TODO:
[self showAlertWithMessage:@"发布功能开发中"];
}
- (void)showAlertWithMessage:(NSString *)message {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示"
message:message
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
}
// MARK: - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.dataSource.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NewMomentCell *cell = [tableView dequeueReusableCellWithIdentifier:@"NewMomentCell" forIndexPath:indexPath];
if (indexPath.row < self.dataSource.count) {
NSDictionary *data = self.dataSource[indexPath.row];
[cell configureWithData:data];
}
return cell;
}
// MARK: - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSLog(@"[NewMomentViewController] 点击动态: %ld", (long)indexPath.row);
// TODO:
[self showAlertWithMessage:[NSString stringWithFormat:@"点击了第 %ld 条动态", (long)indexPath.row]];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 200;
}
//
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
CGFloat screenHeight = scrollView.frame.size.height;
if (offsetY > contentHeight - screenHeight - 100 && !self.isLoading) {
[self loadData];
}
}
// MARK: - Lazy Loading
- (UITableView *)tableView {
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
_tableView.delegate = self;
_tableView.dataSource = self;
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_tableView.backgroundColor = self.view.backgroundColor;
_tableView.estimatedRowHeight = 200;
_tableView.rowHeight = UITableViewAutomaticDimension;
_tableView.showsVerticalScrollIndicator = NO;
_tableView.contentInset = UIEdgeInsetsMake(10, 0, 10, 0);
// Cell
[_tableView registerClass:[NewMomentCell class] forCellReuseIdentifier:@"NewMomentCell"];
//
_tableView.refreshControl = self.refreshControl;
}
return _tableView;
}
- (UIRefreshControl *)refreshControl {
if (!_refreshControl) {
_refreshControl = [[UIRefreshControl alloc] init];
[_refreshControl addTarget:self action:@selector(onRefresh) forControlEvents:UIControlEventValueChanged];
}
return _refreshControl;
}
- (UIButton *)publishButton {
if (!_publishButton) {
_publishButton = [UIButton buttonWithType:UIButtonTypeCustom];
_publishButton.backgroundColor = [UIColor colorWithRed:0.2 green:0.6 blue:0.86 alpha:1.0]; //
_publishButton.layer.cornerRadius = 28;
_publishButton.layer.shadowColor = [UIColor blackColor].CGColor;
_publishButton.layer.shadowOffset = CGSizeMake(0, 2);
_publishButton.layer.shadowOpacity = 0.3;
_publishButton.layer.shadowRadius = 4;
// 使
[_publishButton setTitle:@"+" forState:UIControlStateNormal];
_publishButton.titleLabel.font = [UIFont systemFontOfSize:32 weight:UIFontWeightLight];
[_publishButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_publishButton addTarget:self action:@selector(onPublishButtonTapped) forControlEvents:UIControlEventTouchUpInside];
}
return _publishButton;
}
- (NSMutableArray *)dataSource {
if (!_dataSource) {
_dataSource = [NSMutableArray array];
}
return _dataSource;
}
@end

View File

@@ -0,0 +1,23 @@
//
// NewMomentCell.h
// YuMi
//
// Created by AI on 2025-10-09.
// Copyright © 2025 YuMi. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/// 新的动态 Cell卡片式设计
/// 完全不同于原 XPMomentsCell 的列表式设计
@interface NewMomentCell : UITableViewCell
/// 配置 Cell 数据
/// @param data 动态数据字典
- (void)configureWithData:(NSDictionary *)data;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,276 @@
//
// NewMomentCell.m
// YuMi
//
// Created by AI on 2025-10-09.
// Copyright © 2025 YuMi. All rights reserved.
//
#import "NewMomentCell.h"
#import <Masonry/Masonry.h>
@interface NewMomentCell ()
// MARK: - UI Components
///
@property (nonatomic, strong) UIView *cardView;
///
@property (nonatomic, strong) UIImageView *avatarImageView;
///
@property (nonatomic, strong) UILabel *nameLabel;
///
@property (nonatomic, strong) UILabel *timeLabel;
///
@property (nonatomic, strong) UILabel *contentLabel;
///
@property (nonatomic, strong) UIView *imagesContainer;
///
@property (nonatomic, strong) UIView *actionBar;
///
@property (nonatomic, strong) UIButton *likeButton;
///
@property (nonatomic, strong) UIButton *commentButton;
///
@property (nonatomic, strong) UIButton *shareButton;
@end
@implementation NewMomentCell
// MARK: - Lifecycle
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.backgroundColor = [UIColor clearColor];
[self setupUI];
}
return self;
}
// MARK: - Setup UI
- (void)setupUI {
// +
[self.contentView addSubview:self.cardView];
[self.cardView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.contentView).offset(15);
make.right.equalTo(self.contentView).offset(-15);
make.top.equalTo(self.contentView).offset(8);
make.bottom.equalTo(self.contentView).offset(-8);
}];
//
[self.cardView addSubview:self.avatarImageView];
[self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.cardView).offset(15);
make.top.equalTo(self.cardView).offset(15);
make.size.mas_equalTo(CGSizeMake(40, 40));
}];
//
[self.cardView addSubview:self.nameLabel];
[self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.avatarImageView.mas_right).offset(10);
make.top.equalTo(self.avatarImageView);
make.right.equalTo(self.cardView).offset(-15);
}];
//
[self.cardView addSubview:self.timeLabel];
[self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.nameLabel);
make.bottom.equalTo(self.avatarImageView);
make.right.equalTo(self.cardView).offset(-15);
}];
//
[self.cardView addSubview:self.contentLabel];
[self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.cardView).offset(15);
make.right.equalTo(self.cardView).offset(-15);
make.top.equalTo(self.avatarImageView.mas_bottom).offset(12);
}];
//
[self.cardView addSubview:self.actionBar];
[self.actionBar mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.cardView);
make.top.equalTo(self.contentLabel.mas_bottom).offset(15);
make.height.mas_equalTo(50);
make.bottom.equalTo(self.cardView).offset(-8);
}];
//
[self.actionBar addSubview:self.likeButton];
[self.likeButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.actionBar).offset(15);
make.centerY.equalTo(self.actionBar);
make.width.mas_greaterThanOrEqualTo(60);
}];
//
[self.actionBar addSubview:self.commentButton];
[self.commentButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.actionBar);
make.centerY.equalTo(self.actionBar);
make.width.mas_greaterThanOrEqualTo(60);
}];
//
[self.actionBar addSubview:self.shareButton];
[self.shareButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.actionBar).offset(-15);
make.centerY.equalTo(self.actionBar);
make.width.mas_greaterThanOrEqualTo(60);
}];
}
// MARK: - Public Methods
- (void)configureWithData:(NSDictionary *)data {
//
self.nameLabel.text = data[@"userName"] ?: @"匿名用户";
//
self.timeLabel.text = @"2小时前"; // TODO:
//
self.contentLabel.text = data[@"content"] ?: @"";
//
NSNumber *likeCount = data[@"likeCount"];
[self.likeButton setTitle:[NSString stringWithFormat:@"👍 %@", likeCount ?: @"0"] forState:UIControlStateNormal];
//
NSNumber *commentCount = data[@"commentCount"];
[self.commentButton setTitle:[NSString stringWithFormat:@"💬 %@", commentCount ?: @"0"] forState:UIControlStateNormal];
//
[self.shareButton setTitle:@"🔗 分享" forState:UIControlStateNormal];
}
// MARK: - Actions
- (void)onLikeButtonTapped {
NSLog(@"[NewMomentCell] 点赞");
// TODO:
}
- (void)onCommentButtonTapped {
NSLog(@"[NewMomentCell] 评论");
// TODO:
}
- (void)onShareButtonTapped {
NSLog(@"[NewMomentCell] 分享");
// TODO:
}
// MARK: - Lazy Loading
- (UIView *)cardView {
if (!_cardView) {
_cardView = [[UIView alloc] init];
_cardView.backgroundColor = [UIColor whiteColor];
_cardView.layer.cornerRadius = 12; //
_cardView.layer.shadowColor = [UIColor blackColor].CGColor;
_cardView.layer.shadowOffset = CGSizeMake(0, 2);
_cardView.layer.shadowOpacity = 0.1;
_cardView.layer.shadowRadius = 8;
_cardView.layer.masksToBounds = NO;
}
return _cardView;
}
- (UIImageView *)avatarImageView {
if (!_avatarImageView) {
_avatarImageView = [[UIImageView alloc] init];
_avatarImageView.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0];
_avatarImageView.layer.cornerRadius = 8; //
_avatarImageView.layer.masksToBounds = YES;
_avatarImageView.contentMode = UIViewContentModeScaleAspectFill;
}
return _avatarImageView;
}
- (UILabel *)nameLabel {
if (!_nameLabel) {
_nameLabel = [[UILabel alloc] init];
_nameLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
_nameLabel.textColor = [UIColor colorWithWhite:0.2 alpha:1.0];
}
return _nameLabel;
}
- (UILabel *)timeLabel {
if (!_timeLabel) {
_timeLabel = [[UILabel alloc] init];
_timeLabel.font = [UIFont systemFontOfSize:12];
_timeLabel.textColor = [UIColor colorWithWhite:0.6 alpha:1.0];
}
return _timeLabel;
}
- (UILabel *)contentLabel {
if (!_contentLabel) {
_contentLabel = [[UILabel alloc] init];
_contentLabel.font = [UIFont systemFontOfSize:15];
_contentLabel.textColor = [UIColor colorWithWhite:0.3 alpha:1.0];
_contentLabel.numberOfLines = 0;
_contentLabel.lineBreakMode = NSLineBreakByWordWrapping;
}
return _contentLabel;
}
- (UIView *)actionBar {
if (!_actionBar) {
_actionBar = [[UIView alloc] init];
_actionBar.backgroundColor = [UIColor colorWithWhite:0.98 alpha:1.0];
}
return _actionBar;
}
- (UIButton *)likeButton {
if (!_likeButton) {
_likeButton = [self createActionButtonWithTitle:@"👍 0"];
[_likeButton addTarget:self action:@selector(onLikeButtonTapped) forControlEvents:UIControlEventTouchUpInside];
}
return _likeButton;
}
- (UIButton *)commentButton {
if (!_commentButton) {
_commentButton = [self createActionButtonWithTitle:@"💬 0"];
[_commentButton addTarget:self action:@selector(onCommentButtonTapped) forControlEvents:UIControlEventTouchUpInside];
}
return _commentButton;
}
- (UIButton *)shareButton {
if (!_shareButton) {
_shareButton = [self createActionButtonWithTitle:@"🔗 分享"];
[_shareButton addTarget:self action:@selector(onShareButtonTapped) forControlEvents:UIControlEventTouchUpInside];
}
return _shareButton;
}
- (UIButton *)createActionButtonWithTitle:(NSString *)title {
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button setTitle:title forState:UIControlStateNormal];
button.titleLabel.font = [UIFont systemFontOfSize:13];
[button setTitleColor:[UIColor colorWithWhite:0.5 alpha:1.0] forState:UIControlStateNormal];
return button;
}
@end

View File

@@ -150,20 +150,15 @@ class NewTabBarController: UITabBarController {
/// ViewControllers /// ViewControllers
private func setupLoggedInViewControllers() { private func setupLoggedInViewControllers() {
// TODO: NewMomentViewController NewMineViewController // ViewControllerOC
// let momentVC = NewMomentViewController() let momentVC = NewMomentViewController()
// let mineVC = NewMineViewController()
let momentVC = UIViewController()
momentVC.view.backgroundColor = .white
momentVC.tabBarItem = createTabBarItem( momentVC.tabBarItem = createTabBarItem(
title: "动态", title: "动态",
normalImage: "tab_moment_normal", normalImage: "tab_moment_normal",
selectedImage: "tab_moment_selected" selectedImage: "tab_moment_selected"
) )
let mineVC = UIViewController() let mineVC = NewMineViewController()
mineVC.view.backgroundColor = .white
mineVC.tabBarItem = createTabBarItem( mineVC.tabBarItem = createTabBarItem(
title: "我的", title: "我的",
normalImage: "tab_mine_normal", normalImage: "tab_mine_normal",
@@ -173,7 +168,7 @@ class NewTabBarController: UITabBarController {
viewControllers = [momentVC, mineVC] viewControllers = [momentVC, mineVC]
selectedIndex = 0 selectedIndex = 0
NSLog("[NewTabBarController] 登录后 ViewControllers 设置完成") NSLog("[NewTabBarController] 登录后 ViewControllers 设置完成 - Moment & Mine")
} }
} }

View File

@@ -43,4 +43,11 @@
#import "BaseViewController.h" #import "BaseViewController.h"
#import "BaseNavigationController.h" #import "BaseNavigationController.h"
// MARK: - New Modules (White Label)
#import "GlobalEventManager.h"
#import "NewMomentViewController.h"
#import "NewMomentCell.h"
#import "NewMineViewController.h"
#import "NewMineHeaderView.h"
#endif /* YuMi_Bridging_Header_h */ #endif /* YuMi_Bridging_Header_h */

View File

@@ -33,27 +33,55 @@
- 集成 GlobalEventManager - 集成 GlobalEventManager
- 支持登录前/后状态切换 - 支持登录前/后状态切换
## 下一步Phase 1 - Day 2-3 ## 已完成Phase 1 - Day 2-3
### 1. Xcode 项目配置 ### 1. Xcode 项目配置
- [ ]新文件添加到 Xcode 项目 -新文件自动添加到 Xcode 项目
- [ ] 配置 Build Settings - ✅ Bridging Header 已更新,包含新模块
- SWIFT_OBJC_BRIDGING_HEADER = YuMi/YuMi-Bridging-Header.h - ✅ Swift/OC 混编配置完成
- DEFINES_MODULE = YES
- SWIFT_VERSION = 5.0
- [ ] 编译验证
### 2. 创建 Moment 模块OC ### 2. 创建 Moment 模块OC
- [ ] 创建 NewMomentViewController.h/m - 创建 NewMomentViewController.h/m
- [ ] 创建 NewMomentCell.h/m - 列表式布局
- [ ] 设计新的 UI 布局(卡片式) - 下拉刷新
- [ ] 准备 30-40 张新图片资源 - 滚动加载更多
- 发布按钮(右下角悬浮)
- ✅ 创建 NewMomentCell.h/m
- 卡片式设计(白色卡片 + 阴影)
- 圆角矩形头像(不是圆形!)
- 底部操作栏(点赞/评论/分享)
- 使用模拟数据
- ✅ 设计新的 UI 布局(完全不同)
### 3. 创建 Mine 模块OC ### 3. 创建 Mine 模块OC
- [ ] 创建 NewMineViewController.h/m - 创建 NewMineViewController.h/m
- [ ] 创建 NewMineHeaderView.h/m - TableView 布局
- [ ] 设计新的 UI 布局(纵向卡片式) - 8 个菜单项
- [ ] 准备 50-60 张新图片资源 - 设置按钮
- ✅ 创建 NewMineHeaderView.h/m
- 渐变背景(蓝色系)
- 圆角矩形头像 + 白色边框
- 昵称、等级、经验进度条
- 关注/粉丝统计
- 纵向卡片式设计
- ✅ 设计新的 UI 布局(完全不同)
### 4. 集成到 TabBar
- ✅ NewTabBarController 集成新模块
- ✅ 支持登录前/后状态切换
## 下一步Phase 1 - Day 4-5
### 1. 编译测试
- [ ] 构建项目,修复编译错误
- [ ] 运行 App测试基本功能
- [ ] 检查 Console 日志
### 2. UI 资源准备
- [ ] 准备 TabBar icon4 张2 tab × 2 状态)
- [ ] 准备 Moment 模块图标30-40 张)
- [ ] 准备 Mine 模块图标50-60 张)
- [ ] 设计 AppIcon 和启动图
## 关键技术细节 ## 关键技术细节

183
white-label-test-guide.md Normal file
View File

@@ -0,0 +1,183 @@
# 白牌项目测试指南
## 如何运行新的 TabBar
### 方式 1在 AppDelegate 中替换根控制器(推荐)
`AppDelegate.m` 中找到设置根控制器的代码,临时替换为 NewTabBarController
```objc
#import "YuMi-Swift.h" // 引入 Swift 类
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// ... 其他初始化代码
// 临时使用新的 TabBar测试用
NewTabBarController *tabBar = [NewTabBarController create];
[tabBar refreshTabBarWithIsLogin:YES]; // 模拟已登录状态
self.window.rootViewController = tabBar;
[self.window makeKeyAndVisible];
return YES;
}
```
### 方式 2通过通知切换推荐用于测试
在任意位置发送通知切换到新 TabBar
```objc
#import "YuMi-Swift.h"
// 在某个按钮点击或测试代码中
NewTabBarController *tabBar = [NewTabBarController create];
[tabBar refreshTabBarWithIsLogin:YES];
UIWindow *window = [UIApplication sharedApplication].keyWindow;
window.rootViewController = tabBar;
```
## 测试清单
### Phase 1 - Day 1-3 测试(基础架构)
#### 1. APIConfig 域名测试
```swift
// 在 Debug 模式下运行
APIConfig.testEncryption()
// 检查 Console 输出:
// Release 域名: https://api.epartylive.com
// 当前环境域名: [测试域名]
// 备用域名: [测试域名]
```
#### 2. GlobalEventManager 测试
- [ ] 启动 App检查 Console 是否输出:
- `[GlobalEventManager] SDK 代理设置完成`
- `[GlobalEventManager] 通知监听已设置`
- `[GlobalEventManager] 房间最小化视图已添加`
#### 3. NewTabBarController 测试
- [ ] TabBar 正常显示2 个 Tab
- [ ] Tab 切换流畅
- [ ] Tab 图标正常显示(如果图片存在)
- [ ] 主色调应用正确(蓝色系)
#### 4. NewMomentViewController 测试
- [ ] 页面正常加载
- [ ] 列表正常显示(模拟数据)
- [ ] 下拉刷新功能正常
- [ ] 滚动到底部自动加载更多
- [ ] 发布按钮显示在右下角
- [ ] 点击 Cell 显示提示
- [ ] 点击发布按钮显示提示
**UI 检查**
- [ ] 卡片式布局(白色卡片 + 阴影)
- [ ] 圆角矩形头像(不是圆形!)
- [ ] 底部操作栏(点赞/评论/分享)
- [ ] 浅灰色背景
- [ ] 15px 左右边距
#### 5. NewMineViewController 测试
- [ ] 页面正常加载
- [ ] 顶部个人信息卡片显示
- [ ] 渐变背景(蓝色渐变)
- [ ] 头像(圆角矩形 + 白色边框)
- [ ] 昵称、等级显示
- [ ] 经验进度条正常
- [ ] 关注/粉丝数显示
- [ ] 菜单列表正常显示8 个菜单项)
- [ ] 点击菜单项显示提示
- [ ] 右上角设置按钮正常
**UI 检查**
- [ ] 头部高度约 280px
- [ ] 渐变背景(蓝色系)
- [ ] 所有文字使用白色
- [ ] 菜单项高度 56px
- [ ] 菜单项带右箭头
## 预期效果
### 代码层面
- Swift 文件5 个APIConfig, NewTabBarController 等)
- OC 新文件6 个GlobalEventManager, Moment, Mine 模块)
- 总新增代码:约 1500 行
- 代码相似度:预计 <20%因为是全新代码
### UI 层面
- TabBar 只有 2 Tabvs 原来的 5
- 完全不同的颜色方案蓝色系
- 卡片式设计vs 原来的列表式
- 圆角矩形头像vs 原来的圆形
- 渐变背景vs 原来的纯色
### 网络层面
- DEBUG使用原测试域名
- RELEASE使用加密的新域名 `https://api.epartylive.com`
- 代码中无明文域名
## 常见问题
### Q1: 编译失败,提示找不到 Swift 类
**A**: 检查以下配置
1. Build Settings Defines Module = YES
2. Build Settings Swift Objc Bridging Header = YuMi/YuMi-Bridging-Header.h
3. 清理项目Cmd + Shift + K然后重新编译
### Q2: 运行时 Crash提示 "selector not recognized"
**A**: 检查
1. Swift 类是否标记了 `@objc`
2. 方法是否标记了 `@objc`
3. Bridging Header 是否包含了所有需要的 OC 头文件
### Q3: TabBar 显示但是是空白页面
**A**: 检查
1. NewMomentViewController NewMineViewController 是否正确初始化
2. Console 是否有错误日志
3. 尝试直接 push 到这些 ViewController 测试
### Q4: 图片不显示
**A**:
1. 图片资源还未添加正常现象
2. 暂时使用 emoji 或文字代替
3. 后续会添加新的图片资源
## 下一步
Phase 1 - Day 2-3 完成后继续
### Day 4-5: 完善 UI 细节
- [ ] 添加真实的图片资源100-150
- [ ] 完善动画效果
- [ ] 优化交互体验
### Day 6-10: 网络层集成
- [ ] 创建 HttpRequestHelper Category
- [ ] 集成真实 API
- [ ] 测试网络请求
### Day 11-15: 全面测试
- [ ] 功能测试
- [ ] 性能测试
- [ ] 相似度检查
- [ ] 准备提审
---
**更新时间**: 2025-10-09
**当前进度**: Phase 1 - Day 2-3 完成
**文件数量**: 11 个新文件
**代码量**: ~1500