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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/PackagingMallShipper/Views/ProductListView.xaml.cs b/PackagingMallShipper/Views/ProductListView.xaml.cs
deleted file mode 100644
index 64cdac6..0000000
--- a/PackagingMallShipper/Views/ProductListView.xaml.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System.Windows.Controls;
-using PackagingMallShipper.ViewModels;
-
-namespace PackagingMallShipper.Views
-{
- public partial class ProductListView : UserControl
- {
- public ProductListView()
- {
- InitializeComponent();
- }
-
- public ProductListView(ProductListViewModel viewModel) : this()
- {
- DataContext = viewModel;
- Loaded += async (s, e) => await viewModel.InitializeAsync();
- }
- }
-}