Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ae90f81b4 | |||
| 024cdfb2d5 | |||
| b09dbbe63f |
+493
-7
@@ -26,6 +26,7 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
@@ -39,6 +40,9 @@ namespace 善学教育积分卡汇率计算器
|
||||
private static readonly string[] NtpServers = { "ntp.aliyun.com", "ntp.ntsc.ac.cn", "pool.ntp.org" };
|
||||
private static readonly string RateTextHorizontal = BuildRateText(true);
|
||||
private static readonly string RateTextVertical = BuildRateText(false);
|
||||
private const string CurrentVersion = "1.2.3";
|
||||
private const string ReleasePageUrl = "https://gittea.dev/ZerkyLiu/Shanxue-Education-Points-Coin-Converter/releases";
|
||||
private const string DownloadUrlTemplate = "https://gittea.dev/ZerkyLiu/Shanxue-Education-Points-Coin-Converter/releases/download/{0}/善学教育积分卡汇率计算器.exe";
|
||||
private readonly Label _currencyLabel;
|
||||
private readonly Label _timeLabel;
|
||||
private readonly Label _timeSourceLabel;
|
||||
@@ -62,6 +66,8 @@ namespace 善学教育积分卡汇率计算器
|
||||
private bool _usingUiAccessTopMost;
|
||||
private bool _suppressPointsTextChange;
|
||||
private readonly bool _forceFallbackTopMost;
|
||||
private Form? _aboutDialog;
|
||||
private Form? _floatingPrompt;
|
||||
private const string UiAccessRelaunchArg = "--uiaccess-relaunch";
|
||||
private const string UiAccessResourceSuffix = ".dll.uiaccess.dll";
|
||||
private const string IconResourceSuffix = ".ico.logo_256x256.ico";
|
||||
@@ -691,16 +697,35 @@ namespace 善学教育积分卡汇率计算器
|
||||
|
||||
private void ShowAboutDialog()
|
||||
{
|
||||
if (_aboutDialog != null && !_aboutDialog.IsDisposed)
|
||||
{
|
||||
_aboutDialog.Show();
|
||||
_aboutDialog.BringToFront();
|
||||
return;
|
||||
}
|
||||
|
||||
var dialog = new Form
|
||||
{
|
||||
Text = "关于",
|
||||
StartPosition = FormStartPosition.CenterParent,
|
||||
StartPosition = FormStartPosition.Manual,
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog,
|
||||
MaximizeBox = false,
|
||||
MinimizeBox = false,
|
||||
ShowInTaskbar = false,
|
||||
ClientSize = new Size(520, 420)
|
||||
};
|
||||
_aboutDialog = dialog;
|
||||
dialog.FormClosed += (_, __) => _aboutDialog = null;
|
||||
|
||||
var layout = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 1,
|
||||
RowCount = 2
|
||||
};
|
||||
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||
|
||||
var textBox = new TextBox
|
||||
{
|
||||
@@ -720,7 +745,7 @@ namespace 善学教育积分卡汇率计算器
|
||||
textBox.Text = string.Join(Environment.NewLine, new[]
|
||||
{
|
||||
"善学教育积分卡汇率计算器",
|
||||
"版本号: 1.0.0",
|
||||
$"版本号: {CurrentVersion}",
|
||||
"作者: ZerkyLiu",
|
||||
$"构建时间:{buildTime}",
|
||||
"运行环境:.NET 4.7",
|
||||
@@ -732,14 +757,454 @@ namespace 善学教育积分卡汇率计算器
|
||||
"• 网络时间同步",
|
||||
"• 高精度阶梯换算",
|
||||
"• UIAccess 置顶支持",
|
||||
"• 联网自动更新",
|
||||
"",
|
||||
"未来计划:",
|
||||
"• 联网自动更新",
|
||||
"• 修复已知问题"
|
||||
});
|
||||
|
||||
dialog.Controls.Add(textBox);
|
||||
dialog.ShowDialog(this);
|
||||
var updateButton = new Button
|
||||
{
|
||||
Text = "检查更新",
|
||||
AutoSize = true,
|
||||
Anchor = AnchorStyles.Right
|
||||
};
|
||||
updateButton.Click += async (_, __) => await HandleUpdateCheckAsync(updateButton);
|
||||
|
||||
var buttonPanel = new Panel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
Height = updateButton.Height + 12,
|
||||
Padding = new Padding(0, 8, 0, 0)
|
||||
};
|
||||
updateButton.Location = new Point(buttonPanel.Width - updateButton.Width, 0);
|
||||
updateButton.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||||
buttonPanel.Controls.Add(updateButton);
|
||||
buttonPanel.Resize += (_, __) =>
|
||||
{
|
||||
updateButton.Location = new Point(buttonPanel.Width - updateButton.Width, 0);
|
||||
};
|
||||
|
||||
layout.Controls.Add(textBox, 0, 0);
|
||||
layout.Controls.Add(buttonPanel, 0, 1);
|
||||
dialog.Controls.Add(layout);
|
||||
CenterFormOnScreen(dialog, this);
|
||||
dialog.Show();
|
||||
dialog.Activate();
|
||||
}
|
||||
|
||||
private async Task HandleUpdateCheckAsync(Button button)
|
||||
{
|
||||
if (!button.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var owner = button.FindForm() ?? this;
|
||||
var originalText = button.Text;
|
||||
button.Enabled = false;
|
||||
button.Text = "检查更新中…";
|
||||
|
||||
try
|
||||
{
|
||||
var releaseInfo = await GetLatestReleaseInfoAsync();
|
||||
if (releaseInfo == null || string.IsNullOrEmpty(releaseInfo.Tag))
|
||||
{
|
||||
await ShowNonModalMessageAsync(owner, "无法获取最新版本信息,请稍后重试。", "更新检查", false);
|
||||
return;
|
||||
}
|
||||
|
||||
var latestVersion = NormalizeVersion(releaseInfo.Tag);
|
||||
var currentVersion = NormalizeVersion(CurrentVersion);
|
||||
if (CompareVersions(currentVersion, latestVersion) >= 0)
|
||||
{
|
||||
await ShowNonModalMessageAsync(owner, "已经是最新版本。", "更新检查", false);
|
||||
return;
|
||||
}
|
||||
|
||||
var consent = await ShowNonModalConfirmAsync(owner, $"检测到新版本 {releaseInfo.Tag},是否下载?", "发现新版本");
|
||||
if (consent != DialogResult.Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var downloadUrl = releaseInfo.DownloadUrl ?? string.Empty;
|
||||
if (string.IsNullOrEmpty(downloadUrl))
|
||||
{
|
||||
downloadUrl = string.Format(CultureInfo.InvariantCulture, DownloadUrlTemplate, Uri.EscapeDataString(releaseInfo.Tag));
|
||||
}
|
||||
var targetPath = GetDownloadTargetPath(releaseInfo.Tag);
|
||||
button.Text = "正在下载中…";
|
||||
var progressPercent = 0;
|
||||
PaintEventHandler painter = (_, e) =>
|
||||
{
|
||||
var rect = button.ClientRectangle;
|
||||
var pad = 4;
|
||||
var barH = 4;
|
||||
var x = rect.X + pad;
|
||||
var y = rect.Bottom - barH - pad;
|
||||
var w = Math.Max(0, rect.Width - pad * 2);
|
||||
using (var back = new SolidBrush(Color.FromArgb(220, 220, 220)))
|
||||
{
|
||||
e.Graphics.FillRectangle(back, x, y, w, barH);
|
||||
}
|
||||
var fillW = (int)Math.Round(w * progressPercent / 100.0);
|
||||
using (var fill = new SolidBrush(Color.FromArgb(76, 175, 80)))
|
||||
{
|
||||
e.Graphics.FillRectangle(fill, x, y, fillW, barH);
|
||||
}
|
||||
};
|
||||
button.Paint += painter;
|
||||
try
|
||||
{
|
||||
await DownloadFileAsync(downloadUrl, targetPath, new Progress<int>(p =>
|
||||
{
|
||||
progressPercent = Math.Max(0, Math.Min(100, p));
|
||||
button.Invalidate();
|
||||
}));
|
||||
}
|
||||
finally
|
||||
{
|
||||
button.Paint -= painter;
|
||||
}
|
||||
await ShowNonModalMessageAsync(owner, $"已下载到:{targetPath}", "下载完成", false);
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = Path.GetDirectoryName(targetPath) ?? GetDownloadFolder(),
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ShowNonModalMessageAsync(owner, $"更新失败:{ex.Message}", "更新失败", false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
button.Text = originalText;
|
||||
button.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CenterFormOnScreen(Form dialog, Form reference)
|
||||
{
|
||||
var screen = Screen.FromControl(reference);
|
||||
var area = screen.WorkingArea;
|
||||
var x = area.X + (area.Width - dialog.Width) / 2;
|
||||
var y = area.Y + (area.Height - dialog.Height) / 2;
|
||||
dialog.Location = new Point(Math.Max(area.X, x), Math.Max(area.Y, y));
|
||||
}
|
||||
|
||||
private async Task ShowNonModalMessageAsync(Form owner, string text, string title, bool yesNo)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
var prompt = CreateFloatingPrompt(owner, title, text, yesNo, out var yesButton, out var noButton);
|
||||
_floatingPrompt = prompt;
|
||||
prompt.FormClosed += (_, __) =>
|
||||
{
|
||||
_floatingPrompt = null;
|
||||
tcs.TrySetResult(true);
|
||||
};
|
||||
prompt.Show();
|
||||
CenterChildOnOwner(prompt, owner);
|
||||
yesButton.Click += (_, __) => prompt.Close();
|
||||
if (yesNo)
|
||||
{
|
||||
noButton.Click += (_, __) => prompt.Close();
|
||||
}
|
||||
await tcs.Task.ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private async Task<DialogResult> ShowNonModalConfirmAsync(Form owner, string text, string title)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<DialogResult>();
|
||||
var prompt = CreateFloatingPrompt(owner, title, text, true, out var yesButton, out var noButton);
|
||||
_floatingPrompt = prompt;
|
||||
prompt.FormClosed += (_, __) =>
|
||||
{
|
||||
_floatingPrompt = null;
|
||||
if (!tcs.Task.IsCompleted)
|
||||
{
|
||||
tcs.TrySetResult(DialogResult.No);
|
||||
}
|
||||
};
|
||||
prompt.Show();
|
||||
CenterChildOnOwner(prompt, owner);
|
||||
yesButton.Click += (_, __) =>
|
||||
{
|
||||
tcs.TrySetResult(DialogResult.Yes);
|
||||
prompt.Close();
|
||||
};
|
||||
noButton.Click += (_, __) =>
|
||||
{
|
||||
tcs.TrySetResult(DialogResult.No);
|
||||
prompt.Close();
|
||||
};
|
||||
return await tcs.Task.ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private static Form CreateFloatingPrompt(Form owner, string title, string text, bool yesNo, out Button yesButton, out Button noButton)
|
||||
{
|
||||
var f = new Form
|
||||
{
|
||||
Text = title,
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog,
|
||||
StartPosition = FormStartPosition.Manual,
|
||||
MaximizeBox = false,
|
||||
MinimizeBox = false,
|
||||
ShowInTaskbar = false,
|
||||
ClientSize = new Size(380, 160)
|
||||
};
|
||||
|
||||
var layout = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 1,
|
||||
RowCount = 2
|
||||
};
|
||||
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||
|
||||
var label = new Label
|
||||
{
|
||||
Text = text,
|
||||
Dock = DockStyle.Fill,
|
||||
TextAlign = ContentAlignment.MiddleLeft,
|
||||
AutoEllipsis = true
|
||||
};
|
||||
|
||||
var panel = new FlowLayoutPanel
|
||||
{
|
||||
FlowDirection = FlowDirection.RightToLeft,
|
||||
Dock = DockStyle.Fill,
|
||||
Padding = new Padding(0, 8, 0, 0),
|
||||
AutoSize = true
|
||||
};
|
||||
|
||||
yesButton = new Button { Text = yesNo ? "是" : "确定", AutoSize = true };
|
||||
noButton = new Button { Text = "否", AutoSize = true };
|
||||
panel.Controls.Add(yesButton);
|
||||
if (yesNo)
|
||||
{
|
||||
panel.Controls.Add(noButton);
|
||||
}
|
||||
|
||||
layout.Controls.Add(label, 0, 0);
|
||||
layout.Controls.Add(panel, 0, 1);
|
||||
f.Controls.Add(layout);
|
||||
return f;
|
||||
}
|
||||
|
||||
private static void CenterChildOnOwner(Form child, Form owner)
|
||||
{
|
||||
var rect = owner.Bounds;
|
||||
var x = rect.X + (rect.Width - child.Width) / 2;
|
||||
var y = rect.Y + (rect.Height - child.Height) / 2;
|
||||
child.Location = new Point(Math.Max(0, x), Math.Max(0, y));
|
||||
}
|
||||
private static string GetDownloadFolder()
|
||||
{
|
||||
var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
var path = string.IsNullOrWhiteSpace(userProfile) ? string.Empty : Path.Combine(userProfile, "Downloads");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
path = Path.GetTempPath();
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
private static string GetDownloadTargetPath(string versionTag)
|
||||
{
|
||||
var folder = GetDownloadFolder();
|
||||
return Path.Combine(folder, $"善学教育积分卡汇率计算器_{versionTag}.exe");
|
||||
}
|
||||
|
||||
private sealed class ReleaseInfo
|
||||
{
|
||||
public ReleaseInfo(string tag, string? downloadUrl)
|
||||
{
|
||||
Tag = tag;
|
||||
DownloadUrl = downloadUrl;
|
||||
}
|
||||
|
||||
public string Tag { get; }
|
||||
public string? DownloadUrl { get; }
|
||||
}
|
||||
|
||||
private async Task<ReleaseInfo?> GetLatestReleaseInfoAsync()
|
||||
{
|
||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
|
||||
var result = await TryGetLatestReleaseInfoAsync(false);
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return await TryGetLatestReleaseInfoAsync(true);
|
||||
}
|
||||
|
||||
private async Task<ReleaseInfo?> TryGetLatestReleaseInfoAsync(bool useProxy)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var client = CreateWebClient(useProxy))
|
||||
{
|
||||
var html = await client.DownloadStringTaskAsync(ReleasePageUrl);
|
||||
var tag = ParseLatestReleaseTag(html) ?? string.Empty;
|
||||
if (tag.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var tagValue = tag;
|
||||
var downloadUrl = ParseLatestDownloadUrl(html);
|
||||
if (string.IsNullOrEmpty(downloadUrl))
|
||||
{
|
||||
var tagUrl = BuildReleaseTagUrl(tagValue);
|
||||
var tagHtml = await client.DownloadStringTaskAsync(tagUrl);
|
||||
downloadUrl = ParseLatestDownloadUrl(tagHtml);
|
||||
}
|
||||
|
||||
return new ReleaseInfo(tagValue, downloadUrl);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string? ParseLatestReleaseTag(string html)
|
||||
{
|
||||
if (string.IsNullOrEmpty(html))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var match = Regex.Match(html, "/releases/tag/([^\"/<>]+)");
|
||||
if (!match.Success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Uri.UnescapeDataString(match.Groups[1].Value);
|
||||
}
|
||||
|
||||
private static string? ParseLatestDownloadUrl(string html)
|
||||
{
|
||||
if (string.IsNullOrEmpty(html))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var hrefMatches = Regex.Matches(html, "href=\"([^\"]+/releases/download/[^\"]+)\"", RegexOptions.IgnoreCase);
|
||||
foreach (Match match in hrefMatches)
|
||||
{
|
||||
var link = WebUtility.HtmlDecode(match.Groups[1].Value);
|
||||
if (link.IndexOf(".exe", StringComparison.OrdinalIgnoreCase) < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return NormalizeDownloadUrl(link);
|
||||
}
|
||||
|
||||
var rawMatch = Regex.Match(html, "/releases/download/[^\"'<>\\s]+\\.exe", RegexOptions.IgnoreCase);
|
||||
if (rawMatch.Success)
|
||||
{
|
||||
return NormalizeDownloadUrl(rawMatch.Value);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string BuildReleaseTagUrl(string tag)
|
||||
{
|
||||
var escaped = Uri.EscapeDataString(tag);
|
||||
return $"{ReleasePageUrl}/tag/{escaped}";
|
||||
}
|
||||
|
||||
private static string NormalizeDownloadUrl(string link)
|
||||
{
|
||||
var cleaned = Uri.UnescapeDataString(link);
|
||||
if (cleaned.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
var baseUri = new Uri(ReleasePageUrl);
|
||||
var resolved = new Uri(baseUri, cleaned);
|
||||
return resolved.ToString();
|
||||
}
|
||||
|
||||
private static string NormalizeVersion(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return value.Trim().TrimStart('v', 'V');
|
||||
}
|
||||
|
||||
private static int CompareVersions(string left, string right)
|
||||
{
|
||||
if (Version.TryParse(left, out var leftVersion) && Version.TryParse(right, out var rightVersion))
|
||||
{
|
||||
return leftVersion.CompareTo(rightVersion);
|
||||
}
|
||||
|
||||
return string.CompareOrdinal(left, right);
|
||||
}
|
||||
|
||||
private static async Task DownloadFileAsync(string url, string targetPath, IProgress<int>? progress = null)
|
||||
{
|
||||
using (var client = CreateWebClient(true))
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
Exception? error = null;
|
||||
client.DownloadProgressChanged += (_, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
progress?.Report(e.ProgressPercentage);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
};
|
||||
client.DownloadFileCompleted += (_, e) =>
|
||||
{
|
||||
error = e.Error;
|
||||
tcs.TrySetResult(true);
|
||||
};
|
||||
client.DownloadFileAsync(new Uri(url), targetPath);
|
||||
await tcs.Task.ConfigureAwait(true);
|
||||
if (error != null)
|
||||
{
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static WebClient CreateWebClient(bool useProxy)
|
||||
{
|
||||
var client = new WebClient();
|
||||
if (useProxy)
|
||||
{
|
||||
var proxy = WebRequest.DefaultWebProxy;
|
||||
if (proxy != null)
|
||||
{
|
||||
proxy.Credentials = CredentialCache.DefaultCredentials;
|
||||
client.Proxy = proxy;
|
||||
}
|
||||
}
|
||||
|
||||
client.Headers[HttpRequestHeader.UserAgent] = "Shanxue-Updater";
|
||||
return client;
|
||||
}
|
||||
|
||||
private bool TryRelaunchWithUiAccess()
|
||||
@@ -966,8 +1431,29 @@ namespace 善学教育积分卡汇率计算器
|
||||
return;
|
||||
}
|
||||
|
||||
TopMost = true;
|
||||
SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
var target = GetActiveTopMostTarget();
|
||||
if (target == null || !target.IsHandleCreated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetWindowPos(target.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
}
|
||||
|
||||
private Form? GetActiveTopMostTarget()
|
||||
{
|
||||
var active = Form.ActiveForm;
|
||||
if (active == null || active.IsDisposed || !active.Visible)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
if (active == this || active == _aboutDialog || active == _floatingPrompt)
|
||||
{
|
||||
return active;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
## 快速开始
|
||||
|
||||
从[发布](http://https://gittea.dev/ZerkyLiu/Shanxue-Education-Points-Coin-Converter/releases "发布")页下载程序本体。
|
||||
从[发布](http://https://gittea.dev/ZerkyLiu/Shanxue-Education-Points-Coin-Converter/releases "发布")页下载程序本体,需要.Net4.7。
|
||||
|
||||
> https://gittea.dev/ZerkyLiu/Shanxue-Education-Points-Coin-Converter/releases
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
|
||||
## 特别鸣谢
|
||||
使用了[RunUIAccess](http://https://github.com/shc0743/RunUIAccess "RunUIAccess")以实现“超级置顶”的主要功能。
|
||||
使用了[RunUIAccess](https://github.com/shc0743/RunUIAccess "RunUIAccess")以实现“超级置顶”的主要功能。
|
||||
|
||||
|
||||
## 许可证
|
||||
|
||||
Reference in New Issue
Block a user