From 4fd9c44ee86fd436331fdbf286bd6588726e813b Mon Sep 17 00:00:00 2001 From: Administrator Date: Thu, 5 Mar 2026 09:30:19 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E5=95=86?= =?UTF-8?q?=E5=93=81=E7=AE=A1=E7=90=86=E6=A8=A1=E5=9D=97=E5=B9=B6=E7=AE=80?= =?UTF-8?q?=E5=8C=96=E4=B8=BB=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PackagingMallShipper/Models/Product.cs | 365 ------------------ PackagingMallShipper/Services/ExcelService.cs | 24 +- PackagingMallShipper/Services/Interfaces.cs | 32 -- .../Services/ProductService.cs | 350 ----------------- .../Services/ProductSyncService.cs | 262 ------------- .../ViewModels/MainViewModel.cs | 7 +- .../ViewModels/ProductListViewModel.cs | 300 -------------- .../Views/LoginWindow.xaml.cs | 8 +- PackagingMallShipper/Views/MainWindow.xaml | 41 +- PackagingMallShipper/Views/MainWindow.xaml.cs | 1 - .../Views/ProductListView.xaml | 206 ---------- .../Views/ProductListView.xaml.cs | 19 - 12 files changed, 17 insertions(+), 1598 deletions(-) delete mode 100644 PackagingMallShipper/Models/Product.cs delete mode 100644 PackagingMallShipper/Services/ProductService.cs delete mode 100644 PackagingMallShipper/Services/ProductSyncService.cs delete mode 100644 PackagingMallShipper/ViewModels/ProductListViewModel.cs delete mode 100644 PackagingMallShipper/Views/ProductListView.xaml delete mode 100644 PackagingMallShipper/Views/ProductListView.xaml.cs diff --git a/PackagingMallShipper/Models/Product.cs b/PackagingMallShipper/Models/Product.cs deleted file mode 100644 index b0c6b80..0000000 --- a/PackagingMallShipper/Models/Product.cs +++ /dev/null @@ -1,365 +0,0 @@ -using System; -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace PackagingMallShipper.Models -{ - /// - /// 本地产品模型 - /// - public class Product - { - /// - /// 商品ID(来自API) - /// - public int Id { get; set; } - - /// - /// 商品名称 - /// - public string Name { get; set; } - - /// - /// 条码编号(来自API的barCode) - /// - public string BarCode { get; set; } - - /// - /// 外部商品编号(来自API的yyId) - /// - public string YyId { get; set; } - - /// - /// 本地自定义货品编号(用户可编辑) - /// 格式如:1222003x12+13222003x24+1322003x12+0000000x12 - /// - public string ItemCode { get; set; } - - /// - /// 现价 - /// - public decimal MinPrice { get; set; } - - /// - /// 原价 - /// - public decimal OriginalPrice { get; set; } - - /// - /// 库存 - /// - public int Stores { get; set; } - - /// - /// 状态(0:上架, 1:下架) - /// - public int Status { get; set; } - - /// - /// 分类ID - /// - public int? CategoryId { get; set; } - - /// - /// 单位 - /// - public string Unit { get; set; } - - /// - /// 图片数组JSON - /// - public string PicsJson { get; set; } - - // ========== 本地管理字段 ========== - - /// - /// 是否为纯本地产品(未同步到远程) - /// - public bool IsLocalOnly { get; set; } - - /// - /// 本地备注 - /// - public string LocalNote { get; set; } - - public DateTime? LocalCreatedAt { get; set; } - public DateTime? LocalUpdatedAt { get; set; } - public DateTime? SyncedAt { get; set; } - - // ========== 扩展价格字段(对应产品报价表)========== - - /// - /// 自提开票价(元/套) - /// - public decimal? PickupPrice { get; set; } - - /// - /// 快递开票价(元/套) - /// - public decimal? ExpressPrice { get; set; } - - /// - /// 包价格(元/箱) - /// - public decimal? PackPrice { get; set; } - - /// - /// 打包数量(套/箱) - /// - public int? PackQuantity { get; set; } - - // ========== 规格参数 ========== - - /// - /// 产品系列 - /// - public string ProductSeries { get; set; } - - /// - /// 放置方式 - /// - public string PlacementMethod { get; set; } - - /// - /// 厚度 - /// - public string Thickness { get; set; } - - /// - /// 孔径尺寸 - /// - public string HoleSize { get; set; } - - /// - /// 纸箱尺寸 - /// - public string BoxSize { get; set; } - - /// - /// 适用蛋型 - /// - public string ApplicableType { get; set; } - - // ========== 计算属性 ========== - - /// - /// 状态文本 - /// - public string StatusText => Status == 0 ? "上架" : "下架"; - - /// - /// 首张图片URL - /// - public string FirstPic - { - get - { - if (string.IsNullOrEmpty(PicsJson)) return null; - try - { - var pics = JsonConvert.DeserializeObject>(PicsJson); - return pics?.Count > 0 ? pics[0] : null; - } - catch - { - return null; - } - } - } - - /// - /// 是否已设置货品编号 - /// - public bool HasItemCode => !string.IsNullOrWhiteSpace(ItemCode); - - /// - /// 用于UI选择 - /// - public bool IsSelected { get; set; } - } - - /// - /// 产品状态枚举 - /// - public static class ProductStatus - { - public const int OnSale = 0; // 上架 - public const int OffSale = 1; // 下架 - - public static string GetStatusText(int status) - { - return status == OnSale ? "上架" : "下架"; - } - } - - /// - /// 产品列表API响应数据 - /// - public class ProductListData - { - [JsonProperty("result")] - public List Result { get; set; } - - [JsonProperty("list")] - public List List { get; set; } - - [JsonProperty("totalRow")] - public int TotalRow { get; set; } - - [JsonProperty("total")] - public int Total { get; set; } - - [JsonProperty("totalPage")] - public int TotalPage { get; set; } - - /// - /// 获取产品列表(兼容多种返回格式) - /// - public List GetProducts() - { - return Result ?? List ?? new List(); - } - - public int GetTotalRow() - { - return TotalRow > 0 ? TotalRow : Total; - } - } - - /// - /// API返回的产品DTO - /// - public class ProductDto - { - [JsonProperty("id")] - public int Id { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("barCode")] - public string BarCode { get; set; } - - [JsonProperty("yyId")] - public string YyId { get; set; } - - [JsonProperty("minPrice")] - public decimal MinPrice { get; set; } - - [JsonProperty("originalPrice")] - public decimal OriginalPrice { get; set; } - - [JsonProperty("stores")] - public int Stores { get; set; } - - [JsonProperty("status")] - public int Status { get; set; } - - [JsonProperty("categoryId")] - public int? CategoryId { get; set; } - - [JsonProperty("unit")] - public string Unit { get; set; } - - [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 = itemCode, - MinPrice = MinPrice, - OriginalPrice = OriginalPrice, - Stores = Stores, - Status = Status, - CategoryId = CategoryId, - Unit = Unit, - PicsJson = Pics != null ? JsonConvert.SerializeObject(Pics) : null, - IsLocalOnly = false, - LocalNote = existingProduct?.LocalNote, - PickupPrice = existingProduct?.PickupPrice, - ExpressPrice = existingProduct?.ExpressPrice, - PackPrice = existingProduct?.PackPrice, - PackQuantity = existingProduct?.PackQuantity, - ProductSeries = existingProduct?.ProductSeries, - PlacementMethod = existingProduct?.PlacementMethod, - Thickness = existingProduct?.Thickness, - HoleSize = existingProduct?.HoleSize, - BoxSize = existingProduct?.BoxSize, - ApplicableType = existingProduct?.ApplicableType, - SyncedAt = DateTime.Now - }; - } - } - - /// - /// 产品扩展属性(存储在服务端 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/ExcelService.cs b/PackagingMallShipper/Services/ExcelService.cs index bc62284..1879c2e 100644 --- a/PackagingMallShipper/Services/ExcelService.cs +++ b/PackagingMallShipper/Services/ExcelService.cs @@ -12,25 +12,17 @@ namespace PackagingMallShipper.Services { private readonly IOrderService _orderService; private readonly IShipService _shipService; - private readonly IProductService _productService; - public ExcelService(IOrderService orderService, IShipService shipService, IProductService productService) + public ExcelService(IOrderService orderService, IShipService shipService) { _orderService = orderService; _shipService = shipService; - _productService = productService; } public async Task ExportPendingOrdersAsync(string filePath) { var orders = await _orderService.GetOrdersAsync(status: 1); - // 收集所有订单中的商品ID - var allGoodsIds = orders.SelectMany(o => o.GetGoodsIds()).Distinct().ToList(); - - // 批量查询货品编号 - var itemCodeMap = await _productService.GetItemCodesByGoodsIdsAsync(allGoodsIds); - using (var workbook = new XLWorkbook()) { var worksheet = workbook.Worksheets.Add("待发货订单"); @@ -39,7 +31,7 @@ namespace PackagingMallShipper.Services { "订单号", "下单时间", "收件人", "联系电话", "省份", "城市", "区县", "收货地址", - "商品信息", "货品编号", "数量", "快递公司", "快递单号" + "商品信息", "数量", "快递公司", "快递单号" }; for (int i = 0; i < headers.Length; i++) @@ -65,10 +57,9 @@ namespace PackagingMallShipper.Services worksheet.Cell(row, 7).Value = order.LogisticsDistrict; worksheet.Cell(row, 8).Value = order.FullAddress; worksheet.Cell(row, 9).Value = order.GoodsInfo; - worksheet.Cell(row, 10).Value = order.GetItemCodesInfo(itemCodeMap); - worksheet.Cell(row, 11).Value = order.TotalQuantity; + worksheet.Cell(row, 10).Value = order.TotalQuantity; + worksheet.Cell(row, 11).Value = ""; worksheet.Cell(row, 12).Value = ""; - worksheet.Cell(row, 13).Value = ""; } worksheet.Column(1).Width = 20; @@ -76,8 +67,7 @@ namespace PackagingMallShipper.Services worksheet.Column(4).Width = 15; worksheet.Column(8).Width = 50; worksheet.Column(9).Width = 30; - worksheet.Column(10).Width = 40; - worksheet.Column(13).Width = 20; + worksheet.Column(12).Width = 20; worksheet.SheetView.FreezeRows(1); @@ -102,8 +92,8 @@ namespace PackagingMallShipper.Services foreach (var row in rows) { var orderNumber = row.Cell(1).GetString().Trim(); - var expressCompanyName = row.Cell(12).GetString().Trim(); - var trackingNumber = row.Cell(13).GetString().Trim(); + var expressCompanyName = row.Cell(11).GetString().Trim(); + var trackingNumber = row.Cell(12).GetString().Trim(); if (string.IsNullOrEmpty(orderNumber)) continue; diff --git a/PackagingMallShipper/Services/Interfaces.cs b/PackagingMallShipper/Services/Interfaces.cs index 52932d4..f70bc3c 100644 --- a/PackagingMallShipper/Services/Interfaces.cs +++ b/PackagingMallShipper/Services/Interfaces.cs @@ -47,36 +47,4 @@ namespace PackagingMallShipper.Services Task ImportAndShipAsync(string filePath); } - public interface IProductService - { - Task> GetProductsAsync(int? status = null, string keyword = null); - Task GetProductByIdAsync(int productId); - Task GetProductByItemCodeAsync(string itemCode); - Task GetProductByBarCodeAsync(string barCode); - Task SaveProductAsync(Product product); - Task DeleteProductAsync(int productId); - Task UpdateItemCodeAsync(int productId, string itemCode); - Task GetProductCountAsync(int? status = null); - Task BatchUpdateItemCodesAsync(Dictionary productItemCodes); - Task> GetItemCodesByGoodsIdsAsync(List goodsIds); - } - - 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/ProductService.cs b/PackagingMallShipper/Services/ProductService.cs deleted file mode 100644 index af9b782..0000000 --- a/PackagingMallShipper/Services/ProductService.cs +++ /dev/null @@ -1,350 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.SQLite; -using System.Threading.Tasks; -using PackagingMallShipper.Data; -using PackagingMallShipper.Models; - -namespace PackagingMallShipper.Services -{ - public class ProductService : IProductService - { - public Task> GetProductsAsync(int? status = null, string keyword = null) - { - return Task.Run(() => - { - var products = new List(); - using (var conn = SqliteHelper.GetConnection()) - { - conn.Open(); - var sql = "SELECT * FROM products_cache WHERE 1=1"; - - if (status.HasValue) - sql += " AND status = @status"; - - if (!string.IsNullOrWhiteSpace(keyword)) - sql += " AND (name LIKE @keyword OR bar_code LIKE @keyword OR yy_id LIKE @keyword OR item_code LIKE @keyword)"; - - sql += " ORDER BY local_updated_at DESC, synced_at DESC"; - - using (var cmd = new SQLiteCommand(sql, conn)) - { - if (status.HasValue) - cmd.Parameters.AddWithValue("@status", status.Value); - - if (!string.IsNullOrWhiteSpace(keyword)) - cmd.Parameters.AddWithValue("@keyword", $"%{keyword}%"); - - using (var reader = cmd.ExecuteReader()) - { - while (reader.Read()) - { - products.Add(MapProduct(reader)); - } - } - } - } - return products; - }); - } - - public Task GetProductByIdAsync(int productId) - { - return Task.Run(() => - { - using (var conn = SqliteHelper.GetConnection()) - { - conn.Open(); - var sql = "SELECT * FROM products_cache WHERE id = @id"; - using (var cmd = new SQLiteCommand(sql, conn)) - { - cmd.Parameters.AddWithValue("@id", productId); - using (var reader = cmd.ExecuteReader()) - { - if (reader.Read()) - return MapProduct(reader); - } - } - } - return null; - }); - } - - public Task GetProductByItemCodeAsync(string itemCode) - { - return Task.Run(() => - { - using (var conn = SqliteHelper.GetConnection()) - { - conn.Open(); - var sql = "SELECT * FROM products_cache WHERE item_code = @itemCode"; - using (var cmd = new SQLiteCommand(sql, conn)) - { - cmd.Parameters.AddWithValue("@itemCode", itemCode); - using (var reader = cmd.ExecuteReader()) - { - if (reader.Read()) - return MapProduct(reader); - } - } - } - return null; - }); - } - - public Task GetProductByBarCodeAsync(string barCode) - { - return Task.Run(() => - { - using (var conn = SqliteHelper.GetConnection()) - { - conn.Open(); - var sql = "SELECT * FROM products_cache WHERE bar_code = @barCode"; - using (var cmd = new SQLiteCommand(sql, conn)) - { - cmd.Parameters.AddWithValue("@barCode", barCode); - using (var reader = cmd.ExecuteReader()) - { - if (reader.Read()) - return MapProduct(reader); - } - } - } - return null; - }); - } - - public Task SaveProductAsync(Product product) - { - return Task.Run(() => - { - using (var conn = SqliteHelper.GetConnection()) - { - conn.Open(); - - // 检查是否存在 - var checkSql = "SELECT id FROM products_cache WHERE id = @id"; - bool exists = false; - using (var cmd = new SQLiteCommand(checkSql, conn)) - { - cmd.Parameters.AddWithValue("@id", product.Id); - exists = cmd.ExecuteScalar() != null; - } - - var sql = exists - ? @"UPDATE products_cache SET - name = @name, bar_code = @barCode, yy_id = @yyId, item_code = @itemCode, - min_price = @minPrice, original_price = @originalPrice, stores = @stores, - status = @status, category_id = @categoryId, unit = @unit, pics_json = @picsJson, - is_local_only = @isLocalOnly, local_note = @localNote, local_updated_at = @localUpdatedAt, - pickup_price = @pickupPrice, express_price = @expressPrice, pack_price = @packPrice, - pack_quantity = @packQuantity, product_series = @productSeries, - placement_method = @placementMethod, thickness = @thickness, hole_size = @holeSize, - box_size = @boxSize, applicable_type = @applicableType - WHERE id = @id" - : @"INSERT INTO products_cache - (id, name, bar_code, yy_id, item_code, min_price, original_price, stores, status, - category_id, unit, pics_json, is_local_only, local_note, local_created_at, local_updated_at, - pickup_price, express_price, pack_price, pack_quantity, product_series, - placement_method, thickness, hole_size, box_size, applicable_type) - VALUES - (@id, @name, @barCode, @yyId, @itemCode, @minPrice, @originalPrice, @stores, @status, - @categoryId, @unit, @picsJson, @isLocalOnly, @localNote, @localCreatedAt, @localUpdatedAt, - @pickupPrice, @expressPrice, @packPrice, @packQuantity, @productSeries, - @placementMethod, @thickness, @holeSize, @boxSize, @applicableType)"; - - using (var cmd = new SQLiteCommand(sql, conn)) - { - cmd.Parameters.AddWithValue("@id", product.Id); - cmd.Parameters.AddWithValue("@name", product.Name ?? ""); - cmd.Parameters.AddWithValue("@barCode", product.BarCode ?? ""); - cmd.Parameters.AddWithValue("@yyId", product.YyId ?? ""); - cmd.Parameters.AddWithValue("@itemCode", product.ItemCode ?? ""); - cmd.Parameters.AddWithValue("@minPrice", product.MinPrice); - cmd.Parameters.AddWithValue("@originalPrice", product.OriginalPrice); - cmd.Parameters.AddWithValue("@stores", product.Stores); - cmd.Parameters.AddWithValue("@status", product.Status); - cmd.Parameters.AddWithValue("@categoryId", (object)product.CategoryId ?? DBNull.Value); - cmd.Parameters.AddWithValue("@unit", product.Unit ?? ""); - cmd.Parameters.AddWithValue("@picsJson", product.PicsJson ?? ""); - cmd.Parameters.AddWithValue("@isLocalOnly", product.IsLocalOnly ? 1 : 0); - cmd.Parameters.AddWithValue("@localNote", product.LocalNote ?? ""); - cmd.Parameters.AddWithValue("@localCreatedAt", product.LocalCreatedAt ?? DateTime.Now); - cmd.Parameters.AddWithValue("@localUpdatedAt", DateTime.Now); - cmd.Parameters.AddWithValue("@pickupPrice", (object)product.PickupPrice ?? DBNull.Value); - cmd.Parameters.AddWithValue("@expressPrice", (object)product.ExpressPrice ?? DBNull.Value); - cmd.Parameters.AddWithValue("@packPrice", (object)product.PackPrice ?? DBNull.Value); - cmd.Parameters.AddWithValue("@packQuantity", (object)product.PackQuantity ?? DBNull.Value); - cmd.Parameters.AddWithValue("@productSeries", product.ProductSeries ?? ""); - cmd.Parameters.AddWithValue("@placementMethod", product.PlacementMethod ?? ""); - cmd.Parameters.AddWithValue("@thickness", product.Thickness ?? ""); - cmd.Parameters.AddWithValue("@holeSize", product.HoleSize ?? ""); - cmd.Parameters.AddWithValue("@boxSize", product.BoxSize ?? ""); - cmd.Parameters.AddWithValue("@applicableType", product.ApplicableType ?? ""); - - return cmd.ExecuteNonQuery() > 0; - } - } - }); - } - - public Task DeleteProductAsync(int productId) - { - return Task.Run(() => - { - using (var conn = SqliteHelper.GetConnection()) - { - conn.Open(); - var sql = "DELETE FROM products_cache WHERE id = @id AND is_local_only = 1"; - using (var cmd = new SQLiteCommand(sql, conn)) - { - cmd.Parameters.AddWithValue("@id", productId); - return cmd.ExecuteNonQuery() > 0; - } - } - }); - } - - public Task UpdateItemCodeAsync(int productId, string itemCode) - { - return Task.Run(() => - { - using (var conn = SqliteHelper.GetConnection()) - { - conn.Open(); - var sql = "UPDATE products_cache SET item_code = @itemCode, local_updated_at = @now WHERE id = @id"; - using (var cmd = new SQLiteCommand(sql, conn)) - { - cmd.Parameters.AddWithValue("@id", productId); - cmd.Parameters.AddWithValue("@itemCode", itemCode ?? ""); - cmd.Parameters.AddWithValue("@now", DateTime.Now); - return cmd.ExecuteNonQuery() > 0; - } - } - }); - } - - public Task GetProductCountAsync(int? status = null) - { - return Task.Run(() => - { - using (var conn = SqliteHelper.GetConnection()) - { - conn.Open(); - var sql = "SELECT COUNT(*) FROM products_cache"; - if (status.HasValue) - sql += " WHERE status = @status"; - - using (var cmd = new SQLiteCommand(sql, conn)) - { - if (status.HasValue) - cmd.Parameters.AddWithValue("@status", status.Value); - - return Convert.ToInt32(cmd.ExecuteScalar()); - } - } - }); - } - - public Task BatchUpdateItemCodesAsync(Dictionary productItemCodes) - { - return Task.Run(() => - { - int updated = 0; - using (var conn = SqliteHelper.GetConnection()) - { - conn.Open(); - using (var transaction = conn.BeginTransaction()) - { - try - { - var sql = "UPDATE products_cache SET item_code = @itemCode, local_updated_at = @now WHERE id = @id"; - foreach (var kvp in productItemCodes) - { - using (var cmd = new SQLiteCommand(sql, conn, transaction)) - { - cmd.Parameters.AddWithValue("@id", kvp.Key); - cmd.Parameters.AddWithValue("@itemCode", kvp.Value ?? ""); - cmd.Parameters.AddWithValue("@now", DateTime.Now); - updated += cmd.ExecuteNonQuery(); - } - } - transaction.Commit(); - } - catch - { - transaction.Rollback(); - throw; - } - } - } - return updated; - }); - } - - public Task> GetItemCodesByGoodsIdsAsync(List goodsIds) - { - return Task.Run(() => - { - var result = new Dictionary(); - if (goodsIds == null || goodsIds.Count == 0) return result; - - using (var conn = SqliteHelper.GetConnection()) - { - conn.Open(); - var idList = string.Join(",", goodsIds); - var sql = $"SELECT id, item_code FROM products_cache WHERE id IN ({idList})"; - - using (var cmd = new SQLiteCommand(sql, conn)) - using (var reader = cmd.ExecuteReader()) - { - while (reader.Read()) - { - var id = reader.GetInt32(0); - var itemCode = reader.GetStringSafe("item_code"); - if (!string.IsNullOrEmpty(itemCode)) - { - result[id] = itemCode; - } - } - } - } - return result; - }); - } - - private Product MapProduct(SQLiteDataReader reader) - { - return new Product - { - Id = reader.GetInt32(reader.GetOrdinal("id")), - Name = reader.GetStringSafe("name"), - BarCode = reader.GetStringSafe("bar_code"), - YyId = reader.GetStringSafe("yy_id"), - ItemCode = reader.GetStringSafe("item_code"), - MinPrice = reader.GetDecimalSafe("min_price"), - OriginalPrice = reader.GetDecimalSafe("original_price"), - Stores = reader.GetInt32Safe("stores"), - Status = reader.GetInt32Safe("status"), - CategoryId = reader.GetInt32Nullable("category_id"), - Unit = reader.GetStringSafe("unit"), - PicsJson = reader.GetStringSafe("pics_json"), - IsLocalOnly = reader.GetInt32Safe("is_local_only") == 1, - LocalNote = reader.GetStringSafe("local_note"), - LocalCreatedAt = reader.GetDateTimeSafe("local_created_at"), - LocalUpdatedAt = reader.GetDateTimeSafe("local_updated_at"), - SyncedAt = reader.GetDateTimeSafe("synced_at"), - PickupPrice = reader.GetDecimalNullable("pickup_price"), - ExpressPrice = reader.GetDecimalNullable("express_price"), - PackPrice = reader.GetDecimalNullable("pack_price"), - PackQuantity = reader.GetInt32Nullable("pack_quantity"), - ProductSeries = reader.GetStringSafe("product_series"), - PlacementMethod = reader.GetStringSafe("placement_method"), - Thickness = reader.GetStringSafe("thickness"), - HoleSize = reader.GetStringSafe("hole_size"), - BoxSize = reader.GetStringSafe("box_size"), - ApplicableType = reader.GetStringSafe("applicable_type") - }; - } - } -} diff --git a/PackagingMallShipper/Services/ProductSyncService.cs b/PackagingMallShipper/Services/ProductSyncService.cs deleted file mode 100644 index ae948cb..0000000 --- a/PackagingMallShipper/Services/ProductSyncService.cs +++ /dev/null @@ -1,262 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.SQLite; -using System.Diagnostics; -using System.Net.Http; -using System.Threading.Tasks; -using Newtonsoft.Json; -using PackagingMallShipper.Data; -using PackagingMallShipper.Models; - -namespace PackagingMallShipper.Services -{ - public class ProductSyncService : IProductSyncService - { - private readonly IAuthService _authService; - private readonly HttpClient _httpClient; - - public event Action OnSyncProgress; - public event Action OnSyncMessage; - - public ProductSyncService(IAuthService authService) - { - _authService = authService; - _httpClient = new HttpClient(); - _httpClient.Timeout = TimeSpan.FromSeconds(60); - } - - public async Task SyncProductsAsync(SyncMode mode = SyncMode.Incremental) - { - var token = _authService.GetToken(); - if (string.IsNullOrEmpty(token)) - throw new UnauthorizedAccessException("未登录,请先登录"); - - var result = new SyncResult(); - - try - { - int page = 1; - int pageSize = 50; - bool hasMore = true; - int totalPages = 1; - - while (hasMore) - { - OnSyncMessage?.Invoke($"正在同步产品第 {page}/{totalPages} 页..."); - - var url = $"https://user.api.it120.cc/user/apiExtShopGoods/list?page={page}&pageSize={pageSize}"; - - Debug.WriteLine($"[产品同步] URL: {url}"); - - _httpClient.DefaultRequestHeaders.Clear(); - _httpClient.DefaultRequestHeaders.Add("X-Token", token); - - var response = await _httpClient.PostAsync(url, null); - var json = await response.Content.ReadAsStringAsync(); - - Debug.WriteLine($"[产品同步] HTTP状态码: {response.StatusCode}"); - Debug.WriteLine($"[产品同步] 响应长度: {json?.Length ?? 0}"); - - var data = JsonConvert.DeserializeObject>(json); - - if (data?.Code != 0) - { - if (data?.Msg?.Contains("token") == true || data?.Msg?.Contains("登录") == true) - throw new UnauthorizedAccessException("当前登录token无效,请重新登录"); - throw new Exception(data?.Msg ?? "获取产品列表失败"); - } - - var products = data.Data?.GetProducts() ?? new List(); - totalPages = data.Data?.TotalPage ?? 1; - if (totalPages == 0) totalPages = 1; - var totalRow = data.Data?.GetTotalRow() ?? 0; - - Debug.WriteLine($"[产品同步] 产品数量: {products.Count}, 总记录: {totalRow}, 总页数: {totalPages}"); - - if (products.Count == 0) break; - - foreach (var productDto in products) - { - var isNew = await SaveProductAsync(productDto); - if (isNew) result.NewCount++; - else result.UpdatedCount++; - } - - result.TotalCount += products.Count; - OnSyncProgress?.Invoke(page, totalPages); - - if (page >= totalPages) hasMore = false; - else page++; - } - - OnSyncMessage?.Invoke($"产品同步完成!新增 {result.NewCount},更新 {result.UpdatedCount}"); - } - catch (Exception ex) - { - Debug.WriteLine($"[产品同步异常] {ex.Message}"); - throw; - } - - return result; - } - - private Task SaveProductAsync(ProductDto dto) - { - return Task.Run(() => - { - using (var conn = SqliteHelper.GetConnection()) - { - conn.Open(); - - // 检查是否存在,并获取现有的本地字段 - string existingItemCode = null; - string existingLocalNote = null; - decimal? existingPickupPrice = null; - decimal? existingExpressPrice = null; - decimal? existingPackPrice = null; - int? existingPackQuantity = null; - string existingProductSeries = null; - string existingPlacementMethod = null; - string existingThickness = null; - string existingHoleSize = null; - string existingBoxSize = null; - string existingApplicableType = null; - bool exists = false; - - var checkSql = "SELECT * FROM products_cache WHERE id = @id"; - using (var cmd = new SQLiteCommand(checkSql, conn)) - { - cmd.Parameters.AddWithValue("@id", dto.Id); - using (var reader = cmd.ExecuteReader()) - { - if (reader.Read()) - { - exists = true; - existingItemCode = reader.GetStringSafe("item_code"); - existingLocalNote = reader.GetStringSafe("local_note"); - existingPickupPrice = reader.GetDecimalNullable("pickup_price"); - existingExpressPrice = reader.GetDecimalNullable("express_price"); - existingPackPrice = reader.GetDecimalNullable("pack_price"); - existingPackQuantity = reader.GetInt32Nullable("pack_quantity"); - existingProductSeries = reader.GetStringSafe("product_series"); - existingPlacementMethod = reader.GetStringSafe("placement_method"); - existingThickness = reader.GetStringSafe("thickness"); - existingHoleSize = reader.GetStringSafe("hole_size"); - existingBoxSize = reader.GetStringSafe("box_size"); - existingApplicableType = reader.GetStringSafe("applicable_type"); - } - } - } - - 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, - category_id, unit, pics_json, is_local_only, synced_at, local_created_at) - VALUES - (@id, @name, @barCode, @yyId, @itemCode, @minPrice, @originalPrice, @stores, @status, - @categoryId, @unit, @picsJson, 0, @syncedAt, @syncedAt)" - : @"UPDATE products_cache SET - name = @name, bar_code = @barCode, yy_id = @yyId, - min_price = @minPrice, original_price = @originalPrice, stores = @stores, - status = @status, category_id = @categoryId, unit = @unit, pics_json = @picsJson, - synced_at = @syncedAt - WHERE id = @id"; - - using (var cmd = new SQLiteCommand(sql, conn)) - { - cmd.Parameters.AddWithValue("@id", dto.Id); - cmd.Parameters.AddWithValue("@name", dto.Name ?? ""); - cmd.Parameters.AddWithValue("@barCode", dto.BarCode ?? ""); - cmd.Parameters.AddWithValue("@yyId", dto.YyId ?? ""); - cmd.Parameters.AddWithValue("@itemCode", itemCode); - cmd.Parameters.AddWithValue("@minPrice", dto.MinPrice); - cmd.Parameters.AddWithValue("@originalPrice", dto.OriginalPrice); - cmd.Parameters.AddWithValue("@stores", dto.Stores); - cmd.Parameters.AddWithValue("@status", dto.Status); - cmd.Parameters.AddWithValue("@categoryId", (object)dto.CategoryId ?? DBNull.Value); - cmd.Parameters.AddWithValue("@unit", dto.Unit ?? ""); - cmd.Parameters.AddWithValue("@picsJson", picsJson); - cmd.Parameters.AddWithValue("@syncedAt", DateTime.Now); - cmd.ExecuteNonQuery(); - } - - return isNew; - } - }); - } - - /// - /// 上传货品编号到服务端(通过 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/MainViewModel.cs b/PackagingMallShipper/ViewModels/MainViewModel.cs index 9ec106e..c979fa0 100644 --- a/PackagingMallShipper/ViewModels/MainViewModel.cs +++ b/PackagingMallShipper/ViewModels/MainViewModel.cs @@ -15,9 +15,6 @@ namespace PackagingMallShipper.ViewModels [ObservableProperty] private OrderListViewModel _orderListViewModel; - [ObservableProperty] - private ProductListViewModel _productListViewModel; - [ObservableProperty] private int _selectedTabIndex = 0; @@ -25,12 +22,10 @@ namespace PackagingMallShipper.ViewModels public MainViewModel( IAuthService authService, - OrderListViewModel orderListViewModel, - ProductListViewModel productListViewModel) + OrderListViewModel orderListViewModel) { _authService = authService; _orderListViewModel = orderListViewModel; - _productListViewModel = productListViewModel; if (_authService.CurrentSession != null) { diff --git a/PackagingMallShipper/ViewModels/ProductListViewModel.cs b/PackagingMallShipper/ViewModels/ProductListViewModel.cs deleted file mode 100644 index b4cef18..0000000 --- a/PackagingMallShipper/ViewModels/ProductListViewModel.cs +++ /dev/null @@ -1,300 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Threading.Tasks; -using System.Windows; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using PackagingMallShipper.Models; -using PackagingMallShipper.Services; - -namespace PackagingMallShipper.ViewModels -{ - public partial class ProductListViewModel : ViewModelBase - { - private readonly IProductService _productService; - private readonly IProductSyncService _productSyncService; - - [ObservableProperty] - private ObservableCollection _products = new ObservableCollection(); - - [ObservableProperty] - private Product _selectedProduct; - - [ObservableProperty] - private int _selectedStatusIndex = 0; // 0:全部, 1:上架, 2:下架 - - [ObservableProperty] - private string _searchText = ""; - - [ObservableProperty] - private int _totalCount; - - [ObservableProperty] - private int _onSaleCount; - - [ObservableProperty] - private string _syncProgress = ""; - - [ObservableProperty] - private DateTime? _lastSyncTime; - - [ObservableProperty] - private bool _isEditing; - - [ObservableProperty] - private string _editingItemCode = ""; - - public ProductListViewModel(IProductService productService, IProductSyncService productSyncService) - { - _productService = productService; - _productSyncService = productSyncService; - - _productSyncService.OnSyncProgress += (current, total) => - { - SyncProgress = $"同步中 {current}/{total}"; - }; - - _productSyncService.OnSyncMessage += (msg) => - { - StatusMessage = msg; - }; - } - - public async Task InitializeAsync() - { - await RefreshProductsAsync(); - await UpdateCountsAsync(); - } - - [RelayCommand] - private async Task RefreshProductsAsync() - { - IsBusy = true; - StatusMessage = "加载中..."; - - try - { - int? status = SelectedStatusIndex switch - { - 1 => ProductStatus.OnSale, - 2 => ProductStatus.OffSale, - _ => null - }; - - var products = await _productService.GetProductsAsync(status, SearchText); - - Products.Clear(); - foreach (var product in products) - { - Products.Add(product); - } - - TotalCount = products.Count; - StatusMessage = $"共 {TotalCount} 个产品"; - } - catch (Exception ex) - { - StatusMessage = $"加载失败: {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - [RelayCommand] - private async Task SyncProductsAsync() - { - IsBusy = true; - StatusMessage = "正在同步产品..."; - - try - { - var result = await _productSyncService.SyncProductsAsync(SyncMode.Full); - LastSyncTime = DateTime.Now; - await RefreshProductsAsync(); - await UpdateCountsAsync(); - - StatusMessage = $"同步完成!新增 {result.NewCount},更新 {result.UpdatedCount}"; - } - 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 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() - { - await RefreshProductsAsync(); - } - - [RelayCommand] - private void StartEditItemCode() - { - if (SelectedProduct == null) - { - MessageBox.Show("请先选择一个产品", "提示", MessageBoxButton.OK, MessageBoxImage.Information); - return; - } - - EditingItemCode = SelectedProduct.ItemCode ?? ""; - IsEditing = true; - } - - [RelayCommand] - private async Task SaveItemCodeAsync() - { - if (SelectedProduct == null) return; - - try - { - await _productService.UpdateItemCodeAsync(SelectedProduct.Id, EditingItemCode); - SelectedProduct.ItemCode = EditingItemCode; - StatusMessage = "货品编号已更新"; - - IsEditing = false; - EditingItemCode = ""; - - await RefreshProductsAsync(); - } - catch (Exception ex) - { - MessageBox.Show($"更新失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); - } - } - - [RelayCommand] - private void CancelEditItemCode() - { - IsEditing = false; - EditingItemCode = ""; - } - - [RelayCommand] - private async Task DeleteProductAsync() - { - if (SelectedProduct == null) return; - - if (!SelectedProduct.IsLocalOnly) - { - MessageBox.Show("只能删除本地创建的产品,不能删除从API同步的产品", "提示", MessageBoxButton.OK, MessageBoxImage.Information); - return; - } - - var result = MessageBox.Show($"确定要删除产品 [{SelectedProduct.Name}] 吗?", "确认删除", - MessageBoxButton.YesNo, MessageBoxImage.Question); - - if (result != MessageBoxResult.Yes) return; - - try - { - await _productService.DeleteProductAsync(SelectedProduct.Id); - await RefreshProductsAsync(); - await UpdateCountsAsync(); - StatusMessage = "删除成功"; - } - catch (Exception ex) - { - MessageBox.Show($"删除失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); - } - } - - private async Task UpdateCountsAsync() - { - try - { - TotalCount = await _productService.GetProductCountAsync(); - OnSaleCount = await _productService.GetProductCountAsync(ProductStatus.OnSale); - } - catch - { - // Ignore - } - } - - partial void OnSelectedStatusIndexChanged(int value) - { - _ = RefreshProductsAsync(); - } - - partial void OnSearchTextChanged(string value) - { - // 可添加防抖逻辑 - } - } -} diff --git a/PackagingMallShipper/Views/LoginWindow.xaml.cs b/PackagingMallShipper/Views/LoginWindow.xaml.cs index b461484..f4d6385 100644 --- a/PackagingMallShipper/Views/LoginWindow.xaml.cs +++ b/PackagingMallShipper/Views/LoginWindow.xaml.cs @@ -49,14 +49,10 @@ namespace PackagingMallShipper.Views var orderService = new OrderService(); var syncService = new SyncService(_authService); var shipService = new ShipService(_authService); - var productService = new ProductService(); - var excelService = new ExcelService(orderService, shipService, productService); + var excelService = new ExcelService(orderService, shipService); var orderListViewModel = new OrderListViewModel(orderService, syncService, shipService, excelService); - var productSyncService = new ProductSyncService(_authService); - var productListViewModel = new ProductListViewModel(productService, productSyncService); - - var mainViewModel = new MainViewModel(_authService, orderListViewModel, productListViewModel); + var mainViewModel = new MainViewModel(_authService, orderListViewModel); var mainWindow = new MainWindow(mainViewModel); mainViewModel.OnLogout += () => diff --git a/PackagingMallShipper/Views/MainWindow.xaml b/PackagingMallShipper/Views/MainWindow.xaml index a69428c..4a9a95f 100644 --- a/PackagingMallShipper/Views/MainWindow.xaml +++ b/PackagingMallShipper/Views/MainWindow.xaml @@ -5,7 +5,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:PackagingMallShipper.Views" mc:Ignorable="d" - Title="包装商城发货助手" + Title="包装商城发货助手(供应商版)" Height="700" Width="1100" WindowStartupLocation="CenterScreen" MinHeight="500" MinWidth="800"> @@ -39,43 +39,16 @@ - - - - - - - - - + + - - - - - - - - - - + + diff --git a/PackagingMallShipper/Views/MainWindow.xaml.cs b/PackagingMallShipper/Views/MainWindow.xaml.cs index a3d6ae1..681d1eb 100644 --- a/PackagingMallShipper/Views/MainWindow.xaml.cs +++ b/PackagingMallShipper/Views/MainWindow.xaml.cs @@ -13,7 +13,6 @@ namespace PackagingMallShipper.Views Loaded += async (s, e) => { await viewModel.OrderListViewModel.InitializeAsync(); - await viewModel.ProductListViewModel.InitializeAsync(); }; } } diff --git a/PackagingMallShipper/Views/ProductListView.xaml b/PackagingMallShipper/Views/ProductListView.xaml deleted file mode 100644 index 352f799..0000000 --- a/PackagingMallShipper/Views/ProductListView.xaml +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -