diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b417df5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,75 @@ +# Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +x86/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio +.vs/ +*.user +*.suo +*.userosscache +*.sln.docstates +*.userprefs + +# NuGet +*.nupkg +**/[Pp]ackages/* +!**/[Pp]ackages/build/ +*.nuget.props +*.nuget.targets + +# Build outputs +publish/ +installer/ +*.exe +*.dll +*.pdb +*.cache + +# Installer files +*.7z +*.zip +*.msi + +# Test results +[Tt]est[Rr]esult*/ +*.trx + +# Rider +.idea/ +*.sln.iml + +# OS files +Thumbs.db +ehthumbs.db +Desktop.ini +.DS_Store + +# Temp files +*.tmp +*.temp +*.log +*~ +nul + +# Local data +*.db +*.sqlite + +# Config with secrets (keep App.config template) +# App.config + +# Generated files +FodyWeavers.xsd + +# Backup files +*.bak +*.orig + +# API test files +*_response.json diff --git a/PackagingMallShipper/App.config b/PackagingMallShipper/App.config index aa19cf8..6cb018a 100644 --- a/PackagingMallShipper/App.config +++ b/PackagingMallShipper/App.config @@ -5,8 +5,8 @@ - - + + diff --git a/PackagingMallShipper/Data/SqliteHelper.cs b/PackagingMallShipper/Data/SqliteHelper.cs index 3f06142..aa17440 100644 --- a/PackagingMallShipper/Data/SqliteHelper.cs +++ b/PackagingMallShipper/Data/SqliteHelper.cs @@ -1,5 +1,6 @@ using System; using System.Data.SQLite; +using System.Diagnostics; using System.IO; using PackagingMallShipper.Helpers; @@ -19,13 +20,21 @@ namespace PackagingMallShipper.Data ); var dir = Path.GetDirectoryName(_dbPath); + Debug.WriteLine($"[数据库初始化] 数据库目录: {dir}"); + if (!Directory.Exists(dir)) + { Directory.CreateDirectory(dir); + Debug.WriteLine($"[数据库初始化] 创建数据库目录"); + } _connectionString = $"Data Source={_dbPath};Version=3;"; + Debug.WriteLine($"[数据库初始化] 数据库路径: {_dbPath}"); + Debug.WriteLine($"[数据库初始化] 数据库文件存在: {File.Exists(_dbPath)}"); if (!File.Exists(_dbPath)) { + Debug.WriteLine($"[数据库初始化] 创建新数据库文件"); SQLiteConnection.CreateFile(_dbPath); CreateTables(); } @@ -40,15 +49,146 @@ namespace PackagingMallShipper.Data private static void CreateTables() { - using (var conn = GetConnection()) + try { - conn.Open(); - var sql = ResourceHelper.GetEmbeddedResource("schema.sql"); - using (var cmd = new SQLiteCommand(sql, conn)) + Debug.WriteLine($"[数据库初始化] 开始创建数据库表"); + using (var conn = GetConnection()) { - cmd.ExecuteNonQuery(); + conn.Open(); + Debug.WriteLine($"[数据库初始化] 数据库连接已打开"); + + string sql; + try + { + // 尝试从嵌入资源读取 + sql = ResourceHelper.GetEmbeddedResource("schema.sql"); + Debug.WriteLine($"[数据库初始化] 从嵌入资源读取 schema.sql,长度: {sql?.Length ?? 0} 字符"); + } + catch (Exception ex) + { + // 如果嵌入资源读取失败,尝试从文件系统读取 + Debug.WriteLine($"[数据库初始化] 嵌入资源读取失败: {ex.Message}"); + var schemaPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data", "Resources", "schema.sql"); + Debug.WriteLine($"[数据库初始化] 尝试从文件读取: {schemaPath}"); + + if (!File.Exists(schemaPath)) + { + // 如果文件也不存在,使用硬编码的SQL + Debug.WriteLine($"[数据库初始化] schema.sql 文件不存在,使用硬编码SQL"); + sql = GetHardcodedSchema(); + } + else + { + sql = File.ReadAllText(schemaPath); + Debug.WriteLine($"[数据库初始化] 从文件读取成功,长度: {sql?.Length ?? 0} 字符"); + } + } + + using (var cmd = new SQLiteCommand(sql, conn)) + { + cmd.ExecuteNonQuery(); + Debug.WriteLine($"[数据库初始化] 数据库表创建成功"); + } } } + catch (Exception ex) + { + Debug.WriteLine($"[数据库初始化] 创建表失败: {ex.Message}"); + Debug.WriteLine($"[数据库初始化] StackTrace: {ex.StackTrace}"); + throw; + } + } + + private static string GetHardcodedSchema() + { + return @" +-- 1. 本地用户会话 +CREATE TABLE IF NOT EXISTS local_session ( + id INTEGER PRIMARY KEY, + mobile TEXT NOT NULL, + token TEXT NOT NULL, + uid INTEGER, + nickname TEXT, + enterprise_id INTEGER, + last_login_at DATETIME DEFAULT CURRENT_TIMESTAMP, + token_expires_at DATETIME +); + +-- 2. 订单缓存表 +CREATE TABLE IF NOT EXISTS orders_cache ( + id INTEGER PRIMARY KEY, + order_number TEXT NOT NULL UNIQUE, + status INTEGER NOT NULL, + amount REAL, + amount_real REAL, + uid INTEGER, + user_mobile TEXT, + logistics_name TEXT, + logistics_mobile TEXT, + logistics_province TEXT, + logistics_city TEXT, + logistics_district TEXT, + logistics_address TEXT, + goods_json TEXT, + express_company_id INTEGER, + express_company_name TEXT, + tracking_number TEXT, + date_ship DATETIME, + sync_status TEXT DEFAULT 'synced', + local_updated_at DATETIME, + date_add DATETIME, + date_pay DATETIME, + date_update DATETIME, + synced_at DATETIME +); + +-- 3. 发货队列表 +CREATE TABLE IF NOT EXISTS ship_queue ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id INTEGER NOT NULL, + order_number TEXT NOT NULL, + express_company_id INTEGER NOT NULL, + tracking_number TEXT NOT NULL, + status TEXT DEFAULT 'pending', + retry_count INTEGER DEFAULT 0, + error_message TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME +); + +-- 4. 同步日志表 +CREATE TABLE IF NOT EXISTS sync_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + sync_type TEXT NOT NULL, + sync_mode TEXT, + sync_start DATETIME NOT NULL, + sync_end DATETIME, + total_count INTEGER DEFAULT 0, + new_count INTEGER DEFAULT 0, + updated_count INTEGER DEFAULT 0, + failed_count INTEGER DEFAULT 0, + status TEXT NOT NULL, + error_message TEXT +); + +-- 5. 操作日志表 +CREATE TABLE IF NOT EXISTS operation_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + operation_type TEXT NOT NULL, + target_id INTEGER, + target_number TEXT, + details TEXT, + result TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- 索引 +CREATE INDEX IF NOT EXISTS idx_orders_status ON orders_cache(status); +CREATE INDEX IF NOT EXISTS idx_orders_date_add ON orders_cache(date_add); +CREATE INDEX IF NOT EXISTS idx_orders_order_number ON orders_cache(order_number); +CREATE INDEX IF NOT EXISTS idx_ship_queue_status ON ship_queue(status); +CREATE INDEX IF NOT EXISTS idx_sync_logs_status ON sync_logs(status); +"; } public static void ExecuteNonQuery(string sql, params SQLiteParameter[] parameters) diff --git a/PackagingMallShipper/FodyWeavers.xml b/PackagingMallShipper/FodyWeavers.xml new file mode 100644 index 0000000..2af0dda --- /dev/null +++ b/PackagingMallShipper/FodyWeavers.xml @@ -0,0 +1,11 @@ + + + + + false + + false + + true + + diff --git a/PackagingMallShipper/Models/ApiResponses.cs b/PackagingMallShipper/Models/ApiResponses.cs index ffa59ab..809ec85 100644 --- a/PackagingMallShipper/Models/ApiResponses.cs +++ b/PackagingMallShipper/Models/ApiResponses.cs @@ -25,6 +25,18 @@ namespace PackagingMallShipper.Models public int Uid { get; set; } } + public class AdminLoginData + { + [JsonProperty("id")] + public int Id { get; set; } + + [JsonProperty("token")] + public string Token { get; set; } + + [JsonProperty("nickname")] + public string Nickname { get; set; } + } + public class UserInfo { [JsonProperty("nick")] @@ -43,18 +55,122 @@ namespace PackagingMallShipper.Models public string Token { get; set; } public string Message { get; set; } public UserInfo UserInfo { get; set; } + public bool RequireCaptcha { get; set; } // 是否需要验证码 } public class OrderListData { + // API可能返回不同的字段名 [JsonProperty("orderList")] public List OrderList { get; set; } + [JsonProperty("result")] + public List Result { get; set; } + + [JsonProperty("list")] + public List List { get; set; } + + // 物流/收件人信息列表(与订单通过id关联) + [JsonProperty("logisticses")] + public List Logisticses { get; set; } + + // 商品列表(与订单通过orderId关联) + [JsonProperty("goods")] + public List Goods { get; set; } + [JsonProperty("totalRow")] public int TotalRow { get; set; } + [JsonProperty("total")] + public int Total { get; set; } + [JsonProperty("totalPage")] public int TotalPage { get; set; } + + // 辅助方法:获取订单列表(兼容多种返回格式)并合并物流信息 + public List GetOrders() + { + var orders = Result ?? OrderList ?? List ?? new List(); + + // 合并物流信息到订单 + if (Logisticses != null && Logisticses.Count > 0) + { + var logisticsMap = new Dictionary(); + foreach (var log in Logisticses) + { + logisticsMap[log.Id] = log; + } + + foreach (var order in orders) + { + if (logisticsMap.TryGetValue(order.Id, out var logistics)) + { + order.LogisticsName = logistics.LinkMan; + order.LogisticsMobile = logistics.Mobile; + order.LogisticsProvince = logistics.ProvinceStr; + order.LogisticsCity = logistics.CityStr; + order.LogisticsDistrict = logistics.AreaStr; + order.LogisticsAddress = logistics.Address; + } + } + } + + // 合并商品信息到订单 + if (Goods != null && Goods.Count > 0) + { + var goodsMap = new Dictionary>(); + foreach (var item in Goods) + { + if (!goodsMap.ContainsKey(item.OrderId)) + goodsMap[item.OrderId] = new List(); + goodsMap[item.OrderId].Add(item); + } + + foreach (var order in orders) + { + if (goodsMap.TryGetValue(order.Id, out var items)) + { + order.Goods = items; + } + } + } + + return orders; + } + + // 辅助方法:获取总记录数 + public int GetTotalRow() + { + return TotalRow > 0 ? TotalRow : Total; + } + } + + // 物流/收件人信息 + public class LogisticsInfo + { + [JsonProperty("id")] + public int Id { get; set; } // 订单ID + + [JsonProperty("linkMan")] + public string LinkMan { get; set; } // 收件人 + + [JsonProperty("mobile")] + public string Mobile { get; set; } // 收件人电话 + + [JsonProperty("provinceStr")] + public string ProvinceStr { get; set; } + + [JsonProperty("cityStr")] + public string CityStr { get; set; } + + [JsonProperty("areaStr")] + public string AreaStr { get; set; } + + [JsonProperty("address")] + public string Address { get; set; } + + [JsonProperty("streetStr")] + public string StreetStr { get; set; } } public class OrderDto diff --git a/PackagingMallShipper/Models/Order.cs b/PackagingMallShipper/Models/Order.cs index 155ce31..10300cf 100644 --- a/PackagingMallShipper/Models/Order.cs +++ b/PackagingMallShipper/Models/Order.cs @@ -63,14 +63,29 @@ namespace PackagingMallShipper.Models public class GoodsItem { + [JsonProperty("orderId")] + public int OrderId { get; set; } + + [JsonProperty("goodsId")] + public int GoodsId { get; set; } + [JsonProperty("goodsName")] public string GoodsName { get; set; } [JsonProperty("number")] public int Number { get; set; } - [JsonProperty("price")] + [JsonProperty("amount")] + public decimal Amount { get; set; } + + [JsonProperty("amountSingle")] public decimal Price { get; set; } + + [JsonProperty("pic")] + public string Pic { get; set; } + + [JsonProperty("unit")] + public string Unit { get; set; } } public static class OrderStatus diff --git a/PackagingMallShipper/PackagingMallShipper.csproj b/PackagingMallShipper/PackagingMallShipper.csproj index d70aed1..1044175 100644 --- a/PackagingMallShipper/PackagingMallShipper.csproj +++ b/PackagingMallShipper/PackagingMallShipper.csproj @@ -1,139 +1,47 @@ - - - + + - Debug - AnyCPU - {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} WinExe + net48 PackagingMallShipper PackagingMallShipper - v4.8 - 512 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 4 - true - true - 7.3 - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - + true + false + 9.0 Resources\Icons\app.ico + false + + + 1.0.0 + 1.0.0.0 + 1.0.0.0 + PackagingMall + 包装商城发货助手 + Copyright © 2025 - - - - - - - - - - - 4.0 - - - - - - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - App.xaml - Code - - - - - - - - - - - - - - - - - - - - - - - - - - LoginWindow.xaml - - - MainWindow.xaml - - - OrderListView.xaml - - - ShippingDialog.xaml - - - - - - - - - - - - + + + + - + + + + + + + + + + + + PreserveNewest + + + diff --git a/PackagingMallShipper/Resources/Icons/app.ico b/PackagingMallShipper/Resources/Icons/app.ico new file mode 100644 index 0000000..0f1afd3 Binary files /dev/null and b/PackagingMallShipper/Resources/Icons/app.ico differ diff --git a/PackagingMallShipper/Resources/Icons/app.svg b/PackagingMallShipper/Resources/Icons/app.svg new file mode 100644 index 0000000..99d9b94 --- /dev/null +++ b/PackagingMallShipper/Resources/Icons/app.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PackagingMallShipper/Services/AuthService.cs b/PackagingMallShipper/Services/AuthService.cs index dbc5b96..d735188 100644 --- a/PackagingMallShipper/Services/AuthService.cs +++ b/PackagingMallShipper/Services/AuthService.cs @@ -1,5 +1,6 @@ using System; using System.Data.SQLite; +using System.Diagnostics; using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; @@ -25,43 +26,74 @@ namespace PackagingMallShipper.Services public bool IsLoggedIn => !string.IsNullOrEmpty(GetToken()); - public async Task LoginAsync(string mobile, string password) + public string GetCaptchaUrl(string key) + { + return $"https://user.api.it120.cc/code?k={Uri.EscapeDataString(key)}"; + } + + public async Task LoginAsync(string userName, string password, string captcha = null, string captchaKey = null) { try { - var url = AppConfig.GetApiUrl($"/user/m/login?mobile={mobile}&pwd={password}"); + // 使用子账号登录接口 /login/userName/v2 + // 注意:该接口会优先判断手机号码登录,如果满足直接登录成功,其次才会尝试子账号登录 + // 该接口要求必须提供验证码参数 + var urlBuilder = new System.Text.StringBuilder(); + urlBuilder.Append("https://user.api.it120.cc/login/userName/v2"); + urlBuilder.Append($"?userName={Uri.EscapeDataString(userName)}"); + urlBuilder.Append($"&pwd={Uri.EscapeDataString(password)}"); + urlBuilder.Append($"&pdomain={Uri.EscapeDataString(AppConfig.SubDomain)}"); + urlBuilder.Append("&rememberMe=true"); + + // 验证码参数是必须的 + 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($"[登录请求] 验证码: {captcha}"); + System.Diagnostics.Debug.WriteLine($"[登录请求] 验证码Key: {captchaKey}"); + System.Diagnostics.Debug.WriteLine($"[登录请求] 子域名: {AppConfig.SubDomain}"); var response = await _httpClient.PostAsync(url, null); var json = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject>(json); + + // 详细日志:API响应 + System.Diagnostics.Debug.WriteLine($"[登录响应] HTTP状态码: {response.StatusCode}"); + System.Diagnostics.Debug.WriteLine($"[登录响应] JSON原始内容: {json}"); + + var result = JsonConvert.DeserializeObject>(json); + + System.Diagnostics.Debug.WriteLine($"[登录响应] 解析结果 - Code: {result?.Code}, Msg: {result?.Msg}"); if (result?.Code != 0) { + System.Diagnostics.Debug.WriteLine($"[登录失败] 错误码: {result?.Code}, 错误信息: {result?.Msg}"); + return new LoginResult { Success = false, - Message = result?.Msg ?? "登录失败" + Message = result?.Msg ?? "登录失败", + RequireCaptcha = result?.Code == 700 // 700 表示需要验证码 }; } var token = result.Data.Token; - var uid = result.Data.Uid; + var adminId = result.Data.Id; - _httpClient.DefaultRequestHeaders.Clear(); - _httpClient.DefaultRequestHeaders.Add("X-Token", token); - - var userInfoUrl = AppConfig.GetApiUrl("/user/detail"); - var userResponse = await _httpClient.GetAsync(userInfoUrl); - var userJson = await userResponse.Content.ReadAsStringAsync(); - var userResult = JsonConvert.DeserializeObject>(userJson); + Debug.WriteLine($"[登录成功] Token: {token?.Substring(0, Math.Min(20, token?.Length ?? 0))}..., AdminId: {adminId}"); _currentSession = new LocalSession { Id = 1, - Mobile = mobile, + Mobile = userName, // 存储用户名/手机号 Token = token, - Uid = uid, - Nickname = userResult?.Data?.Nick ?? mobile, + Uid = adminId, + Nickname = result.Data.Nickname ?? userName, LastLoginAt = DateTime.Now, TokenExpiresAt = DateTime.Now.AddHours(AppConfig.TokenExpireHours) }; @@ -72,11 +104,14 @@ namespace PackagingMallShipper.Services { Success = true, Token = token, - UserInfo = userResult?.Data + UserInfo = null // 子账号登录不需要额外获取用户信息 }; } catch (Exception ex) { + Debug.WriteLine($"[登录异常] {ex.GetType().Name}: {ex.Message}"); + Debug.WriteLine($"[登录异常] StackTrace: {ex.StackTrace}"); + return new LoginResult { Success = false, diff --git a/PackagingMallShipper/Services/ExcelService.cs b/PackagingMallShipper/Services/ExcelService.cs index ca76d1e..b116165 100644 --- a/PackagingMallShipper/Services/ExcelService.cs +++ b/PackagingMallShipper/Services/ExcelService.cs @@ -49,8 +49,9 @@ namespace PackagingMallShipper.Services worksheet.Cell(row, 1).Value = order.OrderNumber; worksheet.Cell(row, 2).Value = order.DateAdd?.ToString("yyyy-MM-dd HH:mm:ss"); worksheet.Cell(row, 3).Value = order.LogisticsName; - worksheet.Cell(row, 4).Value = order.LogisticsMobile; - worksheet.Cell(row, 4).SetDataType(XLDataType.Text); + var mobileCell = worksheet.Cell(row, 4); + mobileCell.Value = order.LogisticsMobile; + mobileCell.Style.NumberFormat.Format = "@"; worksheet.Cell(row, 5).Value = order.LogisticsProvince; worksheet.Cell(row, 6).Value = order.LogisticsCity; worksheet.Cell(row, 7).Value = order.LogisticsDistrict; diff --git a/PackagingMallShipper/Services/Interfaces.cs b/PackagingMallShipper/Services/Interfaces.cs index b131ddf..04348a7 100644 --- a/PackagingMallShipper/Services/Interfaces.cs +++ b/PackagingMallShipper/Services/Interfaces.cs @@ -8,7 +8,8 @@ namespace PackagingMallShipper.Services { public interface IAuthService { - Task LoginAsync(string mobile, string password); + Task LoginAsync(string userName, string password, string captcha = null, string captchaKey = null); + string GetCaptchaUrl(string key); string GetToken(); bool IsLoggedIn { get; } LocalSession CurrentSession { get; } diff --git a/PackagingMallShipper/Services/OrderService.cs b/PackagingMallShipper/Services/OrderService.cs index 0485234..fe72a67 100644 --- a/PackagingMallShipper/Services/OrderService.cs +++ b/PackagingMallShipper/Services/OrderService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Data.SQLite; using System.Threading.Tasks; diff --git a/PackagingMallShipper/Services/SyncService.cs b/PackagingMallShipper/Services/SyncService.cs index 210af36..89e6c4a 100644 --- a/PackagingMallShipper/Services/SyncService.cs +++ b/PackagingMallShipper/Services/SyncService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data.SQLite; +using System.Diagnostics; using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; @@ -57,22 +58,44 @@ namespace PackagingMallShipper.Services { OnSyncMessage?.Invoke($"正在同步第 {page}/{totalPages} 页..."); - var url = AppConfig.GetApiUrl($"/order/list?page={page}&pageSize={pageSize}"); + // 使用正确的API端点:/user/apiExtOrder/list (POST请求) + // 域名:user.api.it120.cc,路径不含subdomain + var urlBuilder = new System.Text.StringBuilder(); + urlBuilder.Append("https://user.api.it120.cc/user/apiExtOrder/list"); + urlBuilder.Append($"?page={page}&pageSize={pageSize}"); + urlBuilder.Append("&export=true"); // 获取更丰富的数据 if (lastSyncTime.HasValue) - url += $"&dateUpdateBegin={lastSyncTime:yyyy-MM-dd HH:mm:ss}"; + urlBuilder.Append($"&dateUpdateBegin={lastSyncTime:yyyy-MM-dd HH:mm:ss}"); + + var url = urlBuilder.ToString(); + + Debug.WriteLine($"[同步请求] URL: {url}"); + Debug.WriteLine($"[同步请求] Token: {token?.Substring(0, Math.Min(20, token?.Length ?? 0))}..."); _httpClient.DefaultRequestHeaders.Clear(); _httpClient.DefaultRequestHeaders.Add("X-Token", token); - var response = await _httpClient.GetAsync(url); + // 使用 POST 请求 + var response = await _httpClient.PostAsync(url, null); var json = await response.Content.ReadAsStringAsync(); + + Debug.WriteLine($"[同步响应] HTTP状态码: {response.StatusCode}"); + var data = JsonConvert.DeserializeObject>(json); if (data?.Code != 0) + { + if (data?.Msg?.Contains("token") == true || data?.Msg?.Contains("登录") == true) + throw new UnauthorizedAccessException($"当前登录token无效,请重新登录"); throw new Exception(data?.Msg ?? "获取订单失败"); + } - var orders = data.Data?.OrderList ?? new List(); + // 使用辅助方法获取订单列表(自动合并物流和商品信息) + var orders = data.Data?.GetOrders() ?? new List(); totalPages = data.Data?.TotalPage ?? 1; + var totalRow = data.Data?.GetTotalRow() ?? 0; + + Debug.WriteLine($"[同步响应] 订单数量: {orders.Count}, 总记录: {totalRow}, 总页数: {totalPages}"); if (orders.Count == 0) break; diff --git a/PackagingMallShipper/ViewModels/LoginViewModel.cs b/PackagingMallShipper/ViewModels/LoginViewModel.cs index 90a4c10..e11cde1 100644 --- a/PackagingMallShipper/ViewModels/LoginViewModel.cs +++ b/PackagingMallShipper/ViewModels/LoginViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using System.Windows; +using System.Windows.Media.Imaging; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using PackagingMallShipper.Services; @@ -10,33 +11,61 @@ namespace PackagingMallShipper.ViewModels public partial class LoginViewModel : ViewModelBase { private readonly IAuthService _authService; + private string _captchaKey = ""; [ObservableProperty] - private string _mobile = ""; + private string _userName = ""; [ObservableProperty] private string _password = ""; + [ObservableProperty] + private string _captcha = ""; + [ObservableProperty] private string _errorMessage = ""; [ObservableProperty] private bool _rememberMe = true; + [ObservableProperty] + private bool _showCaptcha = false; + + [ObservableProperty] + private BitmapImage _captchaImageSource; + public event Action OnLoginSuccess; public LoginViewModel(IAuthService authService) { _authService = authService; LoadSavedCredentials(); + + // 子账号登录必须提供验证码,默认显示 + ShowCaptcha = true; + RefreshCaptcha(); + } + + [RelayCommand] + public void RefreshCaptcha() + { + _captchaKey = Guid.NewGuid().ToString("N"); + var url = _authService.GetCaptchaUrl(_captchaKey); + + CaptchaImageSource = new BitmapImage(); + CaptchaImageSource.BeginInit(); + CaptchaImageSource.UriSource = new Uri(url); + CaptchaImageSource.CacheOption = BitmapCacheOption.None; + CaptchaImageSource.CreateOptions = BitmapCreateOptions.IgnoreImageCache; + CaptchaImageSource.EndInit(); } [RelayCommand] private async Task LoginAsync() { - if (string.IsNullOrWhiteSpace(Mobile)) + if (string.IsNullOrWhiteSpace(UserName)) { - ErrorMessage = "请输入手机号"; + ErrorMessage = "请输入用户名或手机号"; return; } @@ -46,12 +75,23 @@ namespace PackagingMallShipper.ViewModels return; } + if (ShowCaptcha && string.IsNullOrWhiteSpace(Captcha)) + { + ErrorMessage = "请输入验证码"; + return; + } + IsBusy = true; ErrorMessage = ""; try { - var result = await _authService.LoginAsync(Mobile, Password); + var result = await _authService.LoginAsync( + UserName, + Password, + ShowCaptcha ? Captcha : null, + ShowCaptcha ? _captchaKey : null + ); if (result.Success) { @@ -64,6 +104,19 @@ namespace PackagingMallShipper.ViewModels else { ErrorMessage = result.Message ?? "登录失败"; + + // 如果需要验证码,显示验证码输入 + if (result.RequireCaptcha && !ShowCaptcha) + { + ShowCaptcha = true; + RefreshCaptcha(); + } + else if (ShowCaptcha) + { + // 验证码错误,刷新验证码 + Captcha = ""; + RefreshCaptcha(); + } } } catch (Exception ex) @@ -80,10 +133,10 @@ namespace PackagingMallShipper.ViewModels { try { - var mobile = Properties.Settings.Default.SavedMobile; - if (!string.IsNullOrEmpty(mobile)) + var userName = Properties.Settings.Default.SavedMobile; + if (!string.IsNullOrEmpty(userName)) { - Mobile = mobile; + UserName = userName; RememberMe = true; } } @@ -97,7 +150,7 @@ namespace PackagingMallShipper.ViewModels { try { - Properties.Settings.Default.SavedMobile = Mobile; + Properties.Settings.Default.SavedMobile = UserName; Properties.Settings.Default.Save(); } catch diff --git a/PackagingMallShipper/Views/LoginWindow.xaml b/PackagingMallShipper/Views/LoginWindow.xaml index 1254635..97084bd 100644 --- a/PackagingMallShipper/Views/LoginWindow.xaml +++ b/PackagingMallShipper/Views/LoginWindow.xaml @@ -4,8 +4,8 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" - Title="包装商城发货助手 - 登录" - Height="400" Width="350" + Title="包装商城发货助手 - 登录" + Height="480" Width="350" WindowStartupLocation="CenterScreen" ResizeMode="NoResize" Background="#F5F5F5"> @@ -22,19 +22,46 @@ HorizontalAlignment="Center" Margin="0,0,0,30"/> - - + - + - - + + + + + + + + + + + + + + + + + diff --git a/PackagingMallShipper/Views/LoginWindow.xaml.cs b/PackagingMallShipper/Views/LoginWindow.xaml.cs index b91d7ab..f766be9 100644 --- a/PackagingMallShipper/Views/LoginWindow.xaml.cs +++ b/PackagingMallShipper/Views/LoginWindow.xaml.cs @@ -34,6 +34,11 @@ namespace PackagingMallShipper.Views } } + private void CaptchaImage_Click(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + _viewModel.RefreshCaptchaCommand.Execute(null); + } + private void OnLoginSuccess() { OpenMainWindow(); diff --git a/PackagingMallShipper/Views/OrderListView.xaml b/PackagingMallShipper/Views/OrderListView.xaml index 048ff4b..9e87f55 100644 --- a/PackagingMallShipper/Views/OrderListView.xaml +++ b/PackagingMallShipper/Views/OrderListView.xaml @@ -77,10 +77,12 @@ Width="90" Height="30" Margin="0,0,5,0" IsEnabled="{Binding IsBusy, Converter={StaticResource InverseBool}}"/> -