This commit is contained in:
扫地僧 2026-04-28 01:24:44 +08:00
parent 1b6455593a
commit 0de0dc29e2
12 changed files with 189 additions and 54 deletions

223
Form1.cs
View File

@ -40,6 +40,8 @@ namespace Vmianqian
private WinPanel pageAlipay = null!;
private WinPanel pageSettings = null!;
private AntdUI.Label lblSummaryTitle = null!;
private AntdUI.Label lblSummaryDesc = null!;
private AntdUI.Label lblTopNotice = null!;
private AntdUI.Label lblWechatStatusValue = null!;
private AntdUI.Label lblAlipayStatusValue = null!;
@ -47,8 +49,15 @@ namespace Vmianqian
private AntdUI.Input txtServerUrl = null!;
private AntdUI.Input txtApiKey = null!;
private AntdUI.Input txtNotifyEmail = null!;
private AntdUI.Label lblServerUrlTitle = null!;
private AntdUI.Label lblApiKeyTitle = null!;
private AntdUI.Label lblNotifyEmailTitle = null!;
private AntdUI.Label lblHeartbeatTitle = null!;
private AntdUI.Label lblHeartbeatDesc = null!;
private AntdUI.Label lblMemberPlaceholder = null!;
private AntdUI.Switch chkHeartbeatEnabled = null!;
private AntdUI.Button btnHeartbeatCheck = null!;
private AntdUI.Button btnClearLog = null!;
private WinTextBox txtLog = null!;
private AntdUI.Input txtWechatPath = null!;
@ -84,6 +93,11 @@ namespace Vmianqian
BackColor = Color.White;
Font = new Font("Microsoft YaHei UI", 9F);
Text = "V免签 Demo";
MinimumSize = new Size(1180, 860);
if (Width < 1280 || Height < 900)
{
Size = new Size(1280, 900);
}
BuildTitlebar();
BuildBottomBar();
@ -109,15 +123,6 @@ namespace Vmianqian
SubText = "Demo"
};
lblTopNotice = new AntdUI.Label
{
Dock = DockStyle.Right,
Width = 260,
Text = "准备接入 V免签服务端通信",
TextAlign = ContentAlignment.MiddleRight,
ForeColor = Color.FromArgb(22, 119, 255)
};
lblAlipayStatusValue = new AntdUI.Label
{
Dock = DockStyle.Right,
@ -241,21 +246,106 @@ namespace Vmianqian
private WinPanel CreatePagePanel()
{
return new WinPanel
var panel = new WinPanel
{
Dock = DockStyle.Fill,
BackColor = Color.FromArgb(245, 247, 250),
AutoScroll = true
};
panel.HorizontalScroll.Enabled = false;
panel.HorizontalScroll.Visible = false;
return panel;
}
private void LayoutHomePage()
{
LayoutPageCards(pageHome, 20);
const int margin = 20;
const int gap = 16;
const int cardHeight = 360;
var pageAvailableWidth = Math.Max(0, pageHome.ClientSize.Width - margin * 2);
pageHome.SuspendLayout();
var summaryCard = FindCard(pageHome, "home-summary");
var top = 160;
try
{
if (summaryCard != null)
{
summaryCard.SuspendLayout();
summaryCard.Bounds = new Rectangle(margin, 20, pageAvailableWidth, 120);
lblSummaryTitle.Location = new Point(28, 24);
lblSummaryDesc.Location = new Point(30, 64);
lblSummaryDesc.Size = new Size(Math.Max(220, pageAvailableWidth - 60), 28);
top = summaryCard.Bottom + margin;
summaryCard.ResumeLayout(false);
}
var configCard = FindCard(pageHome, "home-config");
var memberCard = FindCard(pageHome, "home-member");
if (configCard != null && memberCard != null)
{
configCard.SuspendLayout();
memberCard.SuspendLayout();
var availableWidth = Math.Max(0, pageAvailableWidth);
var rowBottom = top + cardHeight;
var rowWidth = Math.Max(0, availableWidth - gap);
var leftWidth = rowWidth / 2;
var rightWidth = rowWidth - leftWidth;
configCard.Bounds = new Rectangle(margin, top, leftWidth, cardHeight);
memberCard.Bounds = new Rectangle(margin + leftWidth + gap, top, rightWidth, cardHeight);
const int contentLeft = 24;
const int contentRight = 24;
var contentWidth = Math.Max(220, configCard.ClientSize.Width - contentLeft - contentRight);
var inputWidth = contentWidth;
lblServerUrlTitle.Location = new Point(contentLeft, 20);
txtServerUrl.Location = new Point(contentLeft, 48);
txtServerUrl.Width = inputWidth;
lblApiKeyTitle.Location = new Point(contentLeft, 94);
txtApiKey.Location = new Point(contentLeft, 122);
txtApiKey.Width = inputWidth;
lblNotifyEmailTitle.Location = new Point(contentLeft, 168);
txtNotifyEmail.Location = new Point(contentLeft, 196);
txtNotifyEmail.Width = inputWidth;
const int actionTop = 252;
const int buttonGap = 12;
var actionButtonWidth = Math.Max(72, (contentWidth - buttonGap * 3) / 4);
btnSaveConfig.Location = new Point(24, actionTop);
btnSaveConfig.Size = new Size(actionButtonWidth, 36);
btnStartService.Location = new Point(btnSaveConfig.Right + buttonGap, actionTop);
btnStartService.Size = new Size(actionButtonWidth, 36);
btnStopService.Location = new Point(btnStartService.Right + buttonGap, actionTop);
btnStopService.Size = new Size(actionButtonWidth, 36);
btnHeartbeatCheck.Location = new Point(btnStopService.Right + buttonGap, actionTop);
btnHeartbeatCheck.Size = new Size(actionButtonWidth, 34);
const int heartbeatTop = 300;
lblHeartbeatTitle.Location = new Point(contentLeft, heartbeatTop);
chkHeartbeatEnabled.Location = new Point(98, heartbeatTop - 4);
lblHeartbeatDesc.Location = new Point(chkHeartbeatEnabled.Right + 4, heartbeatTop + 1);
lblMemberPlaceholder.Size = new Size(Math.Max(220, memberCard.ClientSize.Width - 48), 52);
var logCardTop = rowBottom + margin;
var homeLogCard = FindCard(pageHome, "home-log");
if (homeLogCard != null)
{
homeLogCard.Top = logCardTop;
}
configCard.ResumeLayout(false);
memberCard.ResumeLayout(false);
}
var logCard = FindCard(pageHome, "home-log");
if (logCard != null)
{
logCard.SuspendLayout();
logCard.Left = margin;
logCard.Width = Math.Max(0, pageHome.ClientSize.Width - margin * 2);
var bottom = pageHome.ClientSize.Height - 20;
logCard.Height = Math.Max(220, bottom - logCard.Top);
@ -263,6 +353,16 @@ namespace Vmianqian
{
txtLog.Size = new Size(Math.Max(240, logCard.ClientSize.Width - 50), Math.Max(120, logCard.ClientSize.Height - 76));
}
if (btnClearLog != null)
{
btnClearLog.Location = new Point(Math.Max(24, logCard.ClientSize.Width - btnClearLog.Width - 24), 14);
}
logCard.ResumeLayout(false);
}
}
finally
{
pageHome.ResumeLayout(false);
}
}
@ -307,7 +407,7 @@ namespace Vmianqian
private static void LayoutPageCards(WinPanel page, int margin)
{
var targetWidth = Math.Max(320, page.ClientSize.Width - margin * 2);
var targetWidth = Math.Max(0, page.ClientSize.Width - margin * 2);
foreach (Control control in page.Controls)
{
@ -333,9 +433,8 @@ namespace Vmianqian
{
var summaryCard = CreateCardPanel(new Rectangle(20, 20, 1080, 120));
summaryCard.Tag = "home-summary";
summaryCard.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
var summaryTitle = new AntdUI.Label
lblSummaryTitle = new AntdUI.Label
{
Text = "欢迎使用 V免签 PC 监听客户端",
Font = new Font("Microsoft YaHei UI", 22F, FontStyle.Regular),
@ -343,7 +442,7 @@ namespace Vmianqian
Location = new Point(28, 24)
};
var summaryDesc = new AntdUI.Label
lblSummaryDesc = new AntdUI.Label
{
Text = "当前阶段先打通“PC端本地监听 -> V免签服务端回调 -> 心跳检测”链路,整体布局按 AntdUI Demo 风格复刻。",
AutoSize = false,
@ -352,29 +451,32 @@ namespace Vmianqian
ForeColor = Color.DimGray
};
summaryCard.Controls.Add(summaryTitle);
summaryCard.Controls.Add(summaryDesc);
summaryCard.Controls.Add(lblSummaryTitle);
summaryCard.Controls.Add(lblSummaryDesc);
var configCard = CreateCardPanel(new Rectangle(20, 160, 1080, 250));
var configCard = CreateCardPanel(new Rectangle(20, 160, 1080, 360));
configCard.Tag = "home-config";
configCard.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
configCard.Controls.Add(CreateTitleLabel("V免签回调地址", 24, 20));
txtServerUrl = CreateInput(24, 48, 470, "例如https://你的域名/pay/notify");
lblServerUrlTitle = CreateTitleLabel("V免签地址", 24, 20);
configCard.Controls.Add(lblServerUrlTitle);
txtServerUrl = CreateInput(24, 48, 500, "例如https://你的域名/");
configCard.Controls.Add(txtServerUrl);
configCard.Controls.Add(CreateTitleLabel("通信密钥 / Token", 530, 20));
txtApiKey = CreateInput(530, 48, 350, "请输入与服务端约定的密钥");
lblApiKeyTitle = CreateTitleLabel("通信密钥 / Token", 24, 94);
configCard.Controls.Add(lblApiKeyTitle);
txtApiKey = CreateInput(24, 122, 500, "请输入与服务端密钥");
configCard.Controls.Add(txtApiKey);
configCard.Controls.Add(CreateTitleLabel("通知邮箱", 24, 102));
txtNotifyEmail = CreateInput(24, 130, 260, "可选");
lblNotifyEmailTitle = CreateTitleLabel("通知邮箱", 24, 168);
configCard.Controls.Add(lblNotifyEmailTitle);
txtNotifyEmail = CreateInput(24, 196, 500, "可选");
configCard.Controls.Add(txtNotifyEmail);
btnSaveConfig = new AntdUI.Button
{
Text = "保存配置",
Location = new Point(530, 128),
Type = TTypeMini.Primary,
Location = new Point(24, 252),
Size = new Size(110, 36),
};
btnSaveConfig.Click += btnSaveConfig_Click;
@ -382,7 +484,8 @@ namespace Vmianqian
btnStartService = new AntdUI.Button
{
Text = "启动监听",
Location = new Point(660, 128),
Type = TTypeMini.Primary,
Location = new Point(146, 252),
Size = new Size(110, 36),
};
btnStartService.Click += btnStart_Click;
@ -390,7 +493,8 @@ namespace Vmianqian
btnStopService = new AntdUI.Button
{
Text = "停止监听",
Location = new Point(790, 128),
Type = TTypeMini.Primary,
Location = new Point(268, 252),
Size = new Size(110, 36),
};
btnStopService.Click += btnStop_Click;
@ -399,40 +503,65 @@ namespace Vmianqian
configCard.Controls.Add(btnStartService);
configCard.Controls.Add(btnStopService);
configCard.Controls.Add(CreateTitleLabel("心跳检测", 24, 188));
lblHeartbeatTitle = CreateTitleLabel("心跳检测", 24, 300);
configCard.Controls.Add(lblHeartbeatTitle);
chkHeartbeatEnabled = new AntdUI.Switch
{
Location = new Point(24, 214),
Location = new Point(98, 296),
Size = new Size(62, 28),
AutoSize = false
};
chkHeartbeatEnabled.CheckedChanged += chkHeartbeatEnabled_CheckedChanged;
var heartbeatDesc = new AntdUI.Label
lblHeartbeatDesc = new AntdUI.Label
{
Text = "开启后后台每 30 秒自动检测一次,自动成功不写日志,失败才写。",
Text = "自动心跳",
AutoSize = true,
Location = new Point(104, 219),
Location = new Point(164, 301),
ForeColor = Color.DimGray
};
btnHeartbeatCheck = new AntdUI.Button
{
Text = "立即检测",
Location = new Point(530, 210),
Type = TTypeMini.Primary,
Location = new Point(390, 292),
Size = new Size(110, 34)
};
btnHeartbeatCheck.Click += btnHeartbeatCheck_Click;
configCard.Controls.Add(chkHeartbeatEnabled);
configCard.Controls.Add(heartbeatDesc);
configCard.Controls.Add(lblHeartbeatDesc);
configCard.Controls.Add(btnHeartbeatCheck);
var logCard = CreateCardPanel(new Rectangle(20, 430, 1080, 300));
var memberCard = CreateCardPanel(new Rectangle(660, 160, 440, 360));
memberCard.Tag = "home-member";
memberCard.Controls.Add(CreateTitleLabel("会员登录绑定", 24, 20));
lblMemberPlaceholder = new AntdUI.Label
{
Text = "预留区域:后续用于会员登录、设备绑定、解绑与状态展示。",
AutoSize = false,
Location = new Point(24, 56),
Size = new Size(392, 52),
ForeColor = Color.DimGray
};
memberCard.Controls.Add(lblMemberPlaceholder);
var logCard = CreateCardPanel(new Rectangle(20, 500, 1080, 300));
logCard.Tag = "home-log";
logCard.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
logCard.Controls.Add(CreateTitleLabel("运行日志", 24, 18));
btnClearLog = new AntdUI.Button
{
Text = "清空",
Type = TTypeMini.Primary,
Ghost = true,
Location = new Point(980, 14),
Size = new Size(72, 30),
Anchor = AnchorStyles.Top | AnchorStyles.Right
};
btnClearLog.Click += (_, _) => txtLog.Clear();
logCard.Controls.Add(btnClearLog);
txtLog = new WinTextBox
{
@ -450,6 +579,7 @@ namespace Vmianqian
pageHome.Controls.Add(summaryCard);
pageHome.Controls.Add(configCard);
pageHome.Controls.Add(memberCard);
pageHome.Controls.Add(logCard);
}
@ -598,7 +728,7 @@ namespace Vmianqian
{
Bounds = bounds,
BackColor = Color.White,
BorderStyle = BorderStyle.FixedSingle
BorderStyle = BorderStyle.None
};
}
@ -1178,7 +1308,8 @@ namespace Vmianqian
var lastPayText = FormatUnixTimestamp(heartbeatResponse.Data.LastPay);
var lastHeartText = FormatUnixTimestamp(heartbeatResponse.Data.LastHeart);
if (heartbeatResponse.Data.JkState == 1)
var monitorState = heartbeatResponse.Data.State ?? heartbeatResponse.Data.JkState;
if (monitorState == 1)
{
if (writeSuccessLog)
{
@ -1188,11 +1319,11 @@ namespace Vmianqian
return;
}
var stateText = heartbeatResponse.Data.JkState switch
var stateText = monitorState switch
{
0 => "掉线",
-1 => "未绑定监控端",
_ => $"未知({heartbeatResponse.Data.JkState?.ToString() ?? "null"})"
_ => $"未知({monitorState?.ToString() ?? "null"})"
};
Log($"心跳检测异常:状态={stateText},最后支付={lastPayText},最后心跳={lastHeartText}返回JSON={heartbeatCheckResult.ResponseBody}");
@ -1283,7 +1414,7 @@ namespace Vmianqian
{
lblTopNotice.Text = isRunning
? $"监听中:{BuildLocalListenUrl(_config)}"
: "准备接入 V免签服务端通信";
: "监听未启动";
}
private static void ValidateConfig(ClientConfig config)
@ -1418,8 +1549,8 @@ namespace Vmianqian
public sealed class ClientConfig
{
public string ServerUrl { get; set; } = "https://zf.yunzer.cn/7eed756ca37ca057370ea9bb208d25fa";
public string ApiKey { get; set; } = "7eed756ca37ca057370ea9bb208d25fa";
public string ServerUrl { get; set; } = string.Empty;
public string ApiKey { get; set; } = string.Empty;
public string NotifyEmail { get; set; } = string.Empty;
public string WechatPath { get; set; } = string.Empty;
public string WechatSid { get; set; } = string.Empty;
@ -1491,6 +1622,10 @@ namespace Vmianqian
[JsonPropertyName("lastheart")]
public long? LastHeart { get; set; }
[JsonPropertyName("state")]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public int? State { get; set; }
[JsonPropertyName("jkstate")]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public int? JkState { get; set; }

View File

@ -1,6 +1,6 @@
{
"ServerUrl": "https://zf.yunzer.cn",
"ApiKey": "7eed756ca37ca057370ea9bb208",
"ApiKey": "7eed756ca37ca057370ea9bb208d25fa",
"NotifyEmail": "1066960883@qq.com",
"WechatPath": "",
"WechatSid": "",

View File

@ -14,7 +14,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Vmianqian")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1b6455593a6213b2e6fa56d99e52d99b3ceab81b")]
[assembly: System.Reflection.AssemblyProductAttribute("Vmianqian")]
[assembly: System.Reflection.AssemblyTitleAttribute("Vmianqian")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@ -1 +1 @@
d7ed290d0d9cfd207fcc9453947b7882a6ba615b446fcf42dcec6201b8c88be8
947e2bf078e14f00a06d6a610ce423bc783b4ea6779f9a00be7651fb9ae1d425