From a61b5fa2fd19e5c0068f9cc0a1a9020342aeccd1 Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Thu, 13 Nov 2025 16:23:46 +0800 Subject: [PATCH] 1 --- CustomKeyboard/Network/KBNetworkManager.m | 7 +- keyBoard/AppDelegate.m | 7 - keyBoard/Class/Network/KBNetworkManager.m | 8 +- keyBoard/Class/Shop/V/KBShopHeadView.h | 10 + keyBoard/Class/Shop/V/KBShopHeadView.m | 248 +++++++++++++++++++++- keyBoard/Class/Shop/VC/KBShopVC.m | 14 +- 6 files changed, 276 insertions(+), 18 deletions(-) diff --git a/CustomKeyboard/Network/KBNetworkManager.m b/CustomKeyboard/Network/KBNetworkManager.m index 73b0e05..f34b957 100644 --- a/CustomKeyboard/Network/KBNetworkManager.m +++ b/CustomKeyboard/Network/KBNetworkManager.m @@ -96,7 +96,12 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; return path; } if (self.baseURL) { - return [NSURL URLWithString:path relativeToURL:self.baseURL].absoluteURL.absoluteString; + // 统一为目录型 base(以 / 结尾),并剥掉 path 的前导 /,避免覆盖 base 路径 + NSString *base = self.baseURL.absoluteString ?: @""; + if (![base hasSuffix:@"/"]) { base = [base stringByAppendingString:@"/"]; } + NSURL *dirBase = [NSURL URLWithString:base]; + NSString *relative = ([path hasPrefix:@"/"]) ? [path substringFromIndex:1] : path; + return [NSURL URLWithString:relative relativeToURL:dirBase].absoluteURL.absoluteString; } return path; // 当无 baseURL 且 path 不是完整 URL 时,让 AFN 处理(可能失败) } diff --git a/keyBoard/AppDelegate.m b/keyBoard/AppDelegate.m index 9693498..6d6ca87 100644 --- a/keyBoard/AppDelegate.m +++ b/keyBoard/AppDelegate.m @@ -176,13 +176,6 @@ static NSString * const kKBKeyboardExtensionBundleId = @"com.loveKey.nyx.CustomK NSDictionary *params = @{ @"code": tokenString ?: @"", }; -// [[KBNetworkManager shared] POST:@"/user/appleLogin" parameters:params headers:nil progress:^(NSProgress * _Nonnull uploadProgress) { -// -// } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { -// -// } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { -// -// }]; [[KBNetworkManager shared] POST:@"/user/appleLogin" jsonBody:params headers:nil completion:^(id _Nullable jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"====="); }]; diff --git a/keyBoard/Class/Network/KBNetworkManager.m b/keyBoard/Class/Network/KBNetworkManager.m index adea2dc..71d5124 100644 --- a/keyBoard/Class/Network/KBNetworkManager.m +++ b/keyBoard/Class/Network/KBNetworkManager.m @@ -122,7 +122,13 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; return path; } if (self.baseURL) { - return [NSURL URLWithString:path relativeToURL:self.baseURL].absoluteURL.absoluteString; + // 1) 统一为目录型 base(确保以 / 结尾),否则相对路径会把最后一段当文件替换 + NSString *base = self.baseURL.absoluteString ?: @""; + if (![base hasSuffix:@"/"]) { base = [base stringByAppendingString:@"/"]; } + NSURL *dirBase = [NSURL URLWithString:base]; + // 2) 防呆:调用方若传了以“/”开头的 path,会导致相对路径从根覆盖,丢失 /api + NSString *relative = ([path hasPrefix:@"/"]) ? [path substringFromIndex:1] : path; + return [NSURL URLWithString:relative relativeToURL:dirBase].absoluteURL.absoluteString; } return path; // 当无 baseURL 且 path 不是完整 URL 时,让 AFN 处理(可能失败) } diff --git a/keyBoard/Class/Shop/V/KBShopHeadView.h b/keyBoard/Class/Shop/V/KBShopHeadView.h index d6d1276..65ea5c1 100644 --- a/keyBoard/Class/Shop/V/KBShopHeadView.h +++ b/keyBoard/Class/Shop/V/KBShopHeadView.h @@ -9,8 +9,18 @@ NS_ASSUME_NONNULL_BEGIN +/// 商城顶部头图 +/// 还原设计稿(见需求截图):左侧「Points\nMall」标题 + 右侧大金币装饰 +/// 下方卡片显示积分与「Recharge」按钮。 +/// Masonry 约束 + 懒加载,中文注释。 @interface KBShopHeadView : UIView +/// 点击充值回调 +@property (nonatomic, copy) void (^onRechargeTapped)(void); + +/// 更新积分展示 +- (void)updatePoints:(NSString *)points; + @end NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Shop/V/KBShopHeadView.m b/keyBoard/Class/Shop/V/KBShopHeadView.m index d24a9fa..85ef0ff 100644 --- a/keyBoard/Class/Shop/V/KBShopHeadView.m +++ b/keyBoard/Class/Shop/V/KBShopHeadView.m @@ -6,16 +6,260 @@ // #import "KBShopHeadView.h" -@interface KBShopHeadView() + +@interface KBShopHeadView () + +// 容器视图(留出与屏幕的左右边距) +@property (nonatomic, strong) UIView *containerView; + +// 顶部左侧标题与点缀 +@property (nonatomic, strong) UILabel *titleLabel; // "Points\nMall" +@property (nonatomic, strong) UIImageView *yellowStarView; // 左侧黄色装饰星:shop_yellowxx_icon +@property (nonatomic, strong) UIImageView *greenStarView; // 右上角绿色装饰星:shop_greenxx_icon + +// 右侧大金币 +@property (nonatomic, strong) UIImageView *bigCoinView; // shop_headbigBg_icon + +// 底部信息卡片 +@property (nonatomic, strong) UIView *infoCard; // 渐变浅绿背景 +@property (nonatomic, strong) UILabel *myPointsLabel; // "My Points" +@property (nonatomic, strong) UIImageView *smallCoinView; // 左侧小金币:shop_jb_icon +@property (nonatomic, strong) UILabel *amountLabel; // 数字 88.00 +@property (nonatomic, strong) UIButton *rechargeButton; // 充值按钮:recharge_btn_bg @end + @implementation KBShopHeadView -- (instancetype)initWithFrame:(CGRect)frame{ +- (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.backgroundColor = [UIColor clearColor]; + + // 组装视图层级 + [self addSubview:self.containerView]; + [self.containerView addSubview:self.titleLabel]; + [self.containerView addSubview:self.yellowStarView]; + [self.containerView addSubview:self.greenStarView]; + [self.containerView addSubview:self.bigCoinView]; + [self.containerView addSubview:self.infoCard]; + [self.infoCard addSubview:self.myPointsLabel]; + [self.infoCard addSubview:self.smallCoinView]; + [self.infoCard addSubview:self.amountLabel]; + [self.infoCard addSubview:self.rechargeButton]; + + // 布局 + [self.containerView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self).offset(16); + make.right.equalTo(self).offset(-16); + make.top.equalTo(self); + make.bottom.equalTo(self); + }]; + + // 左侧标题 + [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.left.equalTo(self.containerView).offset(0); + make.bottom.equalTo(self.containerView).offset(0); + }]; + + // 左侧黄色装饰星 + [self.yellowStarView mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.equalTo(self.titleLabel.mas_centerY).offset(6); + make.left.equalTo(self.containerView).offset(6); + make.width.height.mas_equalTo(16); + }]; + + // 右上角绿色装饰星 + [self.greenStarView mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.equalTo(self.containerView).offset(10); + make.right.equalTo(self.containerView).offset(-6); + make.width.height.mas_equalTo(14); + }]; + + // 右侧大金币(装饰) + [self.bigCoinView mas_makeConstraints:^(MASConstraintMaker *make) { + make.bottom.equalTo(self.containerView); + make.left.right.equalTo(self.containerView); + make.height.mas_equalTo(KBFit(238)); + }]; + + // 底部信息卡片 + [self.infoCard mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.equalTo(self.containerView); + make.bottom.equalTo(self.containerView); + make.height.mas_equalTo(KBFit(140)); + }]; + + [self.myPointsLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.infoCard).offset(16); + make.top.equalTo(self.infoCard).offset(16); + }]; + + [self.smallCoinView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.infoCard).offset(16); + make.top.equalTo(self.myPointsLabel.mas_bottom).offset(12); + make.width.height.mas_equalTo(36); + }]; + + [self.amountLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.smallCoinView.mas_right).offset(10); + make.centerY.equalTo(self.smallCoinView); + }]; + + [self.rechargeButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.equalTo(self.amountLabel); + make.right.equalTo(self.infoCard).offset(-16); + make.width.mas_equalTo(120); + make.height.mas_equalTo(40); + }]; } return self; } +- (void)layoutSubviews { + [super layoutSubviews]; + + // 圆角;头图容器做裁剪,避免内容溢出 +// self.containerView.layer.cornerRadius = 16.0; +// self.containerView.layer.masksToBounds = YES; +// +// self.infoCard.layer.cornerRadius = 16.0; +// self.infoCard.layer.masksToBounds = YES; +// +// // 卡片底色做一个很浅的左→右渐变,贴近设计微绿色 +// // 先移除已有渐变,避免多次叠加 +// NSMutableArray *remove = [NSMutableArray array]; +// for (CALayer *l in self.infoCard.layer.sublayers) { +// if ([l isKindOfClass:[CAGradientLayer class]]) { [remove addObject:l]; } +// } +// for (CALayer *l in remove) { [l removeFromSuperlayer]; } +// +// CAGradientLayer *g = [CAGradientLayer layer]; +// g.colors = @[(__bridge id)[UIColor colorWithHex:0xF4FFFE].CGColor, +// (__bridge id)[UIColor colorWithHex:0xE8FFF6].CGColor]; +// g.startPoint = CGPointMake(0, 0.5); +// g.endPoint = CGPointMake(1, 0.5); +// g.frame = self.infoCard.bounds; +// [self.infoCard.layer insertSublayer:g atIndex:0]; +} + +#pragma mark - Public +- (void)updatePoints:(NSString *)points { + if (points.length == 0) { points = @"0"; } + self.amountLabel.text = points; +} + +#pragma mark - Actions +- (void)onRechargeTappedAction { + if (self.onRechargeTapped) { self.onRechargeTapped(); } +} + +#pragma mark - Lazy UI +- (UIView *)containerView { + if (!_containerView) { + _containerView = [UIView new]; +// _containerView.backgroundColor = [UIColor whiteColor]; + } + return _containerView; +} + +- (UILabel *)titleLabel { + if (!_titleLabel) { + _titleLabel = [UILabel new]; + _titleLabel.numberOfLines = 0; + _titleLabel.text = @"Points\nMall"; // 两行展示 + _titleLabel.font = [UIFont systemFontOfSize:30 weight:UIFontWeightBold]; + _titleLabel.textColor = [UIColor colorWithHex:0x1B1F1A]; + } + return _titleLabel; +} + +- (UIImageView *)yellowStarView { + if (!_yellowStarView) { + _yellowStarView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"shop_yellowxx_icon"]]; + _yellowStarView.contentMode = UIViewContentModeScaleAspectFit; + } + return _yellowStarView; +} + +- (UIImageView *)greenStarView { + if (!_greenStarView) { + _greenStarView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"shop_greenxx_icon"]]; + _greenStarView.contentMode = UIViewContentModeScaleAspectFit; + } + return _greenStarView; +} + +- (UIImageView *)bigCoinView { + if (!_bigCoinView) { + _bigCoinView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"shop_headbigBg_icon"]]; + _bigCoinView.contentMode = UIViewContentModeScaleAspectFit; + } + return _bigCoinView; +} + +- (UIView *)infoCard { + if (!_infoCard) { + _infoCard = [UIView new]; + } + return _infoCard; +} + +- (UILabel *)myPointsLabel { + if (!_myPointsLabel) { + _myPointsLabel = [UILabel new]; + _myPointsLabel.text = @"My Points"; + _myPointsLabel.textColor = [UIColor colorWithHex:0x1B1F1A]; + _myPointsLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightSemibold]; + } + return _myPointsLabel; +} + +- (UIImageView *)smallCoinView { + if (!_smallCoinView) { + _smallCoinView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"shop_jb_icon"]]; + _smallCoinView.contentMode = UIViewContentModeScaleAspectFit; + } + return _smallCoinView; +} + +- (UILabel *)amountLabel { + if (!_amountLabel) { + _amountLabel = [UILabel new]; + _amountLabel.text = @"88.00"; // 默认文案 + _amountLabel.textColor = [UIColor colorWithHex:KBColorValue]; + _amountLabel.font = [UIFont systemFontOfSize:38 weight:UIFontWeightBold]; + _amountLabel.adjustsFontSizeToFitWidth = YES; + _amountLabel.minimumScaleFactor = 0.7; + } + return _amountLabel; +} + +- (UIButton *)rechargeButton { + if (!_rechargeButton) { + _rechargeButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [_rechargeButton setTitle:@"Recharge" forState:UIControlStateNormal]; + [_rechargeButton setTitleColor:[UIColor colorWithHex:0x1B1F1A] forState:UIControlStateNormal]; + _rechargeButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold]; + + // 切图背景图(可拉伸) + UIImage *bg = [UIImage imageNamed:@"recharge_btn_bg"]; + if (bg) { + CGFloat cap = 20; // 圆角拉伸区域 + bg = [bg resizableImageWithCapInsets:UIEdgeInsetsMake(cap, cap, cap, cap) resizingMode:UIImageResizingModeStretch]; + [_rechargeButton setBackgroundImage:bg forState:UIControlStateNormal]; + } else { + // 兜底:用绿色渐变生成 + UIImage *fallback = [UIImage kb_gradientImageWithColors:@[[UIColor colorWithHex:0xA6F6E9], [UIColor colorWithHex:0xD6FFF4]] + size:CGSizeMake(120, 40) + direction:KBGradientDirectionLeftToRight]; + [_rechargeButton setBackgroundImage:fallback forState:UIControlStateNormal]; + } + + _rechargeButton.layer.cornerRadius = 20; + _rechargeButton.layer.masksToBounds = YES; + [_rechargeButton addTarget:self action:@selector(onRechargeTappedAction) forControlEvents:UIControlEventTouchUpInside]; + } + return _rechargeButton; +} + @end diff --git a/keyBoard/Class/Shop/VC/KBShopVC.m b/keyBoard/Class/Shop/VC/KBShopVC.m index 8ad5684..5a722fc 100644 --- a/keyBoard/Class/Shop/VC/KBShopVC.m +++ b/keyBoard/Class/Shop/VC/KBShopVC.m @@ -143,7 +143,7 @@ static const CGFloat JXheightForHeaderInSection = 50; self.naviBGView = [[UIView alloc] init]; self.naviBGView.alpha = 0; - self.naviBGView.backgroundColor = [UIColor whiteColor]; + self.naviBGView.backgroundColor = [UIColor clearColor]; self.naviBGView.frame = CGRectMake(0, 0, KB_SCREEN_WIDTH, KB_NAV_TOTAL_HEIGHT); [self.view addSubview:self.naviBGView]; UILabel *naviTitleLabel = [[UILabel alloc] init]; @@ -219,15 +219,15 @@ static const CGFloat JXheightForHeaderInSection = 50; - (void)pagerView:(JXPagerView *)pagerView mainTableViewDidScroll:(UIScrollView *)scrollView { - // 正确的吸顶阈值:头图高度 - 固定区偏移(即导航高度) + // 计算吸顶阈值:头图高度 - 固定区偏移(即导航高度) CGFloat headerH = (CGFloat)[self tableHeaderViewHeightInPagerView:self.pagerView]; CGFloat thresholdDistance = MAX(0.0, headerH - self.pagerView.pinSectionHeaderVerticalOffset); - CGFloat percent = (thresholdDistance > 0 ? scrollView.contentOffset.y/thresholdDistance : 1); - percent = MAX(0, MIN(1, percent)); - self.naviBGView.alpha = percent; - // 分类条背景:未吸顶时透明,吸顶后白色 - // 触发吸顶的大致阈值 ≈ 头图高度 - 顶部偏移(此处用头图高度近似即可) + + // 不要渐变:小于阈值 alpha=0,达到阈值(吸顶)后 alpha=1 BOOL shouldWhite = (thresholdDistance > 0.0 && scrollView.contentOffset.y >= (thresholdDistance - 0.5)); + self.naviBGView.alpha = shouldWhite ? 1.0 : 0.0; + + // 分类条背景:未吸顶时透明,吸顶后白色 if (shouldWhite != self.categoryIsWhite) { self.categoryIsWhite = shouldWhite; UIColor *bg = shouldWhite ? [UIColor whiteColor] : [UIColor clearColor];