Compare commits
2 Commits
main
...
supplier-v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c077fa9989 | ||
|
|
4fd9c44ee8 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -48,11 +48,6 @@ installer/
|
|||||||
Thumbs.db
|
Thumbs.db
|
||||||
ehthumbs.db
|
ehthumbs.db
|
||||||
Desktop.ini
|
Desktop.ini
|
||||||
|
|
||||||
# Local secrets / credentials
|
|
||||||
.env
|
|
||||||
.env.local
|
|
||||||
.env.*.local
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# Temp files
|
# Temp files
|
||||||
|
|||||||
@@ -6,9 +6,8 @@
|
|||||||
<appSettings>
|
<appSettings>
|
||||||
<!-- API工厂配置 -->
|
<!-- API工厂配置 -->
|
||||||
<add key="ApiBaseUrl" value="https://api.it120.cc" />
|
<add key="ApiBaseUrl" value="https://api.it120.cc" />
|
||||||
|
<add key="CommonApiBaseUrl" value="https://common.apifm.com" />
|
||||||
<add key="SubDomain" value="let5see" />
|
<add key="SubDomain" value="let5see" />
|
||||||
<!-- 商户ID(int32),用于 /login/operator/v2 子账号登录 -->
|
|
||||||
<add key="MerchantId" value="15073" />
|
|
||||||
|
|
||||||
<!-- 同步配置 -->
|
<!-- 同步配置 -->
|
||||||
<add key="SyncPageSize" value="50" />
|
<add key="SyncPageSize" value="50" />
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ namespace PackagingMallShipper.Helpers
|
|||||||
public static string ApiBaseUrl =>
|
public static string ApiBaseUrl =>
|
||||||
ConfigurationManager.AppSettings["ApiBaseUrl"] ?? "https://user.api.it120.cc";
|
ConfigurationManager.AppSettings["ApiBaseUrl"] ?? "https://user.api.it120.cc";
|
||||||
|
|
||||||
|
public static string CommonApiBaseUrl =>
|
||||||
|
ConfigurationManager.AppSettings["CommonApiBaseUrl"] ?? "https://common.apifm.com";
|
||||||
|
|
||||||
public static string SubDomain =>
|
public static string SubDomain =>
|
||||||
ConfigurationManager.AppSettings["SubDomain"] ?? "vv125s";
|
ConfigurationManager.AppSettings["SubDomain"] ?? "vv125s";
|
||||||
|
|
||||||
public static string MerchantId =>
|
|
||||||
ConfigurationManager.AppSettings["MerchantId"] ?? "";
|
|
||||||
|
|
||||||
public static int SyncPageSize =>
|
public static int SyncPageSize =>
|
||||||
int.TryParse(ConfigurationManager.AppSettings["SyncPageSize"], out var size) ? size : 50;
|
int.TryParse(ConfigurationManager.AppSettings["SyncPageSize"], out var size) ? size : 50;
|
||||||
|
|
||||||
@@ -26,5 +26,10 @@ namespace PackagingMallShipper.Helpers
|
|||||||
{
|
{
|
||||||
return $"{ApiBaseUrl}/{SubDomain}{endpoint}";
|
return $"{ApiBaseUrl}/{SubDomain}{endpoint}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetCommonApiUrl(string endpoint)
|
||||||
|
{
|
||||||
|
return $"{CommonApiBaseUrl}{endpoint}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,365 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace PackagingMallShipper.Models
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 本地产品模型
|
|
||||||
/// </summary>
|
|
||||||
public class Product
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 商品ID(来自API)
|
|
||||||
/// </summary>
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 商品名称
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 条码编号(来自API的barCode)
|
|
||||||
/// </summary>
|
|
||||||
public string BarCode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 外部商品编号(来自API的yyId)
|
|
||||||
/// </summary>
|
|
||||||
public string YyId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地自定义货品编号(用户可编辑)
|
|
||||||
/// 格式如:1222003x12+13222003x24+1322003x12+0000000x12
|
|
||||||
/// </summary>
|
|
||||||
public string ItemCode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 现价
|
|
||||||
/// </summary>
|
|
||||||
public decimal MinPrice { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 原价
|
|
||||||
/// </summary>
|
|
||||||
public decimal OriginalPrice { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 库存
|
|
||||||
/// </summary>
|
|
||||||
public int Stores { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 状态(0:上架, 1:下架)
|
|
||||||
/// </summary>
|
|
||||||
public int Status { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 分类ID
|
|
||||||
/// </summary>
|
|
||||||
public int? CategoryId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 单位
|
|
||||||
/// </summary>
|
|
||||||
public string Unit { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 图片数组JSON
|
|
||||||
/// </summary>
|
|
||||||
public string PicsJson { get; set; }
|
|
||||||
|
|
||||||
// ========== 本地管理字段 ==========
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否为纯本地产品(未同步到远程)
|
|
||||||
/// </summary>
|
|
||||||
public bool IsLocalOnly { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地备注
|
|
||||||
/// </summary>
|
|
||||||
public string LocalNote { get; set; }
|
|
||||||
|
|
||||||
public DateTime? LocalCreatedAt { get; set; }
|
|
||||||
public DateTime? LocalUpdatedAt { get; set; }
|
|
||||||
public DateTime? SyncedAt { get; set; }
|
|
||||||
|
|
||||||
// ========== 扩展价格字段(对应产品报价表)==========
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 自提开票价(元/套)
|
|
||||||
/// </summary>
|
|
||||||
public decimal? PickupPrice { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 快递开票价(元/套)
|
|
||||||
/// </summary>
|
|
||||||
public decimal? ExpressPrice { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 包价格(元/箱)
|
|
||||||
/// </summary>
|
|
||||||
public decimal? PackPrice { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 打包数量(套/箱)
|
|
||||||
/// </summary>
|
|
||||||
public int? PackQuantity { get; set; }
|
|
||||||
|
|
||||||
// ========== 规格参数 ==========
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 产品系列
|
|
||||||
/// </summary>
|
|
||||||
public string ProductSeries { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 放置方式
|
|
||||||
/// </summary>
|
|
||||||
public string PlacementMethod { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 厚度
|
|
||||||
/// </summary>
|
|
||||||
public string Thickness { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 孔径尺寸
|
|
||||||
/// </summary>
|
|
||||||
public string HoleSize { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 纸箱尺寸
|
|
||||||
/// </summary>
|
|
||||||
public string BoxSize { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 适用蛋型
|
|
||||||
/// </summary>
|
|
||||||
public string ApplicableType { get; set; }
|
|
||||||
|
|
||||||
// ========== 计算属性 ==========
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 状态文本
|
|
||||||
/// </summary>
|
|
||||||
public string StatusText => Status == 0 ? "上架" : "下架";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 首张图片URL
|
|
||||||
/// </summary>
|
|
||||||
public string FirstPic
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(PicsJson)) return null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var pics = JsonConvert.DeserializeObject<List<string>>(PicsJson);
|
|
||||||
return pics?.Count > 0 ? pics[0] : null;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否已设置货品编号
|
|
||||||
/// </summary>
|
|
||||||
public bool HasItemCode => !string.IsNullOrWhiteSpace(ItemCode);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 用于UI选择
|
|
||||||
/// </summary>
|
|
||||||
public bool IsSelected { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 产品状态枚举
|
|
||||||
/// </summary>
|
|
||||||
public static class ProductStatus
|
|
||||||
{
|
|
||||||
public const int OnSale = 0; // 上架
|
|
||||||
public const int OffSale = 1; // 下架
|
|
||||||
|
|
||||||
public static string GetStatusText(int status)
|
|
||||||
{
|
|
||||||
return status == OnSale ? "上架" : "下架";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 产品列表API响应数据
|
|
||||||
/// </summary>
|
|
||||||
public class ProductListData
|
|
||||||
{
|
|
||||||
[JsonProperty("result")]
|
|
||||||
public List<ProductDto> Result { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("list")]
|
|
||||||
public List<ProductDto> List { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("totalRow")]
|
|
||||||
public int TotalRow { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("total")]
|
|
||||||
public int Total { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("totalPage")]
|
|
||||||
public int TotalPage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取产品列表(兼容多种返回格式)
|
|
||||||
/// </summary>
|
|
||||||
public List<ProductDto> GetProducts()
|
|
||||||
{
|
|
||||||
return Result ?? List ?? new List<ProductDto>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetTotalRow()
|
|
||||||
{
|
|
||||||
return TotalRow > 0 ? TotalRow : Total;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// API返回的产品DTO
|
|
||||||
/// </summary>
|
|
||||||
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<string> Pics { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 扩展属性JSON(用于存储货品编号等自定义字段)
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("extJson")]
|
|
||||||
public ProductExtJson ExtJson { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取扩展属性中的货品编号
|
|
||||||
/// </summary>
|
|
||||||
public string GetItemCode()
|
|
||||||
{
|
|
||||||
return ExtJson?.ItemCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 转换为本地Product模型
|
|
||||||
/// </summary>
|
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 产品扩展属性(存储在服务端 extJsonStr 字段)
|
|
||||||
/// </summary>
|
|
||||||
public class ProductExtJson
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 货品编号
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("itemCode")]
|
|
||||||
public string ItemCode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地备注(可选同步)
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("localNote")]
|
|
||||||
public string LocalNote { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 序列化为JSON字符串
|
|
||||||
/// </summary>
|
|
||||||
public string ToJsonString()
|
|
||||||
{
|
|
||||||
return JsonConvert.SerializeObject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 从JSON字符串解析
|
|
||||||
/// </summary>
|
|
||||||
public static ProductExtJson FromJsonString(string json)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(json)) return null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return JsonConvert.DeserializeObject<ProductExtJson>(json);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
@@ -11,13 +11,13 @@
|
|||||||
<ApplicationIcon>Resources\Icons\app.ico</ApplicationIcon>
|
<ApplicationIcon>Resources\Icons\app.ico</ApplicationIcon>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
|
||||||
<!-- 鐗堟湰淇℃伅 -->
|
<!-- 版本信息 -->
|
||||||
<Version>1.0.2</Version>
|
<Version>1.0.1</Version>
|
||||||
<FileVersion>1.0.2.0</FileVersion>
|
<FileVersion>1.0.1.0</FileVersion>
|
||||||
<AssemblyVersion>1.0.2.0</AssemblyVersion>
|
<AssemblyVersion>1.0.1.0</AssemblyVersion>
|
||||||
<Company>PackagingMall</Company>
|
<Company>PackagingMall</Company>
|
||||||
<Product>鍖呰鍟嗗煄鍙戣揣鍔╂墜</Product>
|
<Product>包装商城发货助手</Product>
|
||||||
<Copyright>Copyright 漏 2025</Copyright>
|
<Copyright>Copyright © 2025</Copyright>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -14,17 +13,11 @@ namespace PackagingMallShipper.Services
|
|||||||
public class AuthService : IAuthService
|
public class AuthService : IAuthService
|
||||||
{
|
{
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly HttpClientHandler _httpClientHandler;
|
|
||||||
private LocalSession _currentSession;
|
private LocalSession _currentSession;
|
||||||
|
|
||||||
public AuthService()
|
public AuthService()
|
||||||
{
|
{
|
||||||
_httpClientHandler = new HttpClientHandler
|
_httpClient = new HttpClient();
|
||||||
{
|
|
||||||
UseCookies = true,
|
|
||||||
CookieContainer = new CookieContainer()
|
|
||||||
};
|
|
||||||
_httpClient = new HttpClient(_httpClientHandler);
|
|
||||||
_httpClient.Timeout = TimeSpan.FromSeconds(30);
|
_httpClient.Timeout = TimeSpan.FromSeconds(30);
|
||||||
LoadSession();
|
LoadSession();
|
||||||
}
|
}
|
||||||
@@ -33,84 +26,40 @@ namespace PackagingMallShipper.Services
|
|||||||
|
|
||||||
public bool IsLoggedIn => !string.IsNullOrEmpty(GetToken());
|
public bool IsLoggedIn => !string.IsNullOrEmpty(GetToken());
|
||||||
|
|
||||||
// 子账号登录的验证码和登录接口,位于 common.apifm.com
|
|
||||||
private const string OperatorHost = "http://common.apifm.com";
|
|
||||||
private const string LegacyHost = "https://user.api.it120.cc";
|
|
||||||
|
|
||||||
private static string AuthHost =>
|
|
||||||
string.IsNullOrWhiteSpace(AppConfig.MerchantId) ? LegacyHost : OperatorHost;
|
|
||||||
|
|
||||||
public string GetCaptchaUrl(string key)
|
public string GetCaptchaUrl(string key)
|
||||||
{
|
{
|
||||||
return $"{AuthHost}/code?k={Uri.EscapeDataString(key)}";
|
return $"https://user.api.it120.cc/code?k={Uri.EscapeDataString(key)}";
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<byte[]> GetCaptchaImageAsync(string key)
|
|
||||||
{
|
|
||||||
var url = GetCaptchaUrl(key);
|
|
||||||
Debug.WriteLine($"[验证码请求] URL: {url}");
|
|
||||||
using (var response = await _httpClient.GetAsync(url))
|
|
||||||
{
|
|
||||||
Debug.WriteLine($"[验证码响应] HTTP状态码: {response.StatusCode}");
|
|
||||||
if (response.Headers.TryGetValues("Set-Cookie", out var setCookieValues))
|
|
||||||
{
|
|
||||||
foreach (var item in setCookieValues)
|
|
||||||
{
|
|
||||||
Debug.WriteLine($"[验证码响应] Set-Cookie: {item}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var cookieCount = _httpClientHandler.CookieContainer.GetCookies(new Uri(AuthHost)).Count;
|
|
||||||
Debug.WriteLine($"[验证码响应] 当前会话Cookie数量: {cookieCount}");
|
|
||||||
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
var data = await response.Content.ReadAsByteArrayAsync();
|
|
||||||
Debug.WriteLine($"[验证码响应] 图片字节数: {data?.Length ?? 0}");
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<LoginResult> LoginAsync(string userName, string password, string captcha = null, string captchaKey = null)
|
public async Task<LoginResult> LoginAsync(string userName, string password, string captcha = null, string captchaKey = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 优先使用 /login/operator/v2(商户号登录),要求配置 MerchantId
|
// 使用子账号登录接口 /login/userName/v2
|
||||||
// 否则回退 /login/userName/v2(专属域名登录)
|
// 注意:该接口会优先判断手机号码登录,如果满足直接登录成功,其次才会尝试子账号登录
|
||||||
var useOperator = !string.IsNullOrWhiteSpace(AppConfig.MerchantId);
|
// 该接口要求必须提供验证码参数
|
||||||
string url;
|
var urlBuilder = new System.Text.StringBuilder();
|
||||||
var form = new System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string, string>>
|
urlBuilder.Append("https://user.api.it120.cc/login/userName/v2");
|
||||||
{
|
urlBuilder.Append($"?userName={Uri.EscapeDataString(userName)}");
|
||||||
new System.Collections.Generic.KeyValuePair<string, string>("userName", userName),
|
urlBuilder.Append($"&pwd={Uri.EscapeDataString(password)}");
|
||||||
new System.Collections.Generic.KeyValuePair<string, string>("pwd", password),
|
urlBuilder.Append($"&pdomain={Uri.EscapeDataString(AppConfig.SubDomain)}");
|
||||||
new System.Collections.Generic.KeyValuePair<string, string>("rememberMe", "true"),
|
urlBuilder.Append("&rememberMe=true");
|
||||||
new System.Collections.Generic.KeyValuePair<string, string>("imgcode", captcha ?? ""),
|
|
||||||
new System.Collections.Generic.KeyValuePair<string, string>("k", captchaKey ?? "")
|
|
||||||
};
|
|
||||||
if (useOperator)
|
|
||||||
{
|
|
||||||
// 商户号子账号登录,走 common.apifm.com/loginAdmin/operator/v2
|
|
||||||
url = $"{OperatorHost}/loginAdmin/operator/v2";
|
|
||||||
form.Add(new System.Collections.Generic.KeyValuePair<string, string>("merchantId", AppConfig.MerchantId));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
url = $"{LegacyHost}/login/userName/v2";
|
|
||||||
form.Add(new System.Collections.Generic.KeyValuePair<string, string>("pdomain", AppConfig.SubDomain));
|
|
||||||
}
|
|
||||||
|
|
||||||
System.Diagnostics.Debug.WriteLine($"[登录请求] URL: {url}");
|
// 验证码参数是必须的
|
||||||
|
urlBuilder.Append($"&imgcode={Uri.EscapeDataString(captcha ?? "")}");
|
||||||
|
urlBuilder.Append($"&k={Uri.EscapeDataString(captchaKey ?? "")}");
|
||||||
|
|
||||||
|
var url = urlBuilder.ToString();
|
||||||
|
|
||||||
|
// 详细日志:请求URL(隐藏密码)
|
||||||
|
var logUrl = url.Replace($"&pwd={Uri.EscapeDataString(password)}", "&pwd=***");
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[登录请求] URL: {logUrl}");
|
||||||
System.Diagnostics.Debug.WriteLine($"[登录请求] 用户名: {userName}");
|
System.Diagnostics.Debug.WriteLine($"[登录请求] 用户名: {userName}");
|
||||||
System.Diagnostics.Debug.WriteLine($"[登录请求] 验证码: {captcha}");
|
System.Diagnostics.Debug.WriteLine($"[登录请求] 验证码: {captcha}");
|
||||||
System.Diagnostics.Debug.WriteLine($"[登录请求] 验证码Key: {captchaKey}");
|
System.Diagnostics.Debug.WriteLine($"[登录请求] 验证码Key: {captchaKey}");
|
||||||
if (useOperator)
|
System.Diagnostics.Debug.WriteLine($"[登录请求] 子域名: {AppConfig.SubDomain}");
|
||||||
System.Diagnostics.Debug.WriteLine($"[登录请求] 商户ID: {AppConfig.MerchantId}");
|
|
||||||
else
|
|
||||||
System.Diagnostics.Debug.WriteLine($"[登录请求] 子域名: {AppConfig.SubDomain}");
|
|
||||||
var cookieCount = _httpClientHandler.CookieContainer.GetCookies(new Uri(AuthHost)).Count;
|
|
||||||
System.Diagnostics.Debug.WriteLine($"[登录请求] 当前会话Cookie数量: {cookieCount}");
|
|
||||||
|
|
||||||
var content = new FormUrlEncodedContent(form);
|
var response = await _httpClient.PostAsync(url, null);
|
||||||
var response = await _httpClient.PostAsync(url, content);
|
|
||||||
var json = await response.Content.ReadAsStringAsync();
|
var json = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
// 详细日志:API响应
|
// 详细日志:API响应
|
||||||
@@ -124,10 +73,6 @@ namespace PackagingMallShipper.Services
|
|||||||
if (result?.Code != 0)
|
if (result?.Code != 0)
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"[登录失败] 错误码: {result?.Code}, 错误信息: {result?.Msg}");
|
System.Diagnostics.Debug.WriteLine($"[登录失败] 错误码: {result?.Code}, 错误信息: {result?.Msg}");
|
||||||
if (result?.Code == 300)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.WriteLine("[登录失败] 服务端判定为验证码错误,重点核对验证码会话(Cookie)、验证码Key、输入值");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LoginResult
|
return new LoginResult
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,25 +12,17 @@ namespace PackagingMallShipper.Services
|
|||||||
{
|
{
|
||||||
private readonly IOrderService _orderService;
|
private readonly IOrderService _orderService;
|
||||||
private readonly IShipService _shipService;
|
private readonly IShipService _shipService;
|
||||||
private readonly IProductService _productService;
|
|
||||||
|
|
||||||
public ExcelService(IOrderService orderService, IShipService shipService, IProductService productService)
|
public ExcelService(IOrderService orderService, IShipService shipService)
|
||||||
{
|
{
|
||||||
_orderService = orderService;
|
_orderService = orderService;
|
||||||
_shipService = shipService;
|
_shipService = shipService;
|
||||||
_productService = productService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> ExportPendingOrdersAsync(string filePath)
|
public async Task<int> ExportPendingOrdersAsync(string filePath)
|
||||||
{
|
{
|
||||||
var orders = await _orderService.GetOrdersAsync(status: 1);
|
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())
|
using (var workbook = new XLWorkbook())
|
||||||
{
|
{
|
||||||
var worksheet = workbook.Worksheets.Add("待发货订单");
|
var worksheet = workbook.Worksheets.Add("待发货订单");
|
||||||
@@ -39,7 +31,7 @@ namespace PackagingMallShipper.Services
|
|||||||
{
|
{
|
||||||
"订单号", "下单时间", "收件人", "联系电话",
|
"订单号", "下单时间", "收件人", "联系电话",
|
||||||
"省份", "城市", "区县", "收货地址",
|
"省份", "城市", "区县", "收货地址",
|
||||||
"商品信息", "货品编号", "数量", "快递公司", "快递单号"
|
"商品信息", "数量", "快递公司", "快递单号"
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < headers.Length; i++)
|
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, 7).Value = order.LogisticsDistrict;
|
||||||
worksheet.Cell(row, 8).Value = order.FullAddress;
|
worksheet.Cell(row, 8).Value = order.FullAddress;
|
||||||
worksheet.Cell(row, 9).Value = order.GoodsInfo;
|
worksheet.Cell(row, 9).Value = order.GoodsInfo;
|
||||||
worksheet.Cell(row, 10).Value = order.GetItemCodesInfo(itemCodeMap);
|
worksheet.Cell(row, 10).Value = order.TotalQuantity;
|
||||||
worksheet.Cell(row, 11).Value = order.TotalQuantity;
|
worksheet.Cell(row, 11).Value = "";
|
||||||
worksheet.Cell(row, 12).Value = "";
|
worksheet.Cell(row, 12).Value = "";
|
||||||
worksheet.Cell(row, 13).Value = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
worksheet.Column(1).Width = 20;
|
worksheet.Column(1).Width = 20;
|
||||||
@@ -76,8 +67,7 @@ namespace PackagingMallShipper.Services
|
|||||||
worksheet.Column(4).Width = 15;
|
worksheet.Column(4).Width = 15;
|
||||||
worksheet.Column(8).Width = 50;
|
worksheet.Column(8).Width = 50;
|
||||||
worksheet.Column(9).Width = 30;
|
worksheet.Column(9).Width = 30;
|
||||||
worksheet.Column(10).Width = 40;
|
worksheet.Column(12).Width = 20;
|
||||||
worksheet.Column(13).Width = 20;
|
|
||||||
|
|
||||||
worksheet.SheetView.FreezeRows(1);
|
worksheet.SheetView.FreezeRows(1);
|
||||||
|
|
||||||
@@ -102,8 +92,8 @@ namespace PackagingMallShipper.Services
|
|||||||
foreach (var row in rows)
|
foreach (var row in rows)
|
||||||
{
|
{
|
||||||
var orderNumber = row.Cell(1).GetString().Trim();
|
var orderNumber = row.Cell(1).GetString().Trim();
|
||||||
var expressCompanyName = row.Cell(12).GetString().Trim();
|
var expressCompanyName = row.Cell(11).GetString().Trim();
|
||||||
var trackingNumber = row.Cell(13).GetString().Trim();
|
var trackingNumber = row.Cell(12).GetString().Trim();
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(orderNumber))
|
if (string.IsNullOrEmpty(orderNumber))
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ namespace PackagingMallShipper.Services
|
|||||||
{
|
{
|
||||||
Task<LoginResult> LoginAsync(string userName, string password, string captcha = null, string captchaKey = null);
|
Task<LoginResult> LoginAsync(string userName, string password, string captcha = null, string captchaKey = null);
|
||||||
string GetCaptchaUrl(string key);
|
string GetCaptchaUrl(string key);
|
||||||
Task<byte[]> GetCaptchaImageAsync(string key);
|
|
||||||
string GetToken();
|
string GetToken();
|
||||||
bool IsLoggedIn { get; }
|
bool IsLoggedIn { get; }
|
||||||
LocalSession CurrentSession { get; }
|
LocalSession CurrentSession { get; }
|
||||||
@@ -48,36 +47,4 @@ namespace PackagingMallShipper.Services
|
|||||||
Task<ImportShipResult> ImportAndShipAsync(string filePath);
|
Task<ImportShipResult> ImportAndShipAsync(string filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IProductService
|
|
||||||
{
|
|
||||||
Task<List<Product>> GetProductsAsync(int? status = null, string keyword = null);
|
|
||||||
Task<Product> GetProductByIdAsync(int productId);
|
|
||||||
Task<Product> GetProductByItemCodeAsync(string itemCode);
|
|
||||||
Task<Product> GetProductByBarCodeAsync(string barCode);
|
|
||||||
Task<bool> SaveProductAsync(Product product);
|
|
||||||
Task<bool> DeleteProductAsync(int productId);
|
|
||||||
Task<bool> UpdateItemCodeAsync(int productId, string itemCode);
|
|
||||||
Task<int> GetProductCountAsync(int? status = null);
|
|
||||||
Task<int> BatchUpdateItemCodesAsync(Dictionary<int, string> productItemCodes);
|
|
||||||
Task<Dictionary<int, string>> GetItemCodesByGoodsIdsAsync(List<int> goodsIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IProductSyncService
|
|
||||||
{
|
|
||||||
Task<SyncResult> SyncProductsAsync(SyncMode mode = SyncMode.Incremental);
|
|
||||||
Task<UploadResult> UploadItemCodesToServerAsync(List<Product> products);
|
|
||||||
event Action<int, int> OnSyncProgress;
|
|
||||||
event Action<string> OnSyncMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 上传结果
|
|
||||||
/// </summary>
|
|
||||||
public class UploadResult
|
|
||||||
{
|
|
||||||
public int TotalCount { get; set; }
|
|
||||||
public int SuccessCount { get; set; }
|
|
||||||
public int FailedCount { get; set; }
|
|
||||||
public List<string> Errors { get; set; } = new List<string>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<List<Product>> GetProductsAsync(int? status = null, string keyword = null)
|
|
||||||
{
|
|
||||||
return Task.Run(() =>
|
|
||||||
{
|
|
||||||
var products = new List<Product>();
|
|
||||||
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<Product> 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<Product> 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<Product> 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<bool> 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<bool> 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<bool> 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<int> 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<int> BatchUpdateItemCodesAsync(Dictionary<int, string> 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<Dictionary<int, string>> GetItemCodesByGoodsIdsAsync(List<int> goodsIds)
|
|
||||||
{
|
|
||||||
return Task.Run(() =>
|
|
||||||
{
|
|
||||||
var result = new Dictionary<int, string>();
|
|
||||||
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")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<int, int> OnSyncProgress;
|
|
||||||
public event Action<string> OnSyncMessage;
|
|
||||||
|
|
||||||
public ProductSyncService(IAuthService authService)
|
|
||||||
{
|
|
||||||
_authService = authService;
|
|
||||||
_httpClient = new HttpClient();
|
|
||||||
_httpClient.Timeout = TimeSpan.FromSeconds(60);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<SyncResult> 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<ApiResponse<ProductListData>>(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<ProductDto>();
|
|
||||||
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<bool> 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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 上传货品编号到服务端(通过 extJsonStr 字段)
|
|
||||||
/// </summary>
|
|
||||||
public async Task<UploadResult> UploadItemCodesToServerAsync(List<Product> 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<string, string>("id", product.Id.ToString()),
|
|
||||||
new KeyValuePair<string, string>("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<ApiResponse<object>>(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -22,37 +23,38 @@ namespace PackagingMallShipper.Services
|
|||||||
public ShipService(IAuthService authService)
|
public ShipService(IAuthService authService)
|
||||||
{
|
{
|
||||||
_authService = authService;
|
_authService = authService;
|
||||||
_httpClient = new HttpClient();
|
_httpClient = new HttpClient
|
||||||
_httpClient.Timeout = TimeSpan.FromSeconds(30);
|
{
|
||||||
|
Timeout = TimeSpan.FromSeconds(30)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ShipResult> ShipOrderAsync(ShipOrderRequest request)
|
public async Task<ShipResult> ShipOrderAsync(ShipOrderRequest request)
|
||||||
{
|
{
|
||||||
var token = _authService.GetToken();
|
var token = _authService.GetToken();
|
||||||
if (string.IsNullOrEmpty(token))
|
if (string.IsNullOrEmpty(token))
|
||||||
throw new UnauthorizedAccessException("未登录");
|
throw new UnauthorizedAccessException("未登录,请先登录");
|
||||||
|
|
||||||
UpdateLocalOrderStatus(request.OrderId, "shipping");
|
UpdateLocalOrderStatus(request.OrderId, "shipping");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var url = AppConfig.GetApiUrl($"/order/delivery") +
|
var query = $"?id={request.OrderId}" +
|
||||||
$"?orderId={request.OrderId}" +
|
$"&expressCompanyId={request.ExpressCompanyId}" +
|
||||||
$"&expressType={request.ExpressCompanyId}" +
|
$"&number={Uri.EscapeDataString(request.TrackingNumber)}";
|
||||||
$"&shipperCode={Uri.EscapeDataString(request.TrackingNumber)}";
|
|
||||||
|
var url = AppConfig.GetCommonApiUrl("/apifmUser/fsmRepair/fahuo") + query;
|
||||||
|
|
||||||
_httpClient.DefaultRequestHeaders.Clear();
|
_httpClient.DefaultRequestHeaders.Clear();
|
||||||
_httpClient.DefaultRequestHeaders.Add("X-Token", token);
|
_httpClient.DefaultRequestHeaders.Add("X-Token", token);
|
||||||
|
|
||||||
var response = await _httpClient.PostAsync(url, null);
|
Debug.WriteLine($"[发货请求] URL: {url}");
|
||||||
var json = await response.Content.ReadAsStringAsync();
|
var result = await PostShipRequestAsync(url);
|
||||||
var result = JsonConvert.DeserializeObject<ApiResponse<object>>(json);
|
|
||||||
|
|
||||||
if (result?.Code != 0)
|
if (result?.Code != 0)
|
||||||
throw new Exception(result?.Msg ?? "发货失败");
|
throw new Exception(result?.Msg ?? "发货失败");
|
||||||
|
|
||||||
UpdateLocalOrderAfterShip(request);
|
UpdateLocalOrderAfterShip(request);
|
||||||
|
|
||||||
return new ShipResult { Success = true };
|
return new ShipResult { Success = true };
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -73,7 +75,7 @@ namespace PackagingMallShipper.Services
|
|||||||
Results = new List<ShipOrderResult>()
|
Results = new List<ShipOrderResult>()
|
||||||
};
|
};
|
||||||
|
|
||||||
int completed = 0;
|
var completed = 0;
|
||||||
|
|
||||||
using (var semaphore = new SemaphoreSlim(concurrency))
|
using (var semaphore = new SemaphoreSlim(concurrency))
|
||||||
{
|
{
|
||||||
@@ -130,6 +132,15 @@ namespace PackagingMallShipper.Services
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<ApiResponse<object>> PostShipRequestAsync(string url)
|
||||||
|
{
|
||||||
|
var response = await _httpClient.PostAsync(url, null);
|
||||||
|
var json = await response.Content.ReadAsStringAsync();
|
||||||
|
Debug.WriteLine($"[发货响应] URL: {url}");
|
||||||
|
Debug.WriteLine($"[发货响应] HTTP: {response.StatusCode}, body: {json}");
|
||||||
|
return JsonConvert.DeserializeObject<ApiResponse<object>>(json);
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateLocalOrderStatus(int orderId, string status, string errorMsg = null)
|
private void UpdateLocalOrderStatus(int orderId, string status, string errorMsg = null)
|
||||||
{
|
{
|
||||||
using (var conn = SqliteHelper.GetConnection())
|
using (var conn = SqliteHelper.GetConnection())
|
||||||
@@ -165,8 +176,7 @@ namespace PackagingMallShipper.Services
|
|||||||
using (var cmd = new SQLiteCommand(sql, conn))
|
using (var cmd = new SQLiteCommand(sql, conn))
|
||||||
{
|
{
|
||||||
cmd.Parameters.AddWithValue("@expressId", request.ExpressCompanyId);
|
cmd.Parameters.AddWithValue("@expressId", request.ExpressCompanyId);
|
||||||
cmd.Parameters.AddWithValue("@expressName",
|
cmd.Parameters.AddWithValue("@expressName", ExpressCompanies.GetName(request.ExpressCompanyId));
|
||||||
ExpressCompanies.GetName(request.ExpressCompanyId));
|
|
||||||
cmd.Parameters.AddWithValue("@trackingNumber", request.TrackingNumber);
|
cmd.Parameters.AddWithValue("@trackingNumber", request.TrackingNumber);
|
||||||
cmd.Parameters.AddWithValue("@dateShip", DateTime.Now);
|
cmd.Parameters.AddWithValue("@dateShip", DateTime.Now);
|
||||||
cmd.Parameters.AddWithValue("@now", DateTime.Now);
|
cmd.Parameters.AddWithValue("@now", DateTime.Now);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using System;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.IO;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using PackagingMallShipper.Services;
|
using PackagingMallShipper.Services;
|
||||||
@@ -44,34 +43,21 @@ namespace PackagingMallShipper.ViewModels
|
|||||||
|
|
||||||
// 子账号登录必须提供验证码,默认显示
|
// 子账号登录必须提供验证码,默认显示
|
||||||
ShowCaptcha = true;
|
ShowCaptcha = true;
|
||||||
_ = RefreshCaptchaAsync();
|
RefreshCaptcha();
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async Task RefreshCaptchaAsync()
|
public void RefreshCaptcha()
|
||||||
{
|
{
|
||||||
try
|
_captchaKey = Guid.NewGuid().ToString("N");
|
||||||
{
|
var url = _authService.GetCaptchaUrl(_captchaKey);
|
||||||
// 采用 JS Math.random 风格 0.xxx 作为 k,与服务端示例一致
|
|
||||||
var rnd = new Random();
|
|
||||||
_captchaKey = "0." + rnd.NextDouble().ToString("F18", System.Globalization.CultureInfo.InvariantCulture).Split('.')[1];
|
|
||||||
var imageBytes = await _authService.GetCaptchaImageAsync(_captchaKey);
|
|
||||||
|
|
||||||
using (var ms = new MemoryStream(imageBytes))
|
CaptchaImageSource = new BitmapImage();
|
||||||
{
|
CaptchaImageSource.BeginInit();
|
||||||
var bitmap = new BitmapImage();
|
CaptchaImageSource.UriSource = new Uri(url);
|
||||||
bitmap.BeginInit();
|
CaptchaImageSource.CacheOption = BitmapCacheOption.None;
|
||||||
bitmap.CacheOption = BitmapCacheOption.OnLoad;
|
CaptchaImageSource.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
|
||||||
bitmap.StreamSource = ms;
|
CaptchaImageSource.EndInit();
|
||||||
bitmap.EndInit();
|
|
||||||
bitmap.Freeze();
|
|
||||||
CaptchaImageSource = bitmap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
ErrorMessage = $"验证码加载失败: {ex.Message}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@@ -100,12 +86,10 @@ namespace PackagingMallShipper.ViewModels
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var normalizedCaptcha = (Captcha ?? string.Empty).Trim().Replace(" ", string.Empty);
|
|
||||||
|
|
||||||
var result = await _authService.LoginAsync(
|
var result = await _authService.LoginAsync(
|
||||||
UserName,
|
UserName,
|
||||||
Password,
|
Password,
|
||||||
ShowCaptcha ? normalizedCaptcha : null,
|
ShowCaptcha ? Captcha : null,
|
||||||
ShowCaptcha ? _captchaKey : null
|
ShowCaptcha ? _captchaKey : null
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -125,13 +109,13 @@ namespace PackagingMallShipper.ViewModels
|
|||||||
if (result.RequireCaptcha && !ShowCaptcha)
|
if (result.RequireCaptcha && !ShowCaptcha)
|
||||||
{
|
{
|
||||||
ShowCaptcha = true;
|
ShowCaptcha = true;
|
||||||
await RefreshCaptchaAsync();
|
RefreshCaptcha();
|
||||||
}
|
}
|
||||||
else if (ShowCaptcha)
|
else if (ShowCaptcha)
|
||||||
{
|
{
|
||||||
// 验证码错误,刷新验证码
|
// 验证码错误,刷新验证码
|
||||||
Captcha = "";
|
Captcha = "";
|
||||||
await RefreshCaptchaAsync();
|
RefreshCaptcha();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ namespace PackagingMallShipper.ViewModels
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private OrderListViewModel _orderListViewModel;
|
private OrderListViewModel _orderListViewModel;
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private ProductListViewModel _productListViewModel;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private int _selectedTabIndex = 0;
|
private int _selectedTabIndex = 0;
|
||||||
|
|
||||||
@@ -25,12 +22,10 @@ namespace PackagingMallShipper.ViewModels
|
|||||||
|
|
||||||
public MainViewModel(
|
public MainViewModel(
|
||||||
IAuthService authService,
|
IAuthService authService,
|
||||||
OrderListViewModel orderListViewModel,
|
OrderListViewModel orderListViewModel)
|
||||||
ProductListViewModel productListViewModel)
|
|
||||||
{
|
{
|
||||||
_authService = authService;
|
_authService = authService;
|
||||||
_orderListViewModel = orderListViewModel;
|
_orderListViewModel = orderListViewModel;
|
||||||
_productListViewModel = productListViewModel;
|
|
||||||
|
|
||||||
if (_authService.CurrentSession != null)
|
if (_authService.CurrentSession != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Microsoft.Win32;
|
|||||||
using PackagingMallShipper.Helpers;
|
using PackagingMallShipper.Helpers;
|
||||||
using PackagingMallShipper.Models;
|
using PackagingMallShipper.Models;
|
||||||
using PackagingMallShipper.Services;
|
using PackagingMallShipper.Services;
|
||||||
|
using PackagingMallShipper.Views;
|
||||||
|
|
||||||
namespace PackagingMallShipper.ViewModels
|
namespace PackagingMallShipper.ViewModels
|
||||||
{
|
{
|
||||||
@@ -633,6 +634,57 @@ namespace PackagingMallShipper.ViewModels
|
|||||||
"提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
"提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ShipSingleOrder(Order order)
|
||||||
|
{
|
||||||
|
if (order == null || order.Status != 1) return;
|
||||||
|
|
||||||
|
var dialog = new ShippingDialog(order);
|
||||||
|
var activeWindow = Application.Current.Windows.OfType<Window>().FirstOrDefault(w => w.IsActive)
|
||||||
|
?? Application.Current.Windows.OfType<Window>().FirstOrDefault(w => w.IsVisible);
|
||||||
|
if (activeWindow != null)
|
||||||
|
dialog.Owner = activeWindow;
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
StatusMessage = $"正在发货 {order.OrderNumber}...";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new ShipOrderRequest
|
||||||
|
{
|
||||||
|
OrderId = order.Id,
|
||||||
|
OrderNumber = order.OrderNumber,
|
||||||
|
ExpressCompanyId = dialog.SelectedExpressId,
|
||||||
|
TrackingNumber = dialog.TrackingNumber
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _shipService.ShipOrderAsync(request);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
StatusMessage = $"订单 {order.OrderNumber} 发货成功";
|
||||||
|
await RefreshOrdersAsync();
|
||||||
|
await UpdateCountsAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StatusMessage = $"发货失败: {result.Message}";
|
||||||
|
MessageBox.Show($"发货失败: {result.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StatusMessage = $"发货失败: {ex.Message}";
|
||||||
|
MessageBox.Show($"发货失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task UpdateCountsAsync()
|
private async Task UpdateCountsAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -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<Product> _products = new ObservableCollection<Product>();
|
|
||||||
|
|
||||||
[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<Product>();
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
// 可添加防抖逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -50,8 +50,6 @@
|
|||||||
<TextBox x:Name="CaptchaTextBox"
|
<TextBox x:Name="CaptchaTextBox"
|
||||||
Text="{Binding Captcha, UpdateSourceTrigger=PropertyChanged}"
|
Text="{Binding Captcha, UpdateSourceTrigger=PropertyChanged}"
|
||||||
Height="35" FontSize="14" Padding="10,5"
|
Height="35" FontSize="14" Padding="10,5"
|
||||||
MaxLength="8"
|
|
||||||
InputMethod.IsInputMethodEnabled="False"
|
|
||||||
Grid.Column="0" Margin="0,0,10,0"/>
|
Grid.Column="0" Margin="0,0,10,0"/>
|
||||||
|
|
||||||
<Border Grid.Column="1" BorderBrush="#CCCCCC" BorderThickness="1" CornerRadius="2">
|
<Border Grid.Column="1" BorderBrush="#CCCCCC" BorderThickness="1" CornerRadius="2">
|
||||||
|
|||||||
@@ -49,14 +49,10 @@ namespace PackagingMallShipper.Views
|
|||||||
var orderService = new OrderService();
|
var orderService = new OrderService();
|
||||||
var syncService = new SyncService(_authService);
|
var syncService = new SyncService(_authService);
|
||||||
var shipService = new ShipService(_authService);
|
var shipService = new ShipService(_authService);
|
||||||
var productService = new ProductService();
|
var excelService = new ExcelService(orderService, shipService);
|
||||||
var excelService = new ExcelService(orderService, shipService, productService);
|
|
||||||
var orderListViewModel = new OrderListViewModel(orderService, syncService, shipService, excelService);
|
var orderListViewModel = new OrderListViewModel(orderService, syncService, shipService, excelService);
|
||||||
|
|
||||||
var productSyncService = new ProductSyncService(_authService);
|
var mainViewModel = new MainViewModel(_authService, orderListViewModel);
|
||||||
var productListViewModel = new ProductListViewModel(productService, productSyncService);
|
|
||||||
|
|
||||||
var mainViewModel = new MainViewModel(_authService, orderListViewModel, productListViewModel);
|
|
||||||
|
|
||||||
var mainWindow = new MainWindow(mainViewModel);
|
var mainWindow = new MainWindow(mainViewModel);
|
||||||
mainViewModel.OnLogout += () =>
|
mainViewModel.OnLogout += () =>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:local="clr-namespace:PackagingMallShipper.Views"
|
xmlns:local="clr-namespace:PackagingMallShipper.Views"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="包装商城发货助手"
|
Title="包装商城发货助手(供应商版)"
|
||||||
Height="700" Width="1100"
|
Height="700" Width="1100"
|
||||||
WindowStartupLocation="CenterScreen"
|
WindowStartupLocation="CenterScreen"
|
||||||
MinHeight="500" MinWidth="800">
|
MinHeight="500" MinWidth="800">
|
||||||
@@ -39,43 +39,16 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- 主内容区 - TabControl -->
|
<!-- 主内容区 -->
|
||||||
<TabControl Grid.Row="1" SelectedIndex="{Binding SelectedTabIndex}">
|
<local:OrderListView Grid.Row="1" DataContext="{Binding OrderListViewModel}"/>
|
||||||
<TabItem Header="订单管理">
|
|
||||||
<local:OrderListView DataContext="{Binding OrderListViewModel}"/>
|
|
||||||
</TabItem>
|
|
||||||
<TabItem Header="产品管理">
|
|
||||||
<local:ProductListView DataContext="{Binding ProductListViewModel}"/>
|
|
||||||
</TabItem>
|
|
||||||
</TabControl>
|
|
||||||
|
|
||||||
<!-- 状态栏 -->
|
<!-- 状态栏 -->
|
||||||
<Border Grid.Row="2" Background="#F0F0F0">
|
<Border Grid.Row="2" Background="#F0F0F0">
|
||||||
<Grid Margin="10,0">
|
<Grid Margin="10,0">
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
<TextBlock VerticalAlignment="Center" FontSize="12"
|
||||||
<TextBlock.Style>
|
Text="{Binding OrderListViewModel.StatusMessage}"/>
|
||||||
<Style TargetType="TextBlock">
|
<TextBlock HorizontalAlignment="Right" VerticalAlignment="Center" FontSize="12"
|
||||||
<Setter Property="Text" Value="{Binding OrderListViewModel.StatusMessage}"/>
|
Text="{Binding OrderListViewModel.SyncProgress}"/>
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding SelectedTabIndex}" Value="1">
|
|
||||||
<Setter Property="Text" Value="{Binding ProductListViewModel.StatusMessage}"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</TextBlock.Style>
|
|
||||||
</TextBlock>
|
|
||||||
<TextBlock HorizontalAlignment="Right" VerticalAlignment="Center" FontSize="12">
|
|
||||||
<TextBlock.Style>
|
|
||||||
<Style TargetType="TextBlock">
|
|
||||||
<Setter Property="Text" Value="{Binding OrderListViewModel.SyncProgress}"/>
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding SelectedTabIndex}" Value="1">
|
|
||||||
<Setter Property="Text" Value="{Binding ProductListViewModel.SyncProgress}"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</TextBlock.Style>
|
|
||||||
</TextBlock>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ namespace PackagingMallShipper.Views
|
|||||||
Loaded += async (s, e) =>
|
Loaded += async (s, e) =>
|
||||||
{
|
{
|
||||||
await viewModel.OrderListViewModel.InitializeAsync();
|
await viewModel.OrderListViewModel.InitializeAsync();
|
||||||
await viewModel.ProductListViewModel.InitializeAsync();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,12 +100,10 @@
|
|||||||
Width="90" Height="30" Margin="0,0,5,0"
|
Width="90" Height="30" Margin="0,0,5,0"
|
||||||
IsEnabled="{Binding IsBusy, Converter={StaticResource InverseBool}}"/>
|
IsEnabled="{Binding IsBusy, Converter={StaticResource InverseBool}}"/>
|
||||||
|
|
||||||
<!-- 暂时禁用导入发货功能
|
|
||||||
<Button Content="📥 导入发货" Command="{Binding ImportAndShipCommand}"
|
<Button Content="📥 导入发货" Command="{Binding ImportAndShipCommand}"
|
||||||
Width="90" Height="30"
|
Width="90" Height="30"
|
||||||
Background="#52C41A" Foreground="White" BorderThickness="0"
|
Background="#52C41A" Foreground="White" BorderThickness="0"
|
||||||
IsEnabled="{Binding IsBusy, Converter={StaticResource InverseBool}}"/>
|
IsEnabled="{Binding IsBusy, Converter={StaticResource InverseBool}}"/>
|
||||||
-->
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- 订单列表 -->
|
<!-- 订单列表 -->
|
||||||
@@ -163,6 +161,37 @@
|
|||||||
Width="70"/>
|
Width="70"/>
|
||||||
|
|
||||||
<DataGridTextColumn Header="快递单号" Binding="{Binding TrackingNumber}" Width="150"/>
|
<DataGridTextColumn Header="快递单号" Binding="{Binding TrackingNumber}" Width="150"/>
|
||||||
|
|
||||||
|
<DataGridTemplateColumn Header="操作" Width="80">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Button Content="发货"
|
||||||
|
Command="{Binding DataContext.ShipSingleOrderCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
|
||||||
|
CommandParameter="{Binding}"
|
||||||
|
Padding="8,2" Cursor="Hand"
|
||||||
|
Background="#1890FF" Foreground="White" BorderThickness="0">
|
||||||
|
<Button.Style>
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Status}" Value="2">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Status}" Value="3">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Status}" Value="4">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Status}" Value="-1">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Button.Style>
|
||||||
|
</Button>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
</DataGrid.Columns>
|
</DataGrid.Columns>
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
|
|
||||||
|
|||||||
@@ -1,206 +0,0 @@
|
|||||||
<UserControl x:Class="PackagingMallShipper.Views.ProductListView"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
Background="White">
|
|
||||||
|
|
||||||
<Grid Margin="15">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<!-- 统计信息 -->
|
|
||||||
<Border Grid.Row="0" Background="#F5F7FA" CornerRadius="4" Padding="15" Margin="0,0,0,15">
|
|
||||||
<Grid>
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
|
||||||
<StackPanel Margin="0,0,40,0">
|
|
||||||
<TextBlock Text="产品总数" FontSize="12" Foreground="#666"/>
|
|
||||||
<TextBlock Text="{Binding TotalCount}" FontSize="24" FontWeight="Bold" Foreground="#1890FF"/>
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Margin="0,0,40,0">
|
|
||||||
<TextBlock Text="上架中" FontSize="12" Foreground="#666"/>
|
|
||||||
<TextBlock Text="{Binding OnSaleCount}" FontSize="24" FontWeight="Bold" Foreground="#52C41A"/>
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
|
|
||||||
<TextBlock Text="上次同步:" FontSize="12" Foreground="#999" VerticalAlignment="Center"/>
|
|
||||||
<TextBlock Text="{Binding LastSyncTime, StringFormat=yyyy-MM-dd HH:mm, TargetNullValue=未同步}"
|
|
||||||
FontSize="12" Foreground="#666" VerticalAlignment="Center"/>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- 工具栏 -->
|
|
||||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,10">
|
|
||||||
<ComboBox Width="100" SelectedIndex="{Binding SelectedStatusIndex}" Height="30">
|
|
||||||
<ComboBoxItem Content="全部"/>
|
|
||||||
<ComboBoxItem Content="上架"/>
|
|
||||||
<ComboBoxItem Content="下架"/>
|
|
||||||
</ComboBox>
|
|
||||||
|
|
||||||
<TextBox Width="250" Margin="10,0"
|
|
||||||
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"
|
|
||||||
VerticalContentAlignment="Center" Height="30"
|
|
||||||
Padding="5,0">
|
|
||||||
<TextBox.Style>
|
|
||||||
<Style TargetType="TextBox">
|
|
||||||
<Style.Triggers>
|
|
||||||
<Trigger Property="Text" Value="">
|
|
||||||
<Setter Property="Background">
|
|
||||||
<Setter.Value>
|
|
||||||
<VisualBrush Stretch="None" AlignmentX="Left">
|
|
||||||
<VisualBrush.Visual>
|
|
||||||
<TextBlock Text="搜索产品名称/货品编号/条码" Foreground="Gray" Margin="5,0"/>
|
|
||||||
</VisualBrush.Visual>
|
|
||||||
</VisualBrush>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Trigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</TextBox.Style>
|
|
||||||
</TextBox>
|
|
||||||
|
|
||||||
<Button Content="搜索" Command="{Binding SearchCommand}"
|
|
||||||
Width="70" Height="30" Margin="0,0,10,0"/>
|
|
||||||
|
|
||||||
<Separator Width="1" Background="#DDD" Margin="10,5"/>
|
|
||||||
|
|
||||||
<Button Content="同步产品" Command="{Binding SyncProductsCommand}"
|
|
||||||
Width="90" Height="30" Margin="0,0,5,0"
|
|
||||||
Background="#1890FF" Foreground="White" BorderThickness="0"
|
|
||||||
IsEnabled="{Binding IsBusy, Converter={StaticResource InverseBool}}"/>
|
|
||||||
|
|
||||||
<Separator Width="1" Background="#DDD" Margin="10,5"/>
|
|
||||||
|
|
||||||
<Button Content="编辑货品编号" Command="{Binding StartEditItemCodeCommand}"
|
|
||||||
Width="100" Height="30" Margin="0,0,5,0"/>
|
|
||||||
|
|
||||||
<Button Content="上传到服务端" Command="{Binding UploadItemCodesToServerCommand}"
|
|
||||||
Width="100" Height="30" Margin="0,0,5,0"
|
|
||||||
Background="#FA8C16" Foreground="White" BorderThickness="0"
|
|
||||||
ToolTip="将本地货品编号同步到服务端"
|
|
||||||
IsEnabled="{Binding IsBusy, Converter={StaticResource InverseBool}}"/>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<!-- 产品列表 -->
|
|
||||||
<DataGrid Grid.Row="2"
|
|
||||||
ItemsSource="{Binding Products}"
|
|
||||||
SelectedItem="{Binding SelectedProduct}"
|
|
||||||
AutoGenerateColumns="False"
|
|
||||||
IsReadOnly="True"
|
|
||||||
SelectionMode="Single"
|
|
||||||
CanUserAddRows="False"
|
|
||||||
CanUserDeleteRows="False"
|
|
||||||
GridLinesVisibility="Horizontal"
|
|
||||||
HorizontalGridLinesBrush="#EEE"
|
|
||||||
RowHeight="45"
|
|
||||||
HeadersVisibility="Column">
|
|
||||||
|
|
||||||
<DataGrid.Columns>
|
|
||||||
<DataGridTextColumn Header="ID" Binding="{Binding Id}" Width="60"/>
|
|
||||||
|
|
||||||
<DataGridTextColumn Header="商品名称" Binding="{Binding Name}" Width="200"/>
|
|
||||||
|
|
||||||
<DataGridTemplateColumn Header="货品编号" Width="280">
|
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<TextBlock Padding="5,0">
|
|
||||||
<TextBlock.Style>
|
|
||||||
<Style TargetType="TextBlock">
|
|
||||||
<Setter Property="Text" Value="{Binding ItemCode}"/>
|
|
||||||
<Setter Property="Foreground" Value="#1890FF"/>
|
|
||||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding HasItemCode}" Value="False">
|
|
||||||
<Setter Property="Text" Value="(未设置)"/>
|
|
||||||
<Setter Property="Foreground" Value="#CCC"/>
|
|
||||||
<Setter Property="FontStyle" Value="Italic"/>
|
|
||||||
<Setter Property="FontWeight" Value="Normal"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</TextBlock.Style>
|
|
||||||
</TextBlock>
|
|
||||||
</DataTemplate>
|
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
|
||||||
</DataGridTemplateColumn>
|
|
||||||
|
|
||||||
<DataGridTextColumn Header="条码" Binding="{Binding BarCode}" Width="120"/>
|
|
||||||
|
|
||||||
<DataGridTextColumn Header="外部编号" Binding="{Binding YyId}" Width="100"/>
|
|
||||||
|
|
||||||
<DataGridTextColumn Header="现价" Binding="{Binding MinPrice, StringFormat=¥{0:F2}}" Width="80"/>
|
|
||||||
|
|
||||||
<DataGridTextColumn Header="库存" Binding="{Binding Stores}" Width="60"/>
|
|
||||||
|
|
||||||
<DataGridTextColumn Header="单位" Binding="{Binding Unit}" Width="50"/>
|
|
||||||
|
|
||||||
<DataGridTemplateColumn Header="状态" Width="60">
|
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<TextBlock Text="{Binding StatusText}" Padding="5,0">
|
|
||||||
<TextBlock.Style>
|
|
||||||
<Style TargetType="TextBlock">
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding Status}" Value="0">
|
|
||||||
<Setter Property="Foreground" Value="#52C41A"/>
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding Status}" Value="1">
|
|
||||||
<Setter Property="Foreground" Value="#999"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</TextBlock.Style>
|
|
||||||
</TextBlock>
|
|
||||||
</DataTemplate>
|
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
|
||||||
</DataGridTemplateColumn>
|
|
||||||
|
|
||||||
<DataGridTextColumn Header="同步时间"
|
|
||||||
Binding="{Binding SyncedAt, StringFormat=MM-dd HH:mm}"
|
|
||||||
Width="100"/>
|
|
||||||
</DataGrid.Columns>
|
|
||||||
</DataGrid>
|
|
||||||
|
|
||||||
<!-- 编辑货品编号面板 -->
|
|
||||||
<Border Grid.Row="3" Background="#F0F5FF" CornerRadius="4" Padding="15" Margin="0,10,0,0"
|
|
||||||
Visibility="{Binding IsEditing, Converter={StaticResource BoolToVisibility}}">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Text="编辑货品编号" FontWeight="Bold" Margin="0,0,0,10"/>
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<TextBlock Text="产品:" VerticalAlignment="Center" Width="50"/>
|
|
||||||
<TextBlock Text="{Binding SelectedProduct.Name}" VerticalAlignment="Center"
|
|
||||||
FontWeight="SemiBold" Foreground="#1890FF"/>
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
|
|
||||||
<TextBlock Text="货品编号:" VerticalAlignment="Center" Width="70"/>
|
|
||||||
<TextBox Text="{Binding EditingItemCode, UpdateSourceTrigger=PropertyChanged}"
|
|
||||||
Width="400" Height="30" Padding="5" VerticalContentAlignment="Center"/>
|
|
||||||
<Button Content="保存" Command="{Binding SaveItemCodeCommand}"
|
|
||||||
Width="70" Height="30" Margin="10,0,0,0"
|
|
||||||
Background="#52C41A" Foreground="White" BorderThickness="0"/>
|
|
||||||
<Button Content="取消" Command="{Binding CancelEditItemCodeCommand}"
|
|
||||||
Width="70" Height="30" Margin="5,0,0,0"/>
|
|
||||||
</StackPanel>
|
|
||||||
<TextBlock Text="格式示例:1222003x12+13222003x24+1322003x12+0000000x12"
|
|
||||||
FontSize="11" Foreground="#999" Margin="70,5,0,0"/>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- 加载遮罩 -->
|
|
||||||
<Border Grid.Row="2" Background="#80FFFFFF"
|
|
||||||
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVisibility}}">
|
|
||||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
|
||||||
<TextBlock Text="..." FontSize="30" HorizontalAlignment="Center"/>
|
|
||||||
<TextBlock Text="{Binding StatusMessage}" FontSize="14" Margin="0,10,0,0"/>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
Title="填写发货信息"
|
Title="填写发货信息"
|
||||||
Height="280" Width="400"
|
Height="320" Width="400"
|
||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
ResizeMode="NoResize"
|
ResizeMode="NoResize"
|
||||||
ShowInTaskbar="False">
|
ShowInTaskbar="False">
|
||||||
|
|||||||
@@ -63,8 +63,6 @@ bin/Release/net462/PackagingMallShipper.exe
|
|||||||
<!-- API工厂配置 -->
|
<!-- API工厂配置 -->
|
||||||
<add key="ApiBaseUrl" value="https://api.it120.cc" />
|
<add key="ApiBaseUrl" value="https://api.it120.cc" />
|
||||||
<add key="SubDomain" value="let5see" />
|
<add key="SubDomain" value="let5see" />
|
||||||
<!-- 商户ID,用于 /loginAdmin/operator/v2 子账号登录 -->
|
|
||||||
<add key="MerchantId" value="15073" />
|
|
||||||
|
|
||||||
<!-- 同步配置 -->
|
<!-- 同步配置 -->
|
||||||
<add key="SyncPageSize" value="50" />
|
<add key="SyncPageSize" value="50" />
|
||||||
@@ -72,8 +70,6 @@ bin/Release/net462/PackagingMallShipper.exe
|
|||||||
</appSettings>
|
</appSettings>
|
||||||
```
|
```
|
||||||
|
|
||||||
> 登录采用 `http://common.apifm.com/loginAdmin/operator/v2` 商户号子账号登录接口;`MerchantId` 未配置时回退至 `https://user.api.it120.cc/login/userName/v2` 的 `pdomain` 登录方式。
|
|
||||||
|
|
||||||
## 数据存储
|
## 数据存储
|
||||||
|
|
||||||
本地数据库位置:`%LOCALAPPDATA%\PackagingMallShipper\data.db`
|
本地数据库位置:`%LOCALAPPDATA%\PackagingMallShipper\data.db`
|
||||||
@@ -106,6 +102,4 @@ PackagingMallShipper/
|
|||||||
|
|
||||||
## 相关文档
|
## 相关文档
|
||||||
|
|
||||||
- [技术方案文档](./轻量级订单发货客户端方案.md)
|
- [技术方案文档](../enterprise-management/docs/轻量级订单发货客户端方案.md)
|
||||||
- [后台接口文档](./后台接口.md)
|
|
||||||
- [安装包构建说明](./BUILD_INSTALLER.md)
|
|
||||||
|
|||||||
48
create_sfx.bat
Normal file
48
create_sfx.bat
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
echo ========================================
|
||||||
|
echo 创建 7z 自解压安装包
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
set "SEVENZIP=C:\Program Files\7-Zip\7z.exe"
|
||||||
|
|
||||||
|
if not exist "%SEVENZIP%" (
|
||||||
|
echo [错误] 未找到 7-Zip
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
:: 创建临时 7z 包
|
||||||
|
echo [1/3] 创建压缩包...
|
||||||
|
"%SEVENZIP%" a -t7z -mx=9 "installer\app.7z" ".\publish\*" -r
|
||||||
|
|
||||||
|
:: 检查 SFX 模块
|
||||||
|
set "SFX_MODULE=C:\Program Files\7-Zip\7zSD.sfx"
|
||||||
|
if not exist "%SFX_MODULE%" (
|
||||||
|
echo.
|
||||||
|
echo [提示] 7zSD.sfx 模块不存在,将创建普通压缩包
|
||||||
|
echo.
|
||||||
|
move "installer\app.7z" "installer\PackagingMallShipper_v1.0.0.7z"
|
||||||
|
echo [完成] 已创建: installer\PackagingMallShipper_v1.0.0.7z
|
||||||
|
goto :end
|
||||||
|
)
|
||||||
|
|
||||||
|
:: 合并为自解压 EXE
|
||||||
|
echo [2/3] 创建自解压程序...
|
||||||
|
copy /b "%SFX_MODULE%" + "sfx_config.txt" + "installer\app.7z" "installer\PackagingMallShipper_SFX_v1.0.0.exe"
|
||||||
|
|
||||||
|
:: 清理临时文件
|
||||||
|
echo [3/3] 清理临时文件...
|
||||||
|
del "installer\app.7z"
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ========================================
|
||||||
|
echo [成功] 自解压安装包已创建!
|
||||||
|
echo.
|
||||||
|
echo 位置: installer\PackagingMallShipper_SFX_v1.0.0.exe
|
||||||
|
echo ========================================
|
||||||
|
|
||||||
|
:end
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
; Inno Setup Script for 包装商城发货助手
|
; Inno Setup Script for 包装商城发货助手
|
||||||
; Requires Inno Setup 6.x
|
; Requires Inno Setup 6.x
|
||||||
|
|
||||||
#define MyAppName "包装商城发货助手"
|
#define MyAppName "包装商城发货助手"
|
||||||
#define MyAppVersion "1.0.2"
|
#define MyAppVersion "1.0.1"
|
||||||
#define MyAppPublisher "PackagingMall"
|
#define MyAppPublisher "PackagingMall"
|
||||||
#define MyAppExeName "PackagingMallShipper.exe"
|
#define MyAppExeName "PackagingMallShipper.exe"
|
||||||
|
|
||||||
|
|||||||
5
sfx_config.txt
Normal file
5
sfx_config.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
;!@Install@!UTF-8!
|
||||||
|
Title="包装商城发货助手 v1.0.0"
|
||||||
|
BeginPrompt="是否安装 包装商城发货助手?"
|
||||||
|
RunProgram="PackagingMallShipper.exe"
|
||||||
|
;!@InstallEnd@!
|
||||||
76
产品.md
Normal file
76
产品.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# 成都邮电鸡蛋托套装报价表(2025-12-28)
|
||||||
|
|
||||||
|
> 备注:
|
||||||
|
> 1. 开具 13% 的增值税专票;
|
||||||
|
> 2. 四川范围内 9.6 m 以上车送货,送货费 20–40 元/方,包卸货;
|
||||||
|
> 3. 纸箱为高标准配置,高于网上常规配置;
|
||||||
|
> 4. 网络店铺可搜索淘宝第一名:捷合农产品包装。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、主表(按套装规格)
|
||||||
|
|
||||||
|
| 序号 | 产品系列 | 产品名称 | 货品编号 | 放置方式 | 蛋托+盖板厚度(mm) | 孔径尺寸(mm) | 纸箱尺寸(mm) | 适用蛋型 | 打包数量(套) | 自提开票(元/套) | 快递开票(元/套) | 网上头部店铺价(不含票) | 包价格(元/箱) |
|
||||||
|
|----|-----------|-----------------------------|--------------------------------------------------------------|------------|------------------|--------------|--------------|----------|--------------|----------------|----------------|----------------------|--------------|
|
||||||
|
| 1 | 20 枚竖大 | 20 枚竖大鸡蛋托+纸箱(12 套) | 1222003×12+13222003×24+1322003×12+0000000×12 | 竖放不粘底 | 10+60+10 | 35–45 | 285×233×91 | 大鸡蛋 | 12 | 2.5157 | 3.2407 | — | 38.8880 |
|
||||||
|
| 2 | 30 枚竖中 | 30 枚竖中鸡蛋托+纸箱(12 套) | 1223001×12+1323001×12+13223001×24+0000000×12 | 竖放不粘底 | 10+58+10 | 36–41 | 312×264×91 | 中小鸡蛋 | 12 | 3.2625 | 3.9875 | 5.9 | 47.8499 |
|
||||||
|
| 3 | 30 枚竖中彩箱 | 30 枚竖中彩箱(6 套) | 1223004×6+1323001×6+13223001×12+0000000×6 | 竖放不粘底 | 15+65+15 | 36–41 | 310×260×110 | 中小鸡蛋 | 6 | 4.43 | 5.88 | — | 35.28 |
|
||||||
|
| 4 | 加厚 30 枚竖中 | 加厚 30 枚竖中(6 套) | 122300101×6+13200101×6+1322300101×12+0000000×6 | 竖放不粘底 | 10+58+10 | 36–41 | 312×264×91 | 中小鸡蛋 | 6 | 4.0517 | 5.835 | — | 35.01 |
|
||||||
|
| 5 | 30 枚横中 | 30 枚横中鸡蛋托+纸箱(10 套) | 1223002×10+1323002×10+13223002×10+0000000×10 | 横放粘底 | 10+46+10 | 42–65 | 338×288×79 | 中偏大 | 10 | 3.3471 | 4.5571 | 7.4 | 45.5709 |
|
||||||
|
| 6 | 30 枚竖大上开口 | 30 枚竖大上开口箱(8 套) | 1323003×8+13223003×16+1223005×8+0000000×8 | 竖放不粘底 | 10+60+10 | 35–45 | 340×280×90 | 大鸡蛋 | 8 | 3.8663 | 4.9538 | — | 39.63 |
|
||||||
|
| 7 | 30 枚竖大 | 30 枚竖大鸡蛋托+纸箱(10 套) | 1323003×10+13223003×20+1223003×10+0000000×10 | 竖放不粘底 | 10+60+10 | 35–45 | 335×285×91 | 大鸡蛋 | 10 | 3.3492 | 4.2192 | 6 | 42.1918 |
|
||||||
|
| 8 | 40 枚竖大 | 40 枚竖大鸡蛋托+纸箱(8 套) | 1322003×16+13222003×24+1224003×8+0000000×8 | 竖放不粘底 | (10+60)×2+10 | 35–45 | 285×233×165 | 大鸡蛋 | 8 | 4.0375 | 5.125 | — | 41.00 |
|
||||||
|
| 9 | 50 枚竖中 | 50 枚竖中鸡蛋托+纸箱(6 套) | 1225001×6+1322501×12+13222501×18+0000000×6 | 竖放不粘底 | (10+57)×2+10 | 36–41 | 262×262×155 | 中小鸡蛋 | 6 | 4.4548 | 5.9048 | 8.5 | 35.4287 |
|
||||||
|
|10 | 50 枚横中 | 50 枚横中鸡蛋托+纸箱(6 套) | 1225002×6+1322502×12+13222502×6+0000000×6 | 横放粘底 | (10+46)×2+10 | 42–65 | 288×288×135 | 中偏大 | 6 | 4.81 | 6.8267 | 5.5 | 40.96 |
|
||||||
|
|11 | 50 枚竖大 | 50 枚竖大鸡蛋托+纸箱(6 套) | 1322503×12+13222503×18+0000000×6+1225003×6 | 竖放不粘底 | (10+60)×2+10 | 35–45 | 285×285×162 | 大鸡蛋 | 6 | 5.0206 | 6.4706 | 10.5 | 38.8238 |
|
||||||
|
|12 | 60 枚竖中 | 60 枚竖中鸡蛋托+纸箱(6 套) | 1226001×6+1323001×12+13223001×18+0000000×6 | 竖放不粘底 | (10+58)×2+10 | 36–41 | 312×262×159 | 中小鸡蛋 | 6 | 4.8707 | 6.3541 | 9.5 | 38.1243 |
|
||||||
|
|13 | 60 枚横中 | 60 枚横中鸡蛋托+纸箱(5 套) | 1226002×5+1323002×10+13223002×5+0000000×5 | 横放粘底 | (10+46)×2+10 | 42–65 | 338×288×135 | 中偏大 | 5 | 5.4791 | 7.2591 | 11.5 | 36.2957 |
|
||||||
|
|14 | 60 枚竖大 | 60 枚竖大鸡蛋托+纸箱(6 套) | 1323003×12+13223003×18+1226003×6+0000000×6 | 竖放不粘底 | (10+60)×2+10 | 45 | 335×285×165 | 大鸡蛋 | 6 | 5.5683 | 7.0183 | — | 42.11 |
|
||||||
|
|15 |100 枚竖中 |100 枚竖中鸡蛋托+纸箱(4 套) |12210001×4+1322501×16+13222501×20+0000000×4 | 竖放不粘底 | (10+57)×4+10 | 36–41 | 262×262×291 | 中小鸡蛋 | 4 | 7.2902 | 9.4652 | 14.5 | 37.8609 |
|
||||||
|
|16 |100 枚横中 |100 枚横中鸡蛋托+纸箱(4 套) |12210002×4+1322502×16+13222502×4+0000000×4 | 横放粘底 | (10+46)×4+10 | 42–65 | 288×288×247 | 中偏大 | 4 | 7.925 | 10.95 | 14.5 | 43.8 |
|
||||||
|
|17 |100 枚竖大 |100 枚竖大鸡蛋托+纸箱(4 套) |1322503×16+13222503×20+0000000×4+12210003×4 | 竖放不粘底 | (10+60)×4+10 | 45 | 285×285×302 | 大鸡蛋 | 4 | 8.3210 | 10.4960 | 16.5 | 41.9839 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 皮蛋/鸭蛋系列
|
||||||
|
|
||||||
|
| 类别 | 产品名称 | 货品编号 | 放置方式 | 厚度(mm) | 孔径(mm) | 纸箱尺寸(mm) | 适用规格 | 套/箱 | 自提开票(元/套) | 快递开票(元/套) | 包价格(元/箱) |
|
||||||
|
|------|----------|--------------------------------------------------------|----------|-----------|----------|---------------|----------|--------|------------------|------------------|----------------|
|
||||||
|
| 皮蛋 | 20 枚皮蛋托+纸箱(6 套) | 232001×6+332001×6 | 竖放 | 10+70+10 | 55 | 340×275×105 | 大皮蛋 | 6 | 4.15 | 6.1667 | 37.00 |
|
||||||
|
| 皮蛋 | 30 枚皮蛋托+纸箱(5 套) | 233001×5+333001×5 | 竖放 | (10+70)+10 | 52 | 320×195×185 | 中皮蛋 | 5 | 4.52 | 6.4 | 32.00 |
|
||||||
|
| 皮蛋 | 40 枚皮蛋托+纸箱(3 套) | 234001×3+332001×6 | 竖放 | (10+70)+10 | 55 | 340×280×195 | 大皮蛋 | 3 | 6.8667 | 10 | 30.00 |
|
||||||
|
| 鸭蛋 | 20 枚鸭蛋托+纸箱(6 套) | S322001×6+S232001×6 | 竖放 | 10+65+10 | 45 | 285×235×106 | 大中鸭蛋 | 6 | 3.4683 | 4.9183 | 29.51 |
|
||||||
|
| 鸭蛋 | 30 枚鸭蛋托+纸箱(6 套) | S321501×12+S233001×6 | 竖放 | (10+65)×2+10 | 45 | 285×180×185 | 大中鸭蛋 | 6 | 4.5580 | 6.0080 | 36.0478 |
|
||||||
|
| 鸭蛋 | 40 枚鸭蛋托+纸箱(5 套) | S322001×10+S234001×5 | 竖放 | (10+65)×2+10 | 45 | 286×246×195 | 大中鸭蛋 | 5 | 5.9099 | 7.6499 | 38.2493 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、速查价目表(Sheet3 精简版)
|
||||||
|
|
||||||
|
| 货品名称 | 货品编号 | 套/箱 | 自提开票(元/套) | 快递开票(元/套) | 包开票(元/箱) |
|
||||||
|
|----------|--------------------------------------------------------|--------|------------------|------------------|----------------|
|
||||||
|
| 20 枚鸡蛋托大套装 12 套竖不粘底 | 1222003×12+13222003×24+1322003×12+0000000×12 | 12 | 2.5157 | 3.2407 | 38.8880 |
|
||||||
|
| 30 枚鸡蛋托中套装 12 套竖不粘底 | 1223001×12+1323001×12+13223001×24+0000000×12 | 12 | 3.2625 | 3.9875 | 47.8499 |
|
||||||
|
| 加厚 30 枚鸡蛋托中套装 6 套竖不粘底 | 122300101×6+13200101×6+1322300101×12+0000000×6 | 6 | 4.0517 | 5.835 | 35.01 |
|
||||||
|
| 30 枚鸡蛋托大套装 10 套横粘底 | 1223002×10+1323002×10+13223002×10+0000000×10 | 10 | 3.3471 | 4.5571 | 45.5709 |
|
||||||
|
| 30 枚鸡蛋托大套装 10 套竖不粘底 | 1323003×10+13223003×20+1223003×10+0000000×10 | 10 | 3.3492 | 4.2192 | 42.1918 |
|
||||||
|
| 30 枚鸡蛋托大套装 8 套竖不粘底(上开口箱) | 1323003×8+13223003×16+1223005×8+0000000×8 | 8 | 3.8663 | 4.9538 | 39.63 |
|
||||||
|
| 30 枚鸡蛋托中套装 6 套竖不粘底彩箱 | 1223004×6+1323001×6+13223001×12+0000000×6 | 6 | 4.43 | 5.88 | 35.28 |
|
||||||
|
| 40 枚鸡蛋托大套装 8 套竖不粘底 | 1322003×16+13222003×24+1224003×8+0000000×8 | 8 | 4.0375 | 5.125 | 41.00 |
|
||||||
|
| 50 枚鸡蛋托中套装 6 套竖不粘底 | 1225001×6+1322501×12+13222501×18+0000000×6 | 6 | 4.4548 | 5.9048 | 35.4287 |
|
||||||
|
| 50 枚鸡蛋托大套装 6 套横粘底 | 1225002×6+1322502×12+13222502×6+0000000×6 | 6 | 4.81 | 6.8267 | 40.96 |
|
||||||
|
| 50 枚鸡蛋托大套装 6 套竖不粘底 | 1322503×12+13222503×18+0000000×6+1225003×6 | 6 | 5.0206 | 6.4706 | 38.8238 |
|
||||||
|
| 60 枚鸡蛋托中套装 6 套竖不粘底 | 1226001×6+1323001×12+13223001×18+0000000×6 | 6 | 4.8707 | 6.3541 | 38.1243 |
|
||||||
|
| 60 枚鸡蛋托大套装 5 套横粘底 | 1226002×5+1323002×10+13223002×5+0000000×5 | 5 | 5.4791 | 7.2591 | 36.2957 |
|
||||||
|
| 60 枚鸡蛋托大套装 6 套竖不粘底 | 1323003×12+13223003×18+1226003×6+0000000×6 | 6 | 5.5683 | 7.0183 | 42.11 |
|
||||||
|
| 100 枚鸡蛋托中套装 4 套竖不粘底 | 12210001×4+1322501×16+13222501×20+0000000×4 | 4 | 7.2902 | 9.4652 | 37.8609 |
|
||||||
|
| 100 枚鸡蛋托大套装 4 套横粘底 | 12210002×4+1322502×16+13222502×4+0000000×4 | 4 | 7.925 | 10.95 | 43.8 |
|
||||||
|
| 100 枚鸡蛋托大套装 4 套竖不粘底 | 1322503×16+13222503×20+0000000×4+12210003×4 | 4 | 8.3210 | 10.4960 | 41.9839 |
|
||||||
|
| 12 枚大鹅蛋对扣+纸箱(6 套) | 431203×12+12431201×6+0000000×6 | 6 | 4.5367 | 5.9867 | 35.92 |
|
||||||
|
| 12 枚大鹅蛋盖板托+纸箱(6 套) | 431201×6+431202×12+12431201×6+0000000×6 | 6 | 4.3233 | 5.7733 | 34.64 |
|
||||||
|
| 20 枚皮蛋托+纸箱 6 套 | 232001×6+332001×6 | 6 | 4.15 | 6.1667 | 37.00 |
|
||||||
|
| 30 枚皮蛋托+纸箱 5 套 | 233001×5+333001×5 | 5 | 4.52 | 6.4 | 32.00 |
|
||||||
|
| 40 枚皮蛋托+纸箱 3 套 | 234001×3+332001×6 | 3 | 6.8667 | 10 | 30.00 |
|
||||||
|
| 20 枚鸭蛋托+纸箱 6 套 | S322001×6+S232001×6 | 6 | 3.4683 | 4.9183 | 29.51 |
|
||||||
|
| 30 枚鸭蛋托+纸箱 6 套 | S321501×12+S233001×6 | 6 | 4.5580 | 6.0080 | 36.0478 |
|
||||||
|
| 40 枚鸭蛋托+纸箱 5 套 | S322001×10+S234001×5 | 5 | 5.9099 | 7.6499 | 38.2493 |
|
||||||
68637
后台_接口API.json
Normal file
68637
后台_接口API.json
Normal file
File diff suppressed because it is too large
Load Diff
87
调试说明.txt
Normal file
87
调试说明.txt
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
登录和数据库调试日志说明
|
||||||
|
==========================
|
||||||
|
|
||||||
|
已为登录功能和数据库初始化添加详细的调试日志输出,帮助诊断问题。
|
||||||
|
|
||||||
|
⚠️ 重要提示:如果遇到"SQLite error (1): no such table: local_session"错误
|
||||||
|
请删除旧数据库后重新运行程序:
|
||||||
|
1. 关闭程序
|
||||||
|
2. 删除文件:%LOCALAPPDATA%\PackagingMallShipper\data.db
|
||||||
|
3. 重新运行程序,数据库会自动重建
|
||||||
|
|
||||||
|
如何查看调试日志:
|
||||||
|
------------------
|
||||||
|
|
||||||
|
方法一:使用 Visual Studio
|
||||||
|
1. 在 Visual Studio 中打开项目
|
||||||
|
2. 按 F5 或点击"调试">"开始调试"启动程序
|
||||||
|
3. 尝试登录
|
||||||
|
4. 查看 Visual Studio 底部的"输出"窗口
|
||||||
|
5. 在输出窗口的下拉菜单中选择"调试"
|
||||||
|
6. 将看到类似以下的详细日志:
|
||||||
|
|
||||||
|
[登录请求] URL: https://user.api.it120.cc/login/userName/v2?userName=...&pwd=***&pdomain=let5see...
|
||||||
|
[登录请求] 用户名: 刘海春
|
||||||
|
[登录请求] 验证码: 1234
|
||||||
|
[登录请求] 验证码Key: abc123...
|
||||||
|
[登录请求] 子域名: let5see
|
||||||
|
[登录响应] HTTP状态码: OK
|
||||||
|
[登录响应] JSON原始内容: {"code":...,"msg":"..."}
|
||||||
|
[登录响应] 解析结果 - Code: xxx, Msg: xxx
|
||||||
|
[登录失败] 错误码: xxx, 错误信息: no user
|
||||||
|
|
||||||
|
方法二:使用 DebugView (无需 Visual Studio)
|
||||||
|
1. 下载 DebugView 工具:https://learn.microsoft.com/en-us/sysinternals/downloads/debugview
|
||||||
|
2. 以管理员身份运行 DebugView
|
||||||
|
3. 确保勾选 "Capture Win32" 和 "Capture Global Win32"
|
||||||
|
4. 运行编译好的 PackagingMallShipper.exe
|
||||||
|
5. 尝试登录
|
||||||
|
6. 在 DebugView 窗口中查看实时调试输出
|
||||||
|
|
||||||
|
日志内容说明:
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1. [登录请求] - 显示发送给API的请求信息
|
||||||
|
- URL:完整的API请求地址(密码已隐藏)
|
||||||
|
- 用户名:输入的用户名
|
||||||
|
- 验证码:输入的验证码值
|
||||||
|
- 验证码Key:用于验证码验证的唯一标识
|
||||||
|
- 子域名:当前配置的子域名
|
||||||
|
|
||||||
|
2. [登录响应] - 显示API返回的响应信息
|
||||||
|
- HTTP状态码:HTTP层面的状态(如 200 OK)
|
||||||
|
- JSON原始内容:API返回的完整JSON响应
|
||||||
|
- 解析结果:从JSON中提取的code和msg字段
|
||||||
|
|
||||||
|
3. [登录失败] - 如果登录失败,显示详细的错误信息
|
||||||
|
- 错误码:API返回的错误代码
|
||||||
|
- 错误信息:API返回的错误消息(如 "no user")
|
||||||
|
|
||||||
|
4. [登录成功] - 如果登录成功,显示token和用户ID
|
||||||
|
|
||||||
|
5. [登录异常] - 如果发生网络异常等错误,显示异常详情
|
||||||
|
|
||||||
|
排查步骤:
|
||||||
|
----------
|
||||||
|
|
||||||
|
1. 确认验证码是否正确输入
|
||||||
|
- 错误码 300 = "图片验证码错误"
|
||||||
|
|
||||||
|
2. 确认用户名编码是否正确
|
||||||
|
- 查看日志中的URL,确认中文用户名是否被正确编码
|
||||||
|
|
||||||
|
3. 确认子域名配置是否正确
|
||||||
|
- 应该为 "let5see"
|
||||||
|
|
||||||
|
4. 查看API返回的完整错误信息
|
||||||
|
- 可能会有更详细的错误描述
|
||||||
|
|
||||||
|
5. 验证HTTP状态码
|
||||||
|
- 应该为 200 OK
|
||||||
|
- 如果是 404,说明接口地址错误
|
||||||
|
|
||||||
|
常见错误码:
|
||||||
|
-----------
|
||||||
|
- 300: 图片验证码错误
|
||||||
|
- 700: 需要验证码
|
||||||
|
- 其他错误码请参考API返回的msg字段
|
||||||
Reference in New Issue
Block a user