// // ScanListCell.m // HMLG Scan Order // // Created by Rui Zhang on 3/30/22. // Copyright © 2022 United Software Applications, Inc. All rights reserved. // #import "ScanListCell.h" #import "RASingleton.h" #import "OLDataProvider.h" #import "ActiveViewController.h" #import "RAConvertor.h" #import "RAUtils.h" #import "AppDelegate.h" #import "RADataProvider.h" // 表格行高(header 行 + data 行各一行) static const CGFloat kTableRowHeight = 24.0; // 单元格左右内边距 static const CGFloat kCellHPadding = 20; // 列最小宽度 static const CGFloat kMinColumnWidth = 125; @implementation ScanListCell - (void)awakeFromNib { [super awakeFromNib]; // priceScrollView self.priceScrollView.showsVerticalScrollIndicator = NO; self.priceScrollView.showsHorizontalScrollIndicator = YES; self.priceScrollView.alwaysBounceVertical = NO; self.priceScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; // 关闭默认选中高亮,避免与购物车高亮冲突 self.selectionStyle = UITableViewCellSelectionStyleNone; // qtyField 样式 self.qtyField.text = @"1"; // iPad 上 NumberPad 会浮动,改用 numbersAndPunctuation 让键盘固定在底部 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { self.qtyField.keyboardType = UIKeyboardTypeNumbersAndPunctuation; // cell 内部过滤非数字,不依赖外部 delegate [self.qtyField addTarget:self action:@selector(qtyFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; } else { self.qtyField.keyboardType = UIKeyboardTypeNumberPad; } self.qtyField.textAlignment = NSTextAlignmentCenter; self.qtyField.borderStyle = UITextBorderStyleRoundedRect; self.qtyField.font = [UIFont systemFontOfSize:15.0]; self.qtyField.layer.cornerRadius = 4.0; self.qtyField.layer.borderWidth = 1.0; self.qtyField.layer.borderColor = [UIColor systemGrayColor].CGColor; self.qtyField.layer.masksToBounds = YES; } - (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; } // MARK: - Add to Cart - (IBAction)onAddToCart:(id)sender { // ── 读取用户输入数量,默认 1,不允许 <= 0 ────────────────────────────── int userQty = [self.qtyField.text intValue]; if (userQty <= 0) userQty = 1; if (RASingleton.sharedInstance.scan_cart == nil) { NSMutableDictionary *cartTemplate = [OLDataProvider loadScanTemplate:@"scan_cart.json"]; RASingleton.sharedInstance.scan_cart = cartTemplate; } if (RASingleton.sharedInstance.scan_cart[@"price_type"] == nil) { RASingleton.sharedInstance.scan_cart[@"price_type"] = @1; RASingleton.sharedInstance.price_type = 1; } NSMutableDictionary *section = [RASingleton.sharedInstance.scan_cart[@"section_0"] mutableCopy]; int count = [section[@"count"] intValue]; NSMutableDictionary *jitem = [self.modelJson mutableCopy]; AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; NSDictionary *price_group = [RADataProvider get_price_group:appDelegate.price_group]; BOOL newitem = YES; for (int i = 0; i < count; i++) { NSMutableDictionary *litem = [section[[NSString stringWithFormat:@"item_%i", i]] mutableCopy]; if ([litem[@"product_id"] isEqualToString:jitem[@"product_id"]]) { newitem = NO; // 用 userQty 累加 litem[@"count"] = @([litem[@"count"] intValue] + userQty); litem[@"cuft"] = @([litem[@"count"] intValue] * [litem[@"unit_cuft"] doubleValue]); double discount = [litem[@"discount"] doubleValue]; int qty = [litem[@"count"] intValue]; double unit_price = [litem[@"unit_price"] doubleValue]; litem[@"subtotal_price"] = [NSString stringWithFormat:@"%f", unit_price * qty * (1 - discount / 100.0)]; section[[NSString stringWithFormat:@"item_%i", i]] = litem; break; } } if (newitem) { jitem[@"count"] = @(userQty); // 使用用户输入数量 jitem[@"check"] = @(YES); jitem[@"cart_item_id"] = [NSUUID UUID].UUIDString; NSString *unit_price_key; if (RASingleton.sharedInstance.price_type == 0) unit_price_key = price_group[@"price_0"][@"name"]; else if (_modelJson[@"special_price"]) unit_price_key = price_group[@"price_2"][@"name"]; else if (_modelJson[@"net_price"]) unit_price_key = price_group[@"price_3"][@"name"]; else unit_price_key = price_group[@"price_1"][@"name"]; NSString *unit_price = _modelJson[unit_price_key]; if ([unit_price isEqualToString:@"N/A"]) unit_price = @"0"; else unit_price = [NSString stringWithFormat:@"%.2f", [unit_price doubleValue]]; jitem[@"unit_price"] = unit_price; jitem[@"erp_unit_price"] = unit_price; // surcharge:key = 当前价格列名 + "_surcharge" NSString *surcharge_key = [unit_price_key stringByAppendingString:@"_surcharge"]; NSString *unit_surcharge = _modelJson[surcharge_key]; // if (unit_surcharge && unit_surcharge.length > 0 && ![unit_surcharge isEqualToString:@"N/A"]) if (unit_surcharge ) jitem[@"unit_surcharge"] = [NSString stringWithFormat:@"%.2f", [unit_surcharge doubleValue]]; else jitem[@"unit_surcharge"] = @"0"; double discount = [jitem[@"discount"] doubleValue]; double dunit_price = [jitem[@"unit_price"] doubleValue]; jitem[@"subtotal_price"] = [NSString stringWithFormat:@"%f", dunit_price * userQty * (1 - discount / 100.0)]; section[[NSString stringWithFormat:@"item_%i", count]] = jitem; section[@"count"] = @(count + 1); } RASingleton.sharedInstance.scan_cart[@"section_0"] = section; [ActiveViewController Notify:@"CartViewController" Message:RA_NOTIFICATION_RELOAD_DATA]; [OLDataProvider saveScanCart:RASingleton.sharedInstance.scan_cart]; // ── 重置数量输入框 ──────────────────────────────────────────────────────── self.qtyField.text = @"1"; // ── 刷新当前列表高亮(Search 和 History VC 都会收到)──────────────────── [[NSNotificationCenter defaultCenter] postNotificationName:@"ScanListShouldRefreshHighlight" object:nil]; [RAUtils message_box:[NSString stringWithFormat:@"%@ added to cart", jitem[@"model"]] message:@"Successfully" completion:nil]; } // MARK: - setModelJson - (void)setModelJson:(NSMutableDictionary *)modelJson { _modelJson = modelJson; AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; NSDictionary *price_group = [RADataProvider get_price_group:appDelegate.price_group]; #ifdef DEBUG NSLog(@"%@", [RAConvertor dict2string:modelJson]); #endif NSString *port = _modelJson[@"port"]; if (port.length == 0) port = @"N/A"; NSString *origin = _modelJson[@"origin"]; if (origin.length == 0) origin = @"N/A"; NSString *dimension = _modelJson[@"dimension"]; if (dimension.length == 0) dimension = @"N/A"; NSString *available = _modelJson[@"available"]; if (available.length == 0) available = @"N/A"; NSString *unit_cuft = _modelJson[@"unit_cuft"]; if (unit_cuft.length == 0) { unit_cuft = @"N/A"; } else { unit_cuft = [NSString stringWithFormat:@"%.2f", [_modelJson[@"unit_cuft"] doubleValue]]; } _labelModel.text = _modelJson[@"model"]; _labelDescription.text = _modelJson[@"description"]; _labelDimension.text = dimension; // ── 重置数量输入框(cell 复用时还原为 1)──────────────────────────────── self.qtyField.text = @"1"; // ── 价格 flag 处理 ───────────────────────────────────────────────────── NSString *price1 = _modelJson[price_group[@"price_1"][@"name"]]; NSString *price2 = _modelJson[price_group[@"price_2"][@"name"]]; NSString *price3 = _modelJson[price_group[@"price_3"][@"name"]]; if (![price1 isEqualToString:@"N/A"] && price1) price1 = [NSString stringWithFormat:@"%.2f", [price1 doubleValue]]; if (![price2 isEqualToString:@"N/A"] && price2) price2 = [NSString stringWithFormat:@"%.2f", [price2 doubleValue]]; if (![price3 isEqualToString:@"N/A"] && price3) price3 = [NSString stringWithFormat:@"%.2f", [price3 doubleValue]]; if (price2 && ![price2 isEqualToString:@"N/A"]) _modelJson[@"special_price"] = @YES; if ((price1 && ![price1 isEqualToString:@"N/A"] && [price1 isEqualToString:price3]) || (price2 && ![price2 isEqualToString:@"N/A"] && [price2 isEqualToString:price3])) _modelJson[@"net_price"] = @NO; // ── 构建动态表格列 ────────────────────────────────────────────────────── NSMutableArray *columns = [NSMutableArray array]; [columns addObject:@{ @"header": @"CuFT", @"value": unit_cuft }]; [columns addObject:@{ @"header": @"Origin", @"value": origin }]; [columns addObject:@{ @"header": @"Port", @"value": port }]; [columns addObject:@{ @"header": @"Available", @"value": available }]; // 根据 price_count 遍历当前价格组的所有价格列 int priceCount = [price_group[@"price_count"] intValue]; for (int i = 0; i < priceCount; i++) { NSString *key = [NSString stringWithFormat:@"price_%d", i]; NSDictionary *pg = price_group[key]; if (!pg || !pg[@"display"]) continue; NSString *displayName = pg[@"display"]; NSString *rawValue = _modelJson[pg[@"name"]]; NSString *displayValue = @""; if (rawValue && ![rawValue isEqualToString:@"N/A"]) { double dval = [rawValue doubleValue]; if (dval > 0) displayValue = [RAConvertor currencyNumber:(float)dval]; } // price_2 固定为 Show Special,仅在 special_price 时显示数值 if (i == 2 && ![_modelJson[@"special_price"] boolValue]) displayValue = @""; [columns addObject:@{ @"header": displayName, @"value": displayValue }]; } [self buildPriceTableWithColumns:columns]; // ── 购物车高亮 ───────────────────────────────────────────────────────────── [self updateCartHighlight]; // ── 更新 modelJson 供购物车使用 ──────────────────────────────────────── NSString *cell_price; if (RASingleton.sharedInstance.price_type == 1) cell_price = _modelJson[price_group[@"price_3"][@"name"]]; else cell_price = _modelJson[price_group[@"price_0"][@"name"]]; NSString *mpack = _modelJson[@"stockUom"]; if ([mpack isEqualToString:@"N/A"]) mpack = @"1"; _modelJson[@"count"] = _modelJson[@"stockUom"]; _modelJson[@"cuft"] = @([mpack intValue] * [unit_cuft doubleValue]); _modelJson[@"subtotal_price"] = @([mpack intValue] * [cell_price doubleValue]); } // MARK: - 购物车高亮 /** 检查当前 modelJson 对应的商品是否已在购物车中。 在购物车中:浅绿背景;不在:恢复默认背景。 在 setModelJson: 末尾调用,也可在外部主动调用(如加入购物车后刷新列表时)。 */ - (void)updateCartHighlight { NSString *productId = _modelJson[@"product_id"]; BOOL inCart = NO; NSDictionary *section = RASingleton.sharedInstance.scan_cart[@"section_0"]; if (section && productId.length > 0) { int count = [section[@"count"] intValue]; for (int i = 0; i < count; i++) { NSDictionary *item = section[[NSString stringWithFormat:@"item_%i", i]]; if ([item[@"product_id"] isEqualToString:productId]) { inCart = YES; break; } } } UIColor *highlightColor = inCart ? [[UIColor systemGreenColor] colorWithAlphaComponent:0.12] : [UIColor systemBackgroundColor]; self.contentView.backgroundColor = highlightColor; self.backgroundColor = highlightColor; } // MARK: - 动态表格构建 - (void)buildPriceTableWithColumns:(NSArray *)columns { for (UIView *v in self.priceScrollView.subviews) [v removeFromSuperview]; if (columns.count == 0) { self.priceScrollView.contentSize = CGSizeZero; return; } UIFont *font = [UIFont systemFontOfSize:15.0]; UIColor *borderColor = UIColor.labelColor; CGFloat totalWidth = 0; NSMutableArray *colWidths = [NSMutableArray arrayWithCapacity:columns.count]; for (NSDictionary *col in columns) { NSString *header = col[@"header"] ?: @""; NSString *value = col[@"value"] ?: @""; NSDictionary *attrs = @{ NSFontAttributeName: font }; CGFloat hw = [header sizeWithAttributes:attrs].width + kCellHPadding * 2; CGFloat vw = [value sizeWithAttributes:attrs].width + kCellHPadding * 2; CGFloat w = MAX(MAX(hw, vw), kMinColumnWidth); [colWidths addObject:@(w)]; } BOOL isLast = NO; CGFloat x = 0; for (NSUInteger i = 0; i < columns.count; i++) { CGFloat w = [colWidths[i] floatValue]; NSDictionary *col = columns[i]; isLast = (i == columns.count - 1); // header 行:画上、左(不画下,避免与 data 行上边框叠加) UILabel *hLabel = [self makeCellLabel:col[@"header"] font:font borderColor:borderColor frame:CGRectMake(x, 0, w, kTableRowHeight) drawTop:YES drawBottom:NO isLast:isLast]; hLabel.backgroundColor = [UIColor secondarySystemBackgroundColor]; [self.priceScrollView addSubview:hLabel]; // data 行:画下、左(不画上,避免与 header 行下边框叠加) UILabel *vLabel = [self makeCellLabel:col[@"value"] font:font borderColor:borderColor frame:CGRectMake(x, kTableRowHeight, w, kTableRowHeight) drawTop:NO drawBottom:YES isLast:isLast]; [self.priceScrollView addSubview:vLabel]; x += w; totalWidth = x; } self.priceScrollView.contentSize = CGSizeMake(totalWidth, kTableRowHeight * 2); } // MARK: - qtyField 数字过滤(iPad 全键盘模式) - (void)qtyFieldDidChange:(UITextField *)textField { NSString *filtered = [[textField.text componentsSeparatedByCharactersInSet: [[NSCharacterSet decimalDigitCharacterSet] invertedSet]] componentsJoinedByString:@""]; if (![textField.text isEqualToString:filtered]) textField.text = filtered; } // MARK: - 辅助 /** 用 CAShapeLayer 画边框,避免相邻列边框叠加变粗: 每列只画上、下、左三边;最后一列额外画右边。 */ - (UILabel *)makeCellLabel:(NSString *)text font:(UIFont *)font borderColor:(UIColor *)borderColor frame:(CGRect)frame drawTop:(BOOL)drawTop drawBottom:(BOOL)drawBottom isLast:(BOOL)isLast { UILabel *label = [[UILabel alloc] initWithFrame:frame]; label.text = text ?: @""; label.font = font; label.textAlignment = NSTextAlignmentCenter; label.clipsToBounds = YES; CGFloat w = frame.size.width; CGFloat h = frame.size.height; CAShapeLayer *border = [CAShapeLayer layer]; UIBezierPath *path = [UIBezierPath bezierPath]; if (drawTop) { [path moveToPoint:CGPointMake(0, 0)]; [path addLineToPoint:CGPointMake(w, 0)]; } if (drawBottom) { [path moveToPoint:CGPointMake(0, h)]; [path addLineToPoint:CGPointMake(w, h)]; } // 左边(每列都画) [path moveToPoint:CGPointMake(0, 0)]; [path addLineToPoint:CGPointMake(0, h)]; // 最后一列补右边 if (isLast) { [path moveToPoint:CGPointMake(w, 0)]; [path addLineToPoint:CGPointMake(w, h)]; } border.path = path.CGPath; border.strokeColor = borderColor.CGColor; border.lineWidth = 1.0; border.fillColor = [UIColor clearColor].CGColor; [label.layer addSublayer:border]; return label; } @end