feat: 完善登录验证码、订单同步和打包功能
- 添加验证码登录支持(图形验证码显示和输入) - 修复订单同步,正确解析收件人信息(从logisticses合并) - 修复API端点配置(user.api.it120.cc) - 添加Costura.Fody实现单文件EXE打包 - 添加应用图标(app.ico) - 添加Inno Setup安装脚本(支持Win7+) - 暂时禁用导入发货功能 - 添加.gitignore文件 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
75
.gitignore
vendored
Normal file
75
.gitignore
vendored
Normal file
@@ -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
|
||||
@@ -5,8 +5,8 @@
|
||||
</startup>
|
||||
<appSettings>
|
||||
<!-- API工厂配置 -->
|
||||
<add key="ApiBaseUrl" value="https://user.api.it120.cc" />
|
||||
<add key="SubDomain" value="vv125s" />
|
||||
<add key="ApiBaseUrl" value="https://api.it120.cc" />
|
||||
<add key="SubDomain" value="let5see" />
|
||||
|
||||
<!-- 同步配置 -->
|
||||
<add key="SyncPageSize" value="50" />
|
||||
|
||||
@@ -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)
|
||||
|
||||
11
PackagingMallShipper/FodyWeavers.xml
Normal file
11
PackagingMallShipper/FodyWeavers.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Costura>
|
||||
<!-- 设置为true以包含调试符号 -->
|
||||
<IncludeDebugSymbols>false</IncludeDebugSymbols>
|
||||
<!-- 禁用压缩可提高启动速度 -->
|
||||
<DisableCompression>false</DisableCompression>
|
||||
<!-- 创建临时目录存放原生DLL -->
|
||||
<CreateTemporaryAssemblies>true</CreateTemporaryAssemblies>
|
||||
</Costura>
|
||||
</Weavers>
|
||||
@@ -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<OrderDto> OrderList { get; set; }
|
||||
|
||||
[JsonProperty("result")]
|
||||
public List<OrderDto> Result { get; set; }
|
||||
|
||||
[JsonProperty("list")]
|
||||
public List<OrderDto> List { get; set; }
|
||||
|
||||
// 物流/收件人信息列表(与订单通过id关联)
|
||||
[JsonProperty("logisticses")]
|
||||
public List<LogisticsInfo> Logisticses { get; set; }
|
||||
|
||||
// 商品列表(与订单通过orderId关联)
|
||||
[JsonProperty("goods")]
|
||||
public List<GoodsItem> 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<OrderDto> GetOrders()
|
||||
{
|
||||
var orders = Result ?? OrderList ?? List ?? new List<OrderDto>();
|
||||
|
||||
// 合并物流信息到订单
|
||||
if (Logisticses != null && Logisticses.Count > 0)
|
||||
{
|
||||
var logisticsMap = new Dictionary<int, LogisticsInfo>();
|
||||
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<int, List<GoodsItem>>();
|
||||
foreach (var item in Goods)
|
||||
{
|
||||
if (!goodsMap.ContainsKey(item.OrderId))
|
||||
goodsMap[item.OrderId] = new List<GoodsItem>();
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,139 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<RootNamespace>PackagingMallShipper</RootNamespace>
|
||||
<AssemblyName>PackagingMallShipper</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>false</UseWindowsForms>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<ApplicationIcon>Resources\Icons\app.ico</ApplicationIcon>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
|
||||
<!-- 版本信息 -->
|
||||
<Version>1.0.0</Version>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<Company>PackagingMall</Company>
|
||||
<Product>包装商城发货助手</Product>
|
||||
<Copyright>Copyright © 2025</Copyright>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Xaml">
|
||||
<RequiredTargetFramework>4.0</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
<Page Include="Views\MainWindow.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\LoginWindow.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\OrderListView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\ShippingDialog.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Compile Include="App.xaml.cs">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Converters\BoolToVisibilityConverter.cs" />
|
||||
<Compile Include="Converters\StatusToColorConverter.cs" />
|
||||
<Compile Include="Converters\StatusToTextConverter.cs" />
|
||||
<Compile Include="Data\SqliteHelper.cs" />
|
||||
<Compile Include="Helpers\AppConfig.cs" />
|
||||
<Compile Include="Helpers\ExpressCompanies.cs" />
|
||||
<Compile Include="Helpers\ResourceHelper.cs" />
|
||||
<Compile Include="Models\ApiResponses.cs" />
|
||||
<Compile Include="Models\LocalSession.cs" />
|
||||
<Compile Include="Models\Order.cs" />
|
||||
<Compile Include="Models\ShipModels.cs" />
|
||||
<Compile Include="Models\SyncLog.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Services\AuthService.cs" />
|
||||
<Compile Include="Services\ExcelService.cs" />
|
||||
<Compile Include="Services\Interfaces.cs" />
|
||||
<Compile Include="Services\OrderService.cs" />
|
||||
<Compile Include="Services\ShipService.cs" />
|
||||
<Compile Include="Services\SyncService.cs" />
|
||||
<Compile Include="ViewModels\LoginViewModel.cs" />
|
||||
<Compile Include="ViewModels\MainViewModel.cs" />
|
||||
<Compile Include="ViewModels\OrderListViewModel.cs" />
|
||||
<Compile Include="ViewModels\ViewModelBase.cs" />
|
||||
<Compile Include="Views\LoginWindow.xaml.cs">
|
||||
<DependentUpon>LoginWindow.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\MainWindow.xaml.cs">
|
||||
<DependentUpon>MainWindow.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\OrderListView.xaml.cs">
|
||||
<DependentUpon>OrderListView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\ShippingDialog.xaml.cs">
|
||||
<DependentUpon>ShippingDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Data\Resources\schema.sql" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons\app.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
<PackageReference Include="System.Data.SQLite" Version="1.0.118" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="ClosedXML" Version="0.102.2" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
||||
<!-- 将所有DLL打包到EXE中 -->
|
||||
<PackageReference Include="Costura.Fody" Version="5.7.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Configuration" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Data\Resources\schema.sql" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="App.config">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
BIN
PackagingMallShipper/Resources/Icons/app.ico
Normal file
BIN
PackagingMallShipper/Resources/Icons/app.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
60
PackagingMallShipper/Resources/Icons/app.svg
Normal file
60
PackagingMallShipper/Resources/Icons/app.svg
Normal file
@@ -0,0 +1,60 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<linearGradient id="boxGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#FF9800;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#F57C00;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="boxSide" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#E65100;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#EF6C00;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="boxTop" x1="0%" y1="100%" x2="0%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#FFB74D;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#FFA726;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="arrowGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#4CAF50;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#388E3C;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- 背景圆形 -->
|
||||
<circle cx="256" cy="256" r="240" fill="#FFF3E0"/>
|
||||
|
||||
<!-- 包装盒主体 - 正面 -->
|
||||
<path d="M100 200 L256 280 L256 420 L100 340 Z" fill="url(#boxGradient)"/>
|
||||
|
||||
<!-- 包装盒主体 - 侧面 -->
|
||||
<path d="M256 280 L412 200 L412 340 L256 420 Z" fill="url(#boxSide)"/>
|
||||
|
||||
<!-- 包装盒顶部 -->
|
||||
<path d="M100 200 L256 120 L412 200 L256 280 Z" fill="url(#boxTop)"/>
|
||||
|
||||
<!-- 盒子封口线 -->
|
||||
<line x1="256" y1="120" x2="256" y2="280" stroke="#E65100" stroke-width="3"/>
|
||||
<line x1="100" y1="200" x2="412" y2="200" stroke="#E65100" stroke-width="2"/>
|
||||
|
||||
<!-- 胶带 -->
|
||||
<rect x="220" y="105" width="72" height="25" rx="3" fill="#8D6E63"/>
|
||||
<rect x="224" y="109" width="64" height="17" rx="2" fill="#A1887F"/>
|
||||
|
||||
<!-- 发货箭头 -->
|
||||
<g transform="translate(320, 80)">
|
||||
<!-- 箭头主体 -->
|
||||
<path d="M0 60 L40 60 L40 40 L80 70 L40 100 L40 80 L0 80 Z" fill="url(#arrowGradient)"/>
|
||||
<!-- 箭头高光 -->
|
||||
<path d="M0 60 L40 60 L40 40 L50 50 L10 50 L10 70 L0 70 Z" fill="#66BB6A" opacity="0.5"/>
|
||||
</g>
|
||||
|
||||
<!-- 商城标志 - 购物车图标简化版 -->
|
||||
<g transform="translate(130, 290)">
|
||||
<circle cx="20" cy="60" r="8" fill="#5D4037"/>
|
||||
<circle cx="50" cy="60" r="8" fill="#5D4037"/>
|
||||
<path d="M5 10 L15 10 L25 45 L55 45 L60 25 L20 25" stroke="#5D4037" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
|
||||
<!-- 文字装饰线 -->
|
||||
<rect x="310" y="300" width="60" height="6" rx="3" fill="#FFCC80"/>
|
||||
<rect x="310" y="315" width="45" height="6" rx="3" fill="#FFCC80"/>
|
||||
<rect x="310" y="330" width="55" height="6" rx="3" fill="#FFCC80"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -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<LoginResult> LoginAsync(string mobile, string password)
|
||||
public string GetCaptchaUrl(string key)
|
||||
{
|
||||
return $"https://user.api.it120.cc/code?k={Uri.EscapeDataString(key)}";
|
||||
}
|
||||
|
||||
public async Task<LoginResult> 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<ApiResponse<LoginData>>(json);
|
||||
|
||||
// 详细日志:API响应
|
||||
System.Diagnostics.Debug.WriteLine($"[登录响应] HTTP状态码: {response.StatusCode}");
|
||||
System.Diagnostics.Debug.WriteLine($"[登录响应] JSON原始内容: {json}");
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ApiResponse<AdminLoginData>>(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<ApiResponse<UserInfo>>(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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -8,7 +8,8 @@ namespace PackagingMallShipper.Services
|
||||
{
|
||||
public interface IAuthService
|
||||
{
|
||||
Task<LoginResult> LoginAsync(string mobile, string password);
|
||||
Task<LoginResult> LoginAsync(string userName, string password, string captcha = null, string captchaKey = null);
|
||||
string GetCaptchaUrl(string key);
|
||||
string GetToken();
|
||||
bool IsLoggedIn { get; }
|
||||
LocalSession CurrentSession { get; }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SQLite;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -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<ApiResponse<OrderListData>>(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<OrderDto>();
|
||||
// 使用辅助方法获取订单列表(自动合并物流和商品信息)
|
||||
var orders = data.Data?.GetOrders() ?? new List<OrderDto>();
|
||||
totalPages = data.Data?.TotalPage ?? 1;
|
||||
var totalRow = data.Data?.GetTotalRow() ?? 0;
|
||||
|
||||
Debug.WriteLine($"[同步响应] 订单数量: {orders.Count}, 总记录: {totalRow}, 总页数: {totalPages}");
|
||||
|
||||
if (orders.Count == 0) break;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
<TextBlock Text="手机号" Margin="0,0,0,5" FontSize="13"/>
|
||||
<TextBox x:Name="MobileTextBox"
|
||||
Text="{Binding Mobile, UpdateSourceTrigger=PropertyChanged}"
|
||||
<TextBlock Text="用户名/手机号" Margin="0,0,0,5" FontSize="13"/>
|
||||
<TextBox x:Name="UserNameTextBox"
|
||||
Text="{Binding UserName, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="35" FontSize="14" Padding="10,5"
|
||||
Margin="0,0,0,15"/>
|
||||
|
||||
|
||||
<TextBlock Text="密码" Margin="0,0,0,5" FontSize="13"/>
|
||||
<PasswordBox x:Name="PasswordBox"
|
||||
Height="35" FontSize="14" Padding="10,5"
|
||||
Margin="0,0,0,10"
|
||||
Margin="0,0,0,15"
|
||||
PasswordChanged="PasswordBox_PasswordChanged"/>
|
||||
|
||||
<CheckBox Content="记住手机号"
|
||||
|
||||
<!-- 验证码区域 -->
|
||||
<StackPanel x:Name="CaptchaPanel"
|
||||
Visibility="{Binding ShowCaptcha, Converter={StaticResource BoolToVisibility}}"
|
||||
Margin="0,0,0,15">
|
||||
<TextBlock Text="验证码" Margin="0,0,0,5" FontSize="13"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBox x:Name="CaptchaTextBox"
|
||||
Text="{Binding Captcha, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="35" FontSize="14" Padding="10,5"
|
||||
Grid.Column="0" Margin="0,0,10,0"/>
|
||||
|
||||
<Border Grid.Column="1" BorderBrush="#CCCCCC" BorderThickness="1" CornerRadius="2">
|
||||
<Image x:Name="CaptchaImage"
|
||||
Source="{Binding CaptchaImageSource}"
|
||||
Stretch="Fill"
|
||||
MouseLeftButtonDown="CaptchaImage_Click"
|
||||
Cursor="Hand"
|
||||
ToolTip="点击刷新验证码"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox Content="记住用户名"
|
||||
IsChecked="{Binding RememberMe}"
|
||||
Margin="0,0,0,20"/>
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -77,10 +77,12 @@
|
||||
Width="90" Height="30" Margin="0,0,5,0"
|
||||
IsEnabled="{Binding IsBusy, Converter={StaticResource InverseBool}}"/>
|
||||
|
||||
<Button Content="📥 导入发货" Command="{Binding ImportAndShipCommand}"
|
||||
<!-- 暂时禁用导入发货功能
|
||||
<Button Content="📥 导入发货" Command="{Binding ImportAndShipCommand}"
|
||||
Width="90" Height="30"
|
||||
Background="#52C41A" Foreground="White" BorderThickness="0"
|
||||
IsEnabled="{Binding IsBusy, Converter={StaticResource InverseBool}}"/>
|
||||
-->
|
||||
</StackPanel>
|
||||
|
||||
<!-- 订单列表 -->
|
||||
|
||||
28
README.md
28
README.md
@@ -31,16 +31,28 @@
|
||||
|
||||
## 开发环境
|
||||
|
||||
- Visual Studio 2019 Community
|
||||
- Visual Studio 2019+ 或 .NET SDK 8.0+
|
||||
- .NET Framework 4.8 开发工具
|
||||
|
||||
## 编译步骤
|
||||
|
||||
1. 使用 Visual Studio 2019 打开 `PackagingMallShipper.sln`
|
||||
### 方式一:命令行编译(推荐)
|
||||
|
||||
```bash
|
||||
# 还原依赖并编译
|
||||
dotnet build PackagingMallShipper.sln -c Release
|
||||
|
||||
# 输出文件位于
|
||||
bin/Release/net48/PackagingMallShipper.exe
|
||||
```
|
||||
|
||||
### 方式二:Visual Studio
|
||||
|
||||
1. 使用 Visual Studio 2019+ 打开 `PackagingMallShipper.sln`
|
||||
2. 还原 NuGet 包
|
||||
3. 选择 `Release` 配置
|
||||
4. 生成解决方案
|
||||
5. 输出文件位于 `bin/Release/` 目录
|
||||
5. 输出文件位于 `bin/Release/net48/` 目录
|
||||
|
||||
## 配置说明
|
||||
|
||||
@@ -49,8 +61,8 @@
|
||||
```xml
|
||||
<appSettings>
|
||||
<!-- API工厂配置 -->
|
||||
<add key="ApiBaseUrl" value="https://user.api.it120.cc" />
|
||||
<add key="SubDomain" value="vv125s" />
|
||||
<add key="ApiBaseUrl" value="https://api.it120.cc" />
|
||||
<add key="SubDomain" value="let5see" />
|
||||
|
||||
<!-- 同步配置 -->
|
||||
<add key="SyncPageSize" value="50" />
|
||||
@@ -64,7 +76,11 @@
|
||||
|
||||
## 使用流程
|
||||
|
||||
1. **登录** - 使用API工厂账号登录
|
||||
1. **登录** - 使用API工厂子账号登录
|
||||
- 输入用户名或手机号
|
||||
- 输入密码
|
||||
- 输入图形验证码(点击图片可刷新)
|
||||
- 首次登录建议勾选"记住用户名"
|
||||
2. **同步订单** - 点击"同步订单"获取最新订单
|
||||
3. **查看订单** - 切换状态筛选,搜索订单
|
||||
4. **发货方式一** - 导出Excel → 填写快递信息 → 导入发货
|
||||
|
||||
58
build_installer.bat
Normal file
58
build_installer.bat
Normal file
@@ -0,0 +1,58 @@
|
||||
@echo off
|
||||
chcp 936 >nul
|
||||
echo ========================================
|
||||
echo PackagingMallShipper - Build Installer
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
set "ISCC="
|
||||
if exist "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" (
|
||||
set "ISCC=C:\Program Files (x86)\Inno Setup 6\ISCC.exe"
|
||||
)
|
||||
if exist "C:\Program Files\Inno Setup 6\ISCC.exe" (
|
||||
set "ISCC=C:\Program Files\Inno Setup 6\ISCC.exe"
|
||||
)
|
||||
|
||||
if "%ISCC%"=="" (
|
||||
echo [Error] Inno Setup 6 not found
|
||||
echo.
|
||||
echo Please download and install Inno Setup 6:
|
||||
echo https://jrsoftware.org/isdl.php
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [Info] Found Inno Setup: %ISCC%
|
||||
echo.
|
||||
|
||||
if not exist "publish\PackagingMallShipper.exe" (
|
||||
echo [Error] publish\PackagingMallShipper.exe not found
|
||||
echo.
|
||||
echo Please build Release version first:
|
||||
echo dotnet build -c Release
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [Info] Compiling installer...
|
||||
echo.
|
||||
|
||||
"%ISCC%" setup.iss
|
||||
|
||||
if %ERRORLEVEL% EQU 0 (
|
||||
echo.
|
||||
echo ========================================
|
||||
echo [Success] Installer created!
|
||||
echo.
|
||||
echo Location: installer\PackagingMallShipper_Setup_v1.0.0.exe
|
||||
echo ========================================
|
||||
echo.
|
||||
if exist "installer" explorer "installer"
|
||||
) else (
|
||||
echo.
|
||||
echo [Error] Build failed, error code: %ERRORLEVEL%
|
||||
)
|
||||
|
||||
pause
|
||||
148
setup.iss
Normal file
148
setup.iss
Normal file
@@ -0,0 +1,148 @@
|
||||
; Inno Setup Script for 包装商城发货助手
|
||||
; Requires Inno Setup 6.x
|
||||
|
||||
#define MyAppName "包装商城发货助手"
|
||||
#define MyAppVersion "1.0.0"
|
||||
#define MyAppPublisher "PackagingMall"
|
||||
#define MyAppExeName "PackagingMallShipper.exe"
|
||||
|
||||
[Setup]
|
||||
; 应用程序信息
|
||||
AppId={{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
DefaultDirName={autopf}\{#MyAppName}
|
||||
DefaultGroupName={#MyAppName}
|
||||
AllowNoIcons=yes
|
||||
; 输出设置
|
||||
OutputDir=installer
|
||||
OutputBaseFilename=PackagingMallShipper_Setup_v{#MyAppVersion}
|
||||
; 压缩设置
|
||||
Compression=lzma2/ultra64
|
||||
SolidCompression=yes
|
||||
; 权限设置
|
||||
PrivilegesRequired=admin
|
||||
; 系统要求 - Windows 7 SP1 或更高版本
|
||||
MinVersion=6.1.7601
|
||||
; 安装向导设置
|
||||
WizardStyle=modern
|
||||
DisableProgramGroupPage=yes
|
||||
; 卸载设置
|
||||
UninstallDisplayName={#MyAppName}
|
||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||
|
||||
[Languages]
|
||||
Name: "chinesesimplified"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "创建桌面快捷方式"; GroupDescription: "附加选项:"; Flags: checkedonce
|
||||
|
||||
[Files]
|
||||
; 主程序
|
||||
Source: "publish\PackagingMallShipper.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "publish\PackagingMallShipper.exe.config"; DestDir: "{app}"; Flags: ignoreversion
|
||||
; SQLite 本地库
|
||||
Source: "publish\x64\SQLite.Interop.dll"; DestDir: "{app}\x64"; Flags: ignoreversion
|
||||
Source: "publish\x86\SQLite.Interop.dll"; DestDir: "{app}\x86"; Flags: ignoreversion
|
||||
; .NET Framework 4.8 离线安装包(可选,取消注释以包含)
|
||||
; Source: "redist\ndp48-x86-x64-allos-enu.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall; Check: not IsDotNetInstalled
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||
Name: "{group}\卸载 {#MyAppName}"; Filename: "{uninstallexe}"
|
||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
; 安装完成后运行程序
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "运行 {#MyAppName}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[Code]
|
||||
// 检查 .NET Framework 4.8 是否已安装
|
||||
function IsDotNetInstalled: Boolean;
|
||||
var
|
||||
Release: Cardinal;
|
||||
begin
|
||||
Result := False;
|
||||
// .NET Framework 4.8 的 Release 值为 528040 或更高
|
||||
if RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', Release) then
|
||||
begin
|
||||
Result := (Release >= 528040);
|
||||
end;
|
||||
end;
|
||||
|
||||
// 获取 .NET Framework 版本描述
|
||||
function GetDotNetVersionStr: String;
|
||||
var
|
||||
Release: Cardinal;
|
||||
begin
|
||||
Result := '未安装';
|
||||
if RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', Release) then
|
||||
begin
|
||||
if Release >= 533320 then
|
||||
Result := '4.8.1 或更高'
|
||||
else if Release >= 528040 then
|
||||
Result := '4.8'
|
||||
else if Release >= 461808 then
|
||||
Result := '4.7.2'
|
||||
else if Release >= 461308 then
|
||||
Result := '4.7.1'
|
||||
else if Release >= 460798 then
|
||||
Result := '4.7'
|
||||
else if Release >= 394802 then
|
||||
Result := '4.6.2'
|
||||
else if Release >= 394254 then
|
||||
Result := '4.6.1'
|
||||
else if Release >= 393295 then
|
||||
Result := '4.6'
|
||||
else
|
||||
Result := '4.5.x 或更低';
|
||||
end;
|
||||
end;
|
||||
|
||||
// 初始化安装向导
|
||||
function InitializeSetup: Boolean;
|
||||
var
|
||||
ErrorCode: Integer;
|
||||
NetVersion: String;
|
||||
begin
|
||||
Result := True;
|
||||
|
||||
// 检查 .NET Framework
|
||||
if not IsDotNetInstalled then
|
||||
begin
|
||||
NetVersion := GetDotNetVersionStr;
|
||||
|
||||
if MsgBox('此程序需要 .NET Framework 4.8 才能运行。' + #13#10 + #13#10 +
|
||||
'当前检测到的版本: ' + NetVersion + #13#10 + #13#10 +
|
||||
'是否要打开微软官网下载 .NET Framework 4.8?' + #13#10 +
|
||||
'(下载完成后请先安装运行时,再重新运行此安装程序)',
|
||||
mbConfirmation, MB_YESNO) = IDYES then
|
||||
begin
|
||||
// 打开 .NET Framework 4.8 下载页面
|
||||
ShellExec('open', 'https://dotnet.microsoft.com/download/dotnet-framework/net48', '', '', SW_SHOW, ewNoWait, ErrorCode);
|
||||
end;
|
||||
|
||||
Result := MsgBox('是否仍要继续安装?' + #13#10 + #13#10 +
|
||||
'注意:如果没有安装 .NET Framework 4.8,程序将无法运行。',
|
||||
mbConfirmation, MB_YESNO) = IDYES;
|
||||
end;
|
||||
end;
|
||||
|
||||
// 卸载时清理数据目录(可选)
|
||||
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
||||
var
|
||||
DataDir: String;
|
||||
begin
|
||||
if CurUninstallStep = usPostUninstall then
|
||||
begin
|
||||
DataDir := ExpandConstant('{localappdata}\PackagingMallShipper');
|
||||
if DirExists(DataDir) then
|
||||
begin
|
||||
if MsgBox('是否删除程序数据(包括本地缓存的订单数据)?', mbConfirmation, MB_YESNO) = IDYES then
|
||||
begin
|
||||
DelTree(DataDir, True, True, True);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
Reference in New Issue
Block a user