From 83030a779ce8e63628d6d5f316614ed38ac64bdc Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 30 Dec 2025 14:59:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B4=A7=E5=93=81=E7=BC=96=E5=8F=B7?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=90=8C=E6=AD=A5=E5=88=B0=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E7=AB=AF=20extJsonStr=20=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ProductDto 增加 extJson 解析,从服务端读取货品编号 - ProductExtJson 类用于序列化/反序列化扩展属性 - 同步时优先使用本地货品编号,其次使用服务端的 - 新增"上传到服务端"按钮,批量上传货品编号 - 调用 /user/apiExtShopGoods/save 接口更新 extJsonStr 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- PackagingMallShipper/Models/Product.cs | 65 ++++++++++++++++- PackagingMallShipper/Services/Interfaces.cs | 12 +++ .../Services/ProductSyncService.cs | 73 ++++++++++++++++++- .../ViewModels/ProductListViewModel.cs | 63 ++++++++++++++++ .../Views/ProductListView.xaml | 6 ++ 5 files changed, 217 insertions(+), 2 deletions(-) diff --git a/PackagingMallShipper/Models/Product.cs b/PackagingMallShipper/Models/Product.cs index 7f24cf0..b0c6b80 100644 --- a/PackagingMallShipper/Models/Product.cs +++ b/PackagingMallShipper/Models/Product.cs @@ -264,18 +264,39 @@ namespace PackagingMallShipper.Models [JsonProperty("pics")] public List Pics { get; set; } + /// + /// 扩展属性JSON(用于存储货品编号等自定义字段) + /// + [JsonProperty("extJson")] + public ProductExtJson ExtJson { get; set; } + + /// + /// 获取扩展属性中的货品编号 + /// + public string GetItemCode() + { + return ExtJson?.ItemCode; + } + /// /// 转换为本地Product模型 /// public Product ToProduct(Product existingProduct = null) { + // 优先使用本地已有的货品编号,其次使用服务端的扩展属性 + var itemCode = existingProduct?.ItemCode; + if (string.IsNullOrEmpty(itemCode)) + { + itemCode = GetItemCode(); + } + return new Product { Id = Id, Name = Name, BarCode = BarCode, YyId = YyId, - ItemCode = existingProduct?.ItemCode, + ItemCode = itemCode, MinPrice = MinPrice, OriginalPrice = OriginalPrice, Stores = Stores, @@ -299,4 +320,46 @@ namespace PackagingMallShipper.Models }; } } + + /// + /// 产品扩展属性(存储在服务端 extJsonStr 字段) + /// + public class ProductExtJson + { + /// + /// 货品编号 + /// + [JsonProperty("itemCode")] + public string ItemCode { get; set; } + + /// + /// 本地备注(可选同步) + /// + [JsonProperty("localNote")] + public string LocalNote { get; set; } + + /// + /// 序列化为JSON字符串 + /// + public string ToJsonString() + { + return JsonConvert.SerializeObject(this); + } + + /// + /// 从JSON字符串解析 + /// + public static ProductExtJson FromJsonString(string json) + { + if (string.IsNullOrEmpty(json)) return null; + try + { + return JsonConvert.DeserializeObject(json); + } + catch + { + return null; + } + } + } } diff --git a/PackagingMallShipper/Services/Interfaces.cs b/PackagingMallShipper/Services/Interfaces.cs index c626fa0..52932d4 100644 --- a/PackagingMallShipper/Services/Interfaces.cs +++ b/PackagingMallShipper/Services/Interfaces.cs @@ -64,7 +64,19 @@ namespace PackagingMallShipper.Services public interface IProductSyncService { Task SyncProductsAsync(SyncMode mode = SyncMode.Incremental); + Task UploadItemCodesToServerAsync(List products); event Action OnSyncProgress; event Action OnSyncMessage; } + + /// + /// 上传结果 + /// + public class UploadResult + { + public int TotalCount { get; set; } + public int SuccessCount { get; set; } + public int FailedCount { get; set; } + public List Errors { get; set; } = new List(); + } } diff --git a/PackagingMallShipper/Services/ProductSyncService.cs b/PackagingMallShipper/Services/ProductSyncService.cs index 2237aa7..ae948cb 100644 --- a/PackagingMallShipper/Services/ProductSyncService.cs +++ b/PackagingMallShipper/Services/ProductSyncService.cs @@ -151,6 +151,13 @@ namespace PackagingMallShipper.Services var isNew = !exists; var picsJson = dto.Pics != null ? JsonConvert.SerializeObject(dto.Pics) : ""; + // 优先使用本地已有的货品编号,其次使用服务端 extJson 中的 + var itemCode = existingItemCode; + if (string.IsNullOrEmpty(itemCode)) + { + itemCode = dto.GetItemCode() ?? ""; + } + var sql = isNew ? @"INSERT INTO products_cache (id, name, bar_code, yy_id, item_code, min_price, original_price, stores, status, @@ -171,7 +178,7 @@ namespace PackagingMallShipper.Services cmd.Parameters.AddWithValue("@name", dto.Name ?? ""); cmd.Parameters.AddWithValue("@barCode", dto.BarCode ?? ""); cmd.Parameters.AddWithValue("@yyId", dto.YyId ?? ""); - cmd.Parameters.AddWithValue("@itemCode", existingItemCode ?? ""); + cmd.Parameters.AddWithValue("@itemCode", itemCode); cmd.Parameters.AddWithValue("@minPrice", dto.MinPrice); cmd.Parameters.AddWithValue("@originalPrice", dto.OriginalPrice); cmd.Parameters.AddWithValue("@stores", dto.Stores); @@ -187,5 +194,69 @@ namespace PackagingMallShipper.Services } }); } + + /// + /// 上传货品编号到服务端(通过 extJsonStr 字段) + /// + public async Task UploadItemCodesToServerAsync(List products) + { + var token = _authService.GetToken(); + if (string.IsNullOrEmpty(token)) + throw new UnauthorizedAccessException("未登录,请先登录"); + + var result = new UploadResult { TotalCount = products.Count }; + + for (int i = 0; i < products.Count; i++) + { + var product = products[i]; + OnSyncMessage?.Invoke($"正在上传 {i + 1}/{products.Count}: {product.Name}"); + OnSyncProgress?.Invoke(i + 1, products.Count); + + try + { + var extJson = new ProductExtJson + { + ItemCode = product.ItemCode, + LocalNote = product.LocalNote + }; + + var url = "https://user.api.it120.cc/user/apiExtShopGoods/save"; + var formData = new FormUrlEncodedContent(new[] + { + new KeyValuePair("id", product.Id.ToString()), + new KeyValuePair("extJsonStr", extJson.ToJsonString()) + }); + + _httpClient.DefaultRequestHeaders.Clear(); + _httpClient.DefaultRequestHeaders.Add("X-Token", token); + + var response = await _httpClient.PostAsync(url, formData); + var json = await response.Content.ReadAsStringAsync(); + + Debug.WriteLine($"[上传货品编号] 产品ID: {product.Id}, 响应: {json}"); + + var data = JsonConvert.DeserializeObject>(json); + + if (data?.Code == 0) + { + result.SuccessCount++; + } + else + { + result.FailedCount++; + result.Errors.Add($"{product.Name}: {data?.Msg ?? "未知错误"}"); + } + } + catch (Exception ex) + { + result.FailedCount++; + result.Errors.Add($"{product.Name}: {ex.Message}"); + Debug.WriteLine($"[上传货品编号异常] {product.Name}: {ex.Message}"); + } + } + + OnSyncMessage?.Invoke($"上传完成!成功 {result.SuccessCount},失败 {result.FailedCount}"); + return result; + } } } diff --git a/PackagingMallShipper/ViewModels/ProductListViewModel.cs b/PackagingMallShipper/ViewModels/ProductListViewModel.cs index ddc6322..b4cef18 100644 --- a/PackagingMallShipper/ViewModels/ProductListViewModel.cs +++ b/PackagingMallShipper/ViewModels/ProductListViewModel.cs @@ -134,6 +134,69 @@ namespace PackagingMallShipper.ViewModels } } + [RelayCommand] + private async Task UploadItemCodesToServerAsync() + { + // 获取所有有货品编号的产品 + var productsWithItemCode = new System.Collections.Generic.List(); + foreach (var p in Products) + { + if (p.HasItemCode) + productsWithItemCode.Add(p); + } + + if (productsWithItemCode.Count == 0) + { + MessageBox.Show("没有已设置货品编号的产品", "提示", MessageBoxButton.OK, MessageBoxImage.Information); + return; + } + + var confirmResult = MessageBox.Show( + $"将上传 {productsWithItemCode.Count} 个产品的货品编号到服务端,是否继续?", + "确认上传", + MessageBoxButton.YesNo, + MessageBoxImage.Question); + + if (confirmResult != MessageBoxResult.Yes) return; + + IsBusy = true; + StatusMessage = "正在上传货品编号..."; + + try + { + var result = await _productSyncService.UploadItemCodesToServerAsync(productsWithItemCode); + + if (result.FailedCount > 0) + { + var errorMsg = string.Join("\n", result.Errors); + MessageBox.Show($"上传完成!成功 {result.SuccessCount},失败 {result.FailedCount}\n\n失败详情:\n{errorMsg}", + "上传结果", MessageBoxButton.OK, MessageBoxImage.Warning); + } + else + { + MessageBox.Show($"上传完成!成功 {result.SuccessCount} 个产品", + "上传成功", MessageBoxButton.OK, MessageBoxImage.Information); + } + + StatusMessage = $"上传完成!成功 {result.SuccessCount},失败 {result.FailedCount}"; + } + catch (UnauthorizedAccessException) + { + StatusMessage = "登录已过期,请重新登录"; + MessageBox.Show("登录已过期,请重新登录", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); + } + catch (Exception ex) + { + StatusMessage = $"上传失败: {ex.Message}"; + MessageBox.Show($"上传失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); + } + finally + { + IsBusy = false; + SyncProgress = ""; + } + } + [RelayCommand] private async Task SearchAsync() { diff --git a/PackagingMallShipper/Views/ProductListView.xaml b/PackagingMallShipper/Views/ProductListView.xaml index 9588f19..352f799 100644 --- a/PackagingMallShipper/Views/ProductListView.xaml +++ b/PackagingMallShipper/Views/ProductListView.xaml @@ -81,6 +81,12 @@