From b28940c02d2b35d78007ad2c44c7eb5efe89ea4a Mon Sep 17 00:00:00 2001 From: chfei Date: Wed, 4 Nov 2026 18:39:51 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E7=99=BB=E5=BD=95=E8=AF=B7=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TEMUKJD/UserForm/LoginForm.cs | 894 +++++++++++++++-------------- Work/Services/CloudGoodsService.cs | 108 +--- Work/Services/ImageCacheService.cs | 70 +-- Work/Store.Api/ApiException.cs | 17 + Work/Store.Api/ApiTransport.cs | 146 +++++ Work/Store.Api/AuthApi.cs | 75 +++ Work/Store.Api/CloudGoodsApi.cs | 55 ++ Work/Store.Api/ImageCacheApi.cs | 41 ++ Work/Store.Api/StoreApiClient.cs | 50 ++ Work/Work.csproj | 1 + 10 files changed, 865 insertions(+), 592 deletions(-) create mode 100644 Work/Store.Api/ApiException.cs create mode 100644 Work/Store.Api/ApiTransport.cs create mode 100644 Work/Store.Api/AuthApi.cs create mode 100644 Work/Store.Api/CloudGoodsApi.cs create mode 100644 Work/Store.Api/ImageCacheApi.cs create mode 100644 Work/Store.Api/StoreApiClient.cs diff --git a/TEMUKJD/UserForm/LoginForm.cs b/TEMUKJD/UserForm/LoginForm.cs index fc63834..b5b6ef8 100644 --- a/TEMUKJD/UserForm/LoginForm.cs +++ b/TEMUKJD/UserForm/LoginForm.cs @@ -14,58 +14,59 @@ using System.Threading.Tasks; using System.Windows.Forms; using TemuKJD.BaseForms; using Work; +using Work.Store.Api; namespace TemuKJD.UserForm { - public partial class LoginForm : BaseForm - { + public partial class LoginForm : BaseForm + { - private Point mouseOffset;//记录鼠标指针的坐标 - private bool isMouseDown;//记录鼠标左键是否按下 - public MainForm mf; - public delegate void delegateView(); - private DevComponents.DotNetBar.PanelEx pnlRememberBox = null; - public static List UserParamList { get; set; } = new List(); + private Point mouseOffset;//记录鼠标指针的坐标 + private bool isMouseDown;//记录鼠标左键是否按下 + public MainForm mf; + public delegate void delegateView(); + private DevComponents.DotNetBar.PanelEx pnlRememberBox = null; + public static List UserParamList { get; set; } = new List(); - public LoginForm() - { - InitializeComponent(); - } + public LoginForm() + { + InitializeComponent(); + } - public void SetMobile(string m) - { - txtMobile.Text = m; - } + public void SetMobile(string m) + { + txtMobile.Text = m; + } - private void LoginForm_Load(object sender, EventArgs e) - { - if (BaseClass.MainPath == "29pdd") - { - this.pnlLoginBg.Style.BackgroundImage = Properties.Resources.pdd_bg; - this.pnlLoginBg.Style.BackgroundImagePosition = DevComponents.DotNetBar.eBackgroundImagePosition.Stretch; - //this.pbLogo.Image = Properties.Resources.pdd_logo; - } - if (BaseClass.MainPath == "29wx") - { - this.pnlLoginBg.Style.BackgroundImage = Properties.Resources.weixin_bg; - this.pnlLoginBg.Style.BackgroundImagePosition = DevComponents.DotNetBar.eBackgroundImagePosition.Stretch; - //this.pbLogo.Image = Properties.Resources.weixin_logo; - } - this.pbLogo.Image = BaseClass.LogoImage; + private void LoginForm_Load(object sender, EventArgs e) + { + if (BaseClass.MainPath == "29pdd") + { + this.pnlLoginBg.Style.BackgroundImage = Properties.Resources.pdd_bg; + this.pnlLoginBg.Style.BackgroundImagePosition = DevComponents.DotNetBar.eBackgroundImagePosition.Stretch; + //this.pbLogo.Image = Properties.Resources.pdd_logo; + } + if (BaseClass.MainPath == "29wx") + { + this.pnlLoginBg.Style.BackgroundImage = Properties.Resources.weixin_bg; + this.pnlLoginBg.Style.BackgroundImagePosition = DevComponents.DotNetBar.eBackgroundImagePosition.Stretch; + //this.pbLogo.Image = Properties.Resources.weixin_logo; + } + this.pbLogo.Image = BaseClass.LogoImage; - GetUserList(); + GetUserList(); #if DEBUG - if (File.Exists(System.Environment.CurrentDirectory + "\\choice.debug.tools")) - { - grpEnvironmentChoice.Visible = true; - pnlShowRemember.Visible = true; - } - else - { - grpEnvironmentChoice.Visible = false; - //pnlShowRemember.Visible = false; - rbtPrd.Checked = true; - } + if (File.Exists(System.Environment.CurrentDirectory + "\\choice.debug.tools")) + { + grpEnvironmentChoice.Visible = true; + pnlShowRemember.Visible = true; + } + else + { + grpEnvironmentChoice.Visible = false; + //pnlShowRemember.Visible = false; + rbtPrd.Checked = true; + } grpEnvironmentChoice.Visible = true; rbtDev.Checked = EnvironmentHelper.IsDevelopment; #else @@ -75,436 +76,461 @@ namespace TemuKJD.UserForm #endif if (BaseClass.Ico != null) - Icon = BaseClass.Ico; + Icon = BaseClass.Ico; - Text = "登录 " + BaseClass.ToolName; - if (BaseClass.LoginImage != null) - pnlLoginBg.Style.BackgroundImage = BaseClass.LoginImage; + Text = "登录 " + BaseClass.ToolName; + if (BaseClass.LoginImage != null) + pnlLoginBg.Style.BackgroundImage = BaseClass.LoginImage; - lblSignUp.Visible = BaseClass.ToolName == "跨境达-管理工具"; - lblSignUp.Visible = false; - if (FileBase.Exists("locales/ii.pak")) - { - string agentConfig = FileBase.ReadAllText("locales/ii.pak"); - if (agentConfig.Contains("\n")) - { - List lstAgentConfig = agentConfig.Split('\n').ToList(); - //0--显示 1--隐藏 - lblSignUp.Visible = lstAgentConfig[0].Trim().Equals("0"); // 是否显示注册功能 - //BaseClass.NoAnnounce = lstAgentConfig[1].Trim().Equals("1"); // 是否显示产品公告 - //if (lstAgentConfig.Count == 3) - //{ - // BaseClass.ShowGuidance = lstAgentConfig[2].Trim().Equals("0"); //是否显示使用指南 - //} - } - else - { - lblSignUp.Visible = agentConfig.Equals("0"); - } - } - else - { - lblSignUp.Visible = BaseClass.IsShowRegister; - lblBuy.Visible = BaseClass.IsShowOpenAccount; - } - lblSignUp.Visible = false; - lblBuy.Visible = false; - txtMobile.Text = BaseClass.UserName; - if (BaseClass.Remember) - { - chkRemember.Checked = true; - if (!string.IsNullOrEmpty(BaseClass.PassWord)) - password.Text = "******"; - } - LogSave.ClearFile(); + lblSignUp.Visible = BaseClass.ToolName == "跨境达-管理工具"; + lblSignUp.Visible = false; + if (FileBase.Exists("locales/ii.pak")) + { + string agentConfig = FileBase.ReadAllText("locales/ii.pak"); + if (agentConfig.Contains("\n")) + { + List lstAgentConfig = agentConfig.Split('\n').ToList(); + //0--显示 1--隐藏 + lblSignUp.Visible = lstAgentConfig[0].Trim().Equals("0"); // 是否显示注册功能 + //BaseClass.NoAnnounce = lstAgentConfig[1].Trim().Equals("1"); // 是否显示产品公告 + //if (lstAgentConfig.Count == 3) + //{ + // BaseClass.ShowGuidance = lstAgentConfig[2].Trim().Equals("0"); //是否显示使用指南 + //} + } + else + { + lblSignUp.Visible = agentConfig.Equals("0"); + } + } + else + { + lblSignUp.Visible = BaseClass.IsShowRegister; + lblBuy.Visible = BaseClass.IsShowOpenAccount; + } + lblSignUp.Visible = false; + lblBuy.Visible = false; + txtMobile.Text = BaseClass.UserName; + if (BaseClass.Remember) + { + chkRemember.Checked = true; + if (!string.IsNullOrEmpty(BaseClass.PassWord)) + password.Text = "******"; + } + LogSave.ClearFile(); - if (File.Exists("locales/wl.pak")) - { - lblSignUp.Visible = false; - lblBuy.Visible = false; - } + if (File.Exists("locales/wl.pak")) + { + lblSignUp.Visible = false; + lblBuy.Visible = false; + } - // 是否显示联系客服按钮 - if (FileBase.Exists("locales/ig.pak") || FileBase.Exists("locales/wl.pak")) - lblContactSupport.Visible = false; - else - lblContactSupport.Visible = true; - lblContactSupport.Visible = false; - } + // 是否显示联系客服按钮 + if (FileBase.Exists("locales/ig.pak") || FileBase.Exists("locales/wl.pak")) + lblContactSupport.Visible = false; + else + lblContactSupport.Visible = true; + lblContactSupport.Visible = false; + } - private void lblSignUp_Click(object sender, EventArgs e) - { - GetHtml.OpenUrl("http://dd.9u99.com/?obj=public/register"); - //RegeditForm r = new RegeditForm(); - //r.lf = this; - //r.ShowDialog(); - } + private void lblSignUp_Click(object sender, EventArgs e) + { + GetHtml.OpenUrl("http://dd.9u99.com/?obj=public/register"); + //RegeditForm r = new RegeditForm(); + //r.lf = this; + //r.ShowDialog(); + } - private void lblResetPassword_Click(object sender, EventArgs e) - { - ResetPasswordForm r = new ResetPasswordForm(); - r.ShowDialog(); - } + private void lblResetPassword_Click(object sender, EventArgs e) + { + ResetPasswordForm r = new ResetPasswordForm(); + r.ShowDialog(); + } - private void remember_CheckedChanged(object sender, EventArgs e) - { - BaseClass.Remember = chkRemember.Checked; - } + private void remember_CheckedChanged(object sender, EventArgs e) + { + BaseClass.Remember = chkRemember.Checked; + } - private void autoLogin_CheckedChanged(object sender, EventArgs e) - { - BaseClass.AutoLogin = autoLogin.Checked; - if (autoLogin.Checked) - chkRemember.Checked = true; - } + private void autoLogin_CheckedChanged(object sender, EventArgs e) + { + BaseClass.AutoLogin = autoLogin.Checked; + if (autoLogin.Checked) + chkRemember.Checked = true; + } - private void loginPanel_Click(object sender, EventArgs e) - { - if (rbtDev.Checked) - { - BaseClass.LoginApiUrl = "http://localhost:9000"; - } - else - BaseClass.LoginApiUrl = BaseClass.ServerUrl; - //"http://localhost:8000/user/user_login" + private void loginPanel_Click(object sender, EventArgs e) + { + if (rbtDev.Checked) + { + BaseClass.LoginApiUrl = "http://localhost:9000"; + } + else + BaseClass.LoginApiUrl = BaseClass.ServerUrl; + //"http://localhost:8000/user/user_login" - UserLogin ul = new UserLogin(); - ul.Mobile = txtMobile.Text; - ul.Password = password.Text; - if (txtMobile.Text.Length != 11) - { - MessageBox.Show("手机号不正确", "提示"); - txtMobile.Focus(); - return; - } - if (password.Text.Length < 6) - { - MessageBox.Show("密码长度不正确", "提示"); - password.Text = ""; - password.Focus(); - return; - } + UserLogin ul = new UserLogin(); + ul.Mobile = txtMobile.Text; + ul.Password = password.Text; + if (txtMobile.Text.Length != 11) + { + MessageBox.Show("手机号不正确", "提示"); + txtMobile.Focus(); + return; + } + if (password.Text.Length < 6) + { + MessageBox.Show("密码长度不正确", "提示"); + password.Text = ""; + password.Focus(); + return; + } - string message = ""; - int weakPassword = 0; - int lockUserName = 0; - string messageInfo = ""; - bool ok = ul.Get(ref message, out lockUserName, out weakPassword, out messageInfo); - if (ok && BaseClass.Remember) - { - var eUser = UserParamList.FirstOrDefault(u => u.UserName == BaseClass.UserName); - if (eUser != null) - UserParamList.Remove(eUser); - UserParamList.Add(new UserParam() - { - UserName = BaseClass.UserName, - PasswordMd5 = BaseClass.PassWord - }); - SaveUserList(); - } - if (!ok) - { - if (message.Contains("账户已过期")) - { - if (lblBuy.Visible == false) - BaseMsgForm.ShowError(message + ",请先联系客服商务开户!", "登录失败"); - else - { - BaseMsgForm.ShowError(message + ",请在弹出网页购买开户", "登录失败"); - GetHtml.OpenUrl("http://dd.jindouduo.com/?obj=open"); - } - } - else - BaseMsgForm.ShowError(message, "登录失败"); - return; - } - if (lockUserName == 1) - { - BaseMsgForm.ShowError(messageInfo, "账户锁定提醒", FormLevel.MEDIUM); - return; - } - //if (lockUserName == 0 && weakPassword == 1) - //{ - // BaseMsgForm.ShowError(messageInfo, "登录密码提醒", FormLevel.MEDIUM); - //} - //if (BaseClass.GroupId != 3 && BaseClass.GroupId != 4 && BaseClass.GroupId != 8 && BaseClass.GroupId != 9 && BaseClass.GroupId != 10 && BaseClass.GroupId != 11) - //{ - // BaseMsgForm.ShowError("您当前无权限登录系统", "登录失败"); - // return; - //} + string message = ""; + int weakPassword = 0; + int lockUserName = 0; + string messageInfo = ""; + bool ok = ul.Get(ref message, out lockUserName, out weakPassword, out messageInfo); + if (ok && BaseClass.Remember) + { + var eUser = UserParamList.FirstOrDefault(u => u.UserName == BaseClass.UserName); + if (eUser != null) + UserParamList.Remove(eUser); + UserParamList.Add(new UserParam() + { + UserName = BaseClass.UserName, + PasswordMd5 = BaseClass.PassWord + }); + SaveUserList(); + } + if (!ok) + { + if (message.Contains("账户已过期")) + { + if (lblBuy.Visible == false) + BaseMsgForm.ShowError(message + ",请先联系客服商务开户!", "登录失败"); + else + { + BaseMsgForm.ShowError(message + ",请在弹出网页购买开户", "登录失败"); + GetHtml.OpenUrl("http://dd.jindouduo.com/?obj=open"); + } + } + else + BaseMsgForm.ShowError(message, "登录失败"); + return; + } + if (lockUserName == 1) + { + BaseMsgForm.ShowError(messageInfo, "账户锁定提醒", FormLevel.MEDIUM); + return; + } + //if (lockUserName == 0 && weakPassword == 1) + //{ + // BaseMsgForm.ShowError(messageInfo, "登录密码提醒", FormLevel.MEDIUM); + //} + //if (BaseClass.GroupId != 3 && BaseClass.GroupId != 4 && BaseClass.GroupId != 8 && BaseClass.GroupId != 9 && BaseClass.GroupId != 10 && BaseClass.GroupId != 11) + //{ + // BaseMsgForm.ShowError("您当前无权限登录系统", "登录失败"); + // return; + //} - //this.mf.pnlShop.Visible /*= this.mf.pnlCollect.Visible*/ = this.mf.pnlTools.Visible = this.mf.pnlUpload.Visible = true; - //if (BaseClass.DyProductCollect) - //{ - // this.mf.pnlCollect.Visible = true; - //} - //mf.SetTitle(); + //this.mf.pnlShop.Visible /*= this.mf.pnlCollect.Visible*/ = this.mf.pnlTools.Visible = this.mf.pnlUpload.Visible = true; + //if (BaseClass.DyProductCollect) + //{ + // this.mf.pnlCollect.Visible = true; + //} + //mf.SetTitle(); - //if (BaseClass.GroupId == 5 || BaseClass.GroupId == 10 || BaseClass.GroupId == 11) // 上货 - //{ - // mf.ShowTaobaoLoginAndInitModule(); - // Close(); - // return; - //} - //if (BaseClass.GroupId == 4) // 客服 - //{ - // Task.Run(() => - // { - // Thread.Sleep(1000); - // Invoke(new Action(() => - // { - // ChatForms.ChatForm cf = new ChatForms.ChatForm(); - // cf.Show(); - // cf.SetTitle(); - // this.mf.Hide(); - // Hide(); - // })); - // }); - // return; - //} - //if (BaseClass.GroupId == 8) - //{ - // Process p = new Process(); - // p.StartInfo.FileName = "TaobaoLoginer.exe"; - // p.StartInfo.Arguments = BaseClass.UserName + " " + BaseClass.PassWord; - // p.StartInfo.UseShellExecute = true; - // p.StartInfo.Verb = "runas"; - // p.Start(); - // Process.GetCurrentProcess().Kill(); - // return; - //} - //if (BaseClass.GroupId == 9) - //{ - // Process p = new Process(); - // p.StartInfo.FileName = "FindLittleRabbitOrder.exe"; - // p.StartInfo.Arguments = BaseClass.UserName + " " + BaseClass.PassWord; - // p.StartInfo.UseShellExecute = true; - // p.StartInfo.Verb = "runas"; - // p.Start(); - // Process.GetCurrentProcess().Kill(); - // return; - //} + //if (BaseClass.GroupId == 5 || BaseClass.GroupId == 10 || BaseClass.GroupId == 11) // 上货 + //{ + // mf.ShowTaobaoLoginAndInitModule(); + // Close(); + // return; + //} + //if (BaseClass.GroupId == 4) // 客服 + //{ + // Task.Run(() => + // { + // Thread.Sleep(1000); + // Invoke(new Action(() => + // { + // ChatForms.ChatForm cf = new ChatForms.ChatForm(); + // cf.Show(); + // cf.SetTitle(); + // this.mf.Hide(); + // Hide(); + // })); + // }); + // return; + //} + //if (BaseClass.GroupId == 8) + //{ + // Process p = new Process(); + // p.StartInfo.FileName = "TaobaoLoginer.exe"; + // p.StartInfo.Arguments = BaseClass.UserName + " " + BaseClass.PassWord; + // p.StartInfo.UseShellExecute = true; + // p.StartInfo.Verb = "runas"; + // p.Start(); + // Process.GetCurrentProcess().Kill(); + // return; + //} + //if (BaseClass.GroupId == 9) + //{ + // Process p = new Process(); + // p.StartInfo.FileName = "FindLittleRabbitOrder.exe"; + // p.StartInfo.Arguments = BaseClass.UserName + " " + BaseClass.PassWord; + // p.StartInfo.UseShellExecute = true; + // p.StartInfo.Verb = "runas"; + // p.Start(); + // Process.GetCurrentProcess().Kill(); + // return; + //} - Hide(); - mf.Hide(); - ChangeForm c = new ChangeForm(); - c.mf = mf; - c.ShowDialog(); + + _ = Task.Run(() => + { + try + { + StoreApiClient.Instance.Auth.Login(new LoginRequest + { + UserName = BaseClass.UserName, + Password = BaseClass.PassWord + }); + } + catch (Exception ex) + { + try + { + LogSave.AddFile("Store API 登录失败:" + ex.Message); + } + catch + { + // 日志失败也不能影响主程序。 + } + } + }); - Close(); - return; - } + Hide(); + mf.Hide(); - private void panelEx1_MouseMove(object sender, MouseEventArgs e) - { - if (isMouseDown) - { - //获取鼠标的位置 - Point mousePos = MousePosition; - mousePos.Offset(mouseOffset.X, mouseOffset.Y); - Location = mousePos; - } - } + ChangeForm c = new ChangeForm(); + c.mf = mf; + c.ShowDialog(); - private void panelEx1_MouseDown(object sender, MouseEventArgs e) - { - if (e.Button == MouseButtons.Left) - { - mouseOffset = new Point(-e.X, -e.Y); - isMouseDown = true; - } - } + Close(); + return; + } - private void panelEx1_MouseUp(object sender, MouseEventArgs e) - { - isMouseDown = false; - } + private void panelEx1_MouseMove(object sender, MouseEventArgs e) + { + if (isMouseDown) + { + //获取鼠标的位置 + Point mousePos = MousePosition; + mousePos.Offset(mouseOffset.X, mouseOffset.Y); + Location = mousePos; + } + } - private void closePanel_Click(object sender, EventArgs e) - { - if (BaseMsgForm.ShowConfirm("是否关闭软件?\r\n必须登录账户才能使用,无账户请先注册一个", "请先登录或者注册", FormLevel.LARGE).DialogResult == DialogResult.OK) - Process.GetCurrentProcess().Kill(); - } + private void panelEx1_MouseDown(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + mouseOffset = new Point(-e.X, -e.Y); + isMouseDown = true; + } + } - private void password_KeyPress(object sender, KeyPressEventArgs e) - { - if (e.KeyChar == 13) - loginPanel_Click(null, null); - } + private void panelEx1_MouseUp(object sender, MouseEventArgs e) + { + isMouseDown = false; + } - private void pnlShowRemember_Click(object sender, EventArgs e) - { - if (pnlShowRemember.Text == "▲") - { - if (pnlRememberBox != null) - { - pnlRememberBox.Controls.Clear(); - pnlRememberBox.Visible = false; - } - pnlShowRemember.Text = "▼"; - return; - } + private void closePanel_Click(object sender, EventArgs e) + { + if (BaseMsgForm.ShowConfirm("是否关闭软件?\r\n必须登录账户才能使用,无账户请先注册一个", "请先登录或者注册", FormLevel.LARGE).DialogResult == DialogResult.OK) + Process.GetCurrentProcess().Kill(); + } - pnlShowRemember.Text = "▲"; + private void password_KeyPress(object sender, KeyPressEventArgs e) + { + if (e.KeyChar == 13) + loginPanel_Click(null, null); + } - ShowUserDropDown(); - } + private void pnlShowRemember_Click(object sender, EventArgs e) + { + if (pnlShowRemember.Text == "▲") + { + if (pnlRememberBox != null) + { + pnlRememberBox.Controls.Clear(); + pnlRememberBox.Visible = false; + } + pnlShowRemember.Text = "▼"; + return; + } - private void ShowUserDropDown() - { - if (pnlRememberBox == null) - { - pnlRememberBox = new DevComponents.DotNetBar.PanelEx(); - pnlRememberBox.Location = new Point(panelEx3.Location.X + txtMobile.Location.X, panelEx3.Location.Y + txtMobile.Location.Y + txtMobile.Height + 1); - pnlRememberBox.Height = 120; - pnlRememberBox.Width = pnlShowRemember.Location.X + pnlShowRemember.Width - txtMobile.Location.X; - pnlRememberBox.AutoScroll = true; - pnlRememberBox.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine; - pnlRememberBox.Style.BorderColor.Color = Color.FromArgb(210, 210, 210); - pnlRememberBox.Padding = new Padding(1); - pnlRememberBox.Style.BackColor1.Color = Color.White; - pnlRememberBox.Style.BackColor2.Color = Color.White; - pnlRememberBox.MouseLeave += PnlRememberBox_MouseLeave; + pnlShowRemember.Text = "▲"; - this.Controls.Add(pnlRememberBox); - pnlRememberBox.BringToFront(); - } + ShowUserDropDown(); + } - pnlRememberBox.Visible = true; - pnlRememberBox.Controls.Clear(); - int top = 0; - foreach (var user in UserParamList) - { - var pnlUser = new DevComponents.DotNetBar.PanelEx(); - pnlUser.Dock = DockStyle.Top; - pnlUser.Location = new Point(0, top); - pnlUser.Tag = user; - pnlUser.Height = 30; - pnlUser.Text = user.UserName; - pnlUser.Style.ForeColor.Color = Color.FromArgb(80, 80, 80); - pnlUser.Style.Font = txtMobile.Font; - pnlUser.Style.BackColor1.Color = Color.Transparent; - pnlUser.Style.BackColor2.Color = Color.Transparent; - pnlUser.Style.BorderSide = DevComponents.DotNetBar.eBorderSide.Bottom; - pnlUser.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine; - pnlUser.Style.BorderColor.Color = Color.FromArgb(225, 225, 225); - pnlUser.Click += PnlUser_Click; + private void ShowUserDropDown() + { + if (pnlRememberBox == null) + { + pnlRememberBox = new DevComponents.DotNetBar.PanelEx(); + pnlRememberBox.Location = new Point(panelEx3.Location.X + txtMobile.Location.X, panelEx3.Location.Y + txtMobile.Location.Y + txtMobile.Height + 1); + pnlRememberBox.Height = 120; + pnlRememberBox.Width = pnlShowRemember.Location.X + pnlShowRemember.Width - txtMobile.Location.X; + pnlRememberBox.AutoScroll = true; + pnlRememberBox.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine; + pnlRememberBox.Style.BorderColor.Color = Color.FromArgb(210, 210, 210); + pnlRememberBox.Padding = new Padding(1); + pnlRememberBox.Style.BackColor1.Color = Color.White; + pnlRememberBox.Style.BackColor2.Color = Color.White; + pnlRememberBox.MouseLeave += PnlRememberBox_MouseLeave; - pnlRememberBox.Controls.Add(pnlUser); + this.Controls.Add(pnlRememberBox); + pnlRememberBox.BringToFront(); + } - var pnlClose = new DevComponents.DotNetBar.PanelEx(); - pnlClose.Location = new Point(146, 7); - pnlClose.Tag = user; - pnlClose.Height = 16; - pnlClose.Width = 16; - pnlClose.Text = "x"; - pnlClose.Style.Font = txtMobile.Font; - pnlClose.Style.Alignment = StringAlignment.Center; - pnlClose.Style.ForeColor.Color = Color.FromArgb(80, 80, 80); - pnlClose.Style.BackColor1.Color = Color.Transparent; - pnlClose.Style.BackColor2.Color = Color.Transparent; - pnlClose.Style.Border = DevComponents.DotNetBar.eBorderType.None; - pnlClose.StyleMouseOver.Alignment = StringAlignment.Center; - pnlClose.StyleMouseOver.ForeColor.Color = Color.FromArgb(255, 255, 255); - pnlClose.StyleMouseOver.BackColor1.Color = Color.FromArgb(127, 127, 127); - pnlClose.StyleMouseOver.BackColor2.Color = Color.FromArgb(127, 127, 127); - pnlClose.Cursor = Cursors.Hand; - pnlClose.Click += PnlClose_Click; + pnlRememberBox.Visible = true; + pnlRememberBox.Controls.Clear(); + int top = 0; + foreach (var user in UserParamList) + { + var pnlUser = new DevComponents.DotNetBar.PanelEx(); + pnlUser.Dock = DockStyle.Top; + pnlUser.Location = new Point(0, top); + pnlUser.Tag = user; + pnlUser.Height = 30; + pnlUser.Text = user.UserName; + pnlUser.Style.ForeColor.Color = Color.FromArgb(80, 80, 80); + pnlUser.Style.Font = txtMobile.Font; + pnlUser.Style.BackColor1.Color = Color.Transparent; + pnlUser.Style.BackColor2.Color = Color.Transparent; + pnlUser.Style.BorderSide = DevComponents.DotNetBar.eBorderSide.Bottom; + pnlUser.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine; + pnlUser.Style.BorderColor.Color = Color.FromArgb(225, 225, 225); + pnlUser.Click += PnlUser_Click; - pnlUser.Controls.Add(pnlClose); + pnlRememberBox.Controls.Add(pnlUser); - top += 30; - } - } + var pnlClose = new DevComponents.DotNetBar.PanelEx(); + pnlClose.Location = new Point(146, 7); + pnlClose.Tag = user; + pnlClose.Height = 16; + pnlClose.Width = 16; + pnlClose.Text = "x"; + pnlClose.Style.Font = txtMobile.Font; + pnlClose.Style.Alignment = StringAlignment.Center; + pnlClose.Style.ForeColor.Color = Color.FromArgb(80, 80, 80); + pnlClose.Style.BackColor1.Color = Color.Transparent; + pnlClose.Style.BackColor2.Color = Color.Transparent; + pnlClose.Style.Border = DevComponents.DotNetBar.eBorderType.None; + pnlClose.StyleMouseOver.Alignment = StringAlignment.Center; + pnlClose.StyleMouseOver.ForeColor.Color = Color.FromArgb(255, 255, 255); + pnlClose.StyleMouseOver.BackColor1.Color = Color.FromArgb(127, 127, 127); + pnlClose.StyleMouseOver.BackColor2.Color = Color.FromArgb(127, 127, 127); + pnlClose.Cursor = Cursors.Hand; + pnlClose.Click += PnlClose_Click; - private void PnlClose_Click(object sender, EventArgs e) - { - var pnl = sender as DevComponents.DotNetBar.PanelEx; - if (pnl != null) - { - var user = pnl.Tag as UserParam; - if (user != null) - { - UserParamList.Remove(user); - SaveUserList(); - ShowUserDropDown(); - } - } - } + pnlUser.Controls.Add(pnlClose); - private void PnlUser_Click(object sender, EventArgs e) - { - var user = (sender as Panel)?.Tag as UserParam; - if (user != null) - { - BaseClass.UserName = user.UserName; - BaseClass.PassWord = user.PasswordMd5; - this.txtMobile.Text = user.UserName; - this.password.Text = "******"; - } - pnlRememberBox.Controls.Clear(); - pnlRememberBox.Visible = false; - pnlShowRemember.Text = "▼"; - } + top += 30; + } + } - private void PnlRememberBox_MouseLeave(object sender, EventArgs e) - { - } + private void PnlClose_Click(object sender, EventArgs e) + { + var pnl = sender as DevComponents.DotNetBar.PanelEx; + if (pnl != null) + { + var user = pnl.Tag as UserParam; + if (user != null) + { + UserParamList.Remove(user); + SaveUserList(); + ShowUserDropDown(); + } + } + } - public static void GetUserList() - { - //if (File.Exists("config\\user_login_config")) - { - try - { - //var txt = FileBase.ReadAllText("config\\user_login_config", System.Text.Encoding.UTF8); - //UserParamList = JsonHelper.DeserializeJsonToList(txt); + private void PnlUser_Click(object sender, EventArgs e) + { + var user = (sender as Panel)?.Tag as UserParam; + if (user != null) + { + BaseClass.UserName = user.UserName; + BaseClass.PassWord = user.PasswordMd5; + this.txtMobile.Text = user.UserName; + this.password.Text = "******"; + } + pnlRememberBox.Controls.Clear(); + pnlRememberBox.Visible = false; + pnlShowRemember.Text = "▼"; + } - UserParamList = NaiBa.Util.Helper.ConfigHelper.LoadConfig>("user_login_config"); - if (UserParamList == null) - UserParamList = new List(); - } - catch - { - UserParamList = new List(); - } - } - } + private void PnlRememberBox_MouseLeave(object sender, EventArgs e) + { + } - public static void SaveUserList() - { - //if (!Directory.Exists("config")) - //{ - // Directory.CreateDirectory("config"); - //} - //try - //{ - // var txt = JsonHelper.SerializeObject(UserParamList); - // FileBase.WriteAllText("config\\user_login_config", txt, System.Text.Encoding.UTF8); - //} - //catch - //{ - //} + public static void GetUserList() + { + //if (File.Exists("config\\user_login_config")) + { + try + { + //var txt = FileBase.ReadAllText("config\\user_login_config", System.Text.Encoding.UTF8); + //UserParamList = JsonHelper.DeserializeJsonToList(txt); - NaiBa.Util.Helper.ConfigHelper.SaveConfig("user_login_config", UserParamList); - } + UserParamList = NaiBa.Util.Helper.ConfigHelper.LoadConfig>("user_login_config"); + if (UserParamList == null) + UserParamList = new List(); + } + catch + { + UserParamList = new List(); + } + } + } - private void lblBuy_Click(object sender, EventArgs e) - { - GetHtml.OpenUrl("http://dd.jindouduo.com/?obj=open"); - } + public static void SaveUserList() + { + //if (!Directory.Exists("config")) + //{ + // Directory.CreateDirectory("config"); + //} + //try + //{ + // var txt = JsonHelper.SerializeObject(UserParamList); + // FileBase.WriteAllText("config\\user_login_config", txt, System.Text.Encoding.UTF8); + //} + //catch + //{ + //} - private void lblContactSupport_Click(object sender, EventArgs e) - { - SupportQrCodeForm qrForm = new SupportQrCodeForm(); - qrForm.ShowDialog(); - } - } + NaiBa.Util.Helper.ConfigHelper.SaveConfig("user_login_config", UserParamList); + } - public class UserParam - { - public string UserName { get; set; } - public string PasswordMd5 { get; set; } - } + private void lblBuy_Click(object sender, EventArgs e) + { + GetHtml.OpenUrl("http://dd.jindouduo.com/?obj=open"); + } + + private void lblContactSupport_Click(object sender, EventArgs e) + { + SupportQrCodeForm qrForm = new SupportQrCodeForm(); + qrForm.ShowDialog(); + } + } + + public class UserParam + { + public string UserName { get; set; } + public string PasswordMd5 { get; set; } + } } diff --git a/Work/Services/CloudGoodsService.cs b/Work/Services/CloudGoodsService.cs index 7bf30a5..0c64d64 100644 --- a/Work/Services/CloudGoodsService.cs +++ b/Work/Services/CloudGoodsService.cs @@ -6,7 +6,7 @@ using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; -using System.Text; +using Work.Store.Api; using System.Threading.Tasks; using Work.Api.Wx; @@ -17,35 +17,13 @@ namespace Work.Services /// public class CloudGoodsService { - private static string ServerUrl = "http://103.90.155.78:9001"; - static CloudGoodsService() - { - if (EnvironmentHelper.IsDevelopment) - { - ServerUrl = "http://localhost:14312"; - } - } public static async Task> GetGoods(GoodsSearchRequest request) { - var html = await GetHtml.PostJsonAsync(ServerUrl + "/api/CloudGoods/SearchGoods", request); - - var ret = JsonConvert.DeserializeObject>>(html); - if (ret.Code != 200) - { - throw new Exception(ret.Msg); - } - return ret.Data; + return await StoreApiClient.Instance.CloudGoods.GetGoods(request); } public static string GetGoodsDetail(long goodsId) { - var html = GetHtml.PostJson(ServerUrl + $"/api/CloudGoods/GetGoodsRawData?goodsId={goodsId}", ""); - - var ret = JsonConvert.DeserializeObject>(html); - if (ret.Code != 200) - { - throw new Exception(ret.Msg); - } - return ret.Data; + return StoreApiClient.Instance.CloudGoods.GetGoodsDetail(goodsId); } /// @@ -338,17 +316,7 @@ namespace Work.Services throw new ArgumentException("违规商品列表不能包含空项。", nameof(items)); } - var html = await GetHtml.PostJsonAsync(ServerUrl + "/api/CloudGoods/ReportViolations", items, timeOut: 5); - var ret = JsonConvert.DeserializeObject>>(html); - if (ret == null) - { - throw new Exception("批量上报违规商品返回为空。"); - } - if (ret.Code != 200) - { - throw new Exception(ret.Msg); - } - return ret.Data ?? throw new Exception("批量上报违规商品返回数据为空。"); + return await StoreApiClient.Instance.CloudGoods.ReportViolations(items); } /// @@ -361,17 +329,7 @@ namespace Work.Services throw new ArgumentNullException(nameof(goodsIds)); } - var html = GetHtml.PostJson(ServerUrl + "/api/CloudGoods/GetViolationStatus", goodsIds, timeOut: 5); - var ret = JsonConvert.DeserializeObject>>(html); - if (ret == null) - { - throw new Exception("批量查询商品违规状态返回为空。"); - } - if (ret.Code != 200) - { - throw new Exception(ret.Msg); - } - return ret.Data ?? throw new Exception("批量查询商品违规状态返回数据为空。"); + return StoreApiClient.Instance.CloudGoods.GetViolationStatus(goodsIds); } /// 逐原因记录店铺动作。日志请求失败不能改变剔除决定或中断后续下架。 @@ -424,23 +382,13 @@ namespace Work.Services { if (requests == null || requests.Count == 0 || requests.Any(item => item == null)) throw new ArgumentException("请提供有效的违规日志。", nameof(requests)); - var html = await GetHtml.PostJsonAsync(ServerUrl + "/api/CloudGoods/RecordViolationLog", requests, timeOut: 5); - var ret = JsonConvert.DeserializeObject(html) ?? throw new Exception("记录违规日志返回为空。"); - if (ret.Code != 200) - throw new Exception(ret.Msg); + await StoreApiClient.Instance.CloudGoods.RecordViolationLogs(requests); } /// 读取日志筛选和显示使用的违规原因字典。 public static async Task> GetViolationReasons() { - using (var web = new System.Net.WebClient { Encoding = Encoding.UTF8 }) - { - var html = await web.DownloadStringTaskAsync(ServerUrl + "/api/CloudGoods/GetViolationReasons"); - var ret = JsonConvert.DeserializeObject>>(html); - if (ret?.Code != 200 || ret.Data == null) - throw new Exception(ret?.Msg ?? "查询违规类型返回为空。"); - return ret.Data; - } + return await StoreApiClient.Instance.CloudGoods.GetViolationReasons(); } public class ViolationReason @@ -457,12 +405,7 @@ namespace Work.Services if (shopIds == null || shopIds.Length == 0 || shopIds.Any(string.IsNullOrWhiteSpace)) throw new ArgumentException("请提供有效的店铺编号。", nameof(shopIds)); var request = new GetViolationLogsRequest { ShopIds = shopIds, GoodsId = goodsId, BeforeId = beforeId, Size = size }; - var html = await GetHtml.PostJsonAsync(ServerUrl + "/api/CloudGoods/GetViolationLogs", request, timeOut: 5); - var ret = JsonConvert.DeserializeObject>>(html) - ?? throw new Exception("查询违规日志返回为空。"); - if (ret.Code != 200) - throw new Exception(ret.Msg); - return ret.Data ?? throw new Exception("查询违规日志返回数据为空。"); + return await StoreApiClient.Instance.CloudGoods.GetViolationLogs(request); } #region user @@ -475,14 +418,7 @@ namespace Work.Services public static async Task> SearchFavorites(UserFavoritesGoodsSearchRequest request) { request.UserName = GetUserName(); - var html = await GetHtml.PostJsonAsync(ServerUrl + "/api/CloudGoods/SearchFavorites", request); - - var ret = JsonConvert.DeserializeObject>>(html); - if (ret.Code != 200) - { - throw new Exception(ret.Msg); - } - return ret.Data; + return await StoreApiClient.Instance.CloudGoods.SearchFavorites(request); } /// @@ -494,18 +430,7 @@ namespace Work.Services public static async Task Favorite(List goodsIds) { - var html = await GetHtml.PostJsonAsync(ServerUrl + $"/api/CloudGoods/AddFavorite", new - { - UserName = GetUserName(), - GoodsIds = goodsIds, - }); - - var ret = JsonConvert.DeserializeObject>(html); - if (ret.Code != 200) - { - throw new Exception(ret.Msg); - } - return ret.Data; + return await StoreApiClient.Instance.CloudGoods.Favorite(GetUserName(), goodsIds); } /// /// 取消收藏 @@ -516,18 +441,7 @@ namespace Work.Services public static async Task RemoveFavorites(List goodsIds) { - var html = await GetHtml.PostJsonAsync(ServerUrl + $"/api/CloudGoods/RemoveFavorites", new - { - UserName = GetUserName(), - GoodsIds = goodsIds, - }); - - var ret = JsonConvert.DeserializeObject>(html); - if (ret.Code != 200) - { - throw new Exception(ret.Msg); - } - return ret.Data; + return await StoreApiClient.Instance.CloudGoods.RemoveFavorites(GetUserName(), goodsIds); } #endregion diff --git a/Work/Services/ImageCacheService.cs b/Work/Services/ImageCacheService.cs index 2acb731..9475205 100644 --- a/Work/Services/ImageCacheService.cs +++ b/Work/Services/ImageCacheService.cs @@ -1,14 +1,11 @@ using AllBase; -using AllBase.Helper; -using AllBase.Model.Result; using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Work.Store.Api; namespace Work.Services; @@ -17,17 +14,6 @@ namespace Work.Services; /// public class ImageCacheService { - private static string ServerUrl = "http://103.90.155.78:9001"; - static ImageCacheService() - { - if (EnvironmentHelper.IsDevelopment) - { - ServerUrl = "http://localhost:14312"; - } - } - - - /// /// 获取图片缓存 /// @@ -37,10 +23,6 @@ public class ImageCacheService Dictionary wxImgMapDic = new Dictionary(StringComparer.OrdinalIgnoreCase); try { - //if (EnvironmentHelper.IsProduction) - //{ - // return wxImgMapDic; - //} List imageUrls = new List(); imageUrls.AddRange(product.detail.galleries.Where(g => g.type == 1).Take(9).Select(a => a.url)); var temp_detailGalleries = product.detail.galleries.Where(g => g.type == 2).ToList(); @@ -58,16 +40,11 @@ public class ImageCacheService List imageUrlMd5s = imageUrls.Select(a => BaseClass.GetMd5(a)).ToList(); - var url = ServerUrl + "/api/ImageCache/GetImageCache";//获取缓存 - var html = GetHtml.PostJson(url, new { md5s = imageUrlMd5s }); - var ret = JsonConvert.DeserializeObject>>(html); - if (ret.Code == 200) + var caches = StoreApiClient.Instance.ImageCache.GetImageCaches(imageUrlMd5s); + foreach (var item in caches) { - foreach (var item in ret.Data) - { - item.IsCache = true; - wxImgMapDic[item.Md5] = item; - } + item.IsCache = true; + wxImgMapDic[item.Md5] = item; } } catch(Exception ex) @@ -86,38 +63,9 @@ public class ImageCacheService { try { - //if (EnvironmentHelper.IsProduction) - //{ - // return; - //} - var data = ""; - data = JsonHelper.SerializeObject(images.Where(a => a.IsCache == false).Select(a => - new - { - md5 = a.Md5, - Platform = platform, - PlatformUrl = a.PlatformUrl - } - )); - - var url = ServerUrl + "/api/ImageCache/AddOrUpdate"; - var html = GetHtml.PostJson(url, data); - - - var ret = JsonConvert.DeserializeObject>(html); - List failed = new List(); - if (ret.Code == 500) - { - if (ret.Data is JArray jArray) - { - failed = jArray.ToObject>() ?? new List(); - } - else if (ret.Data is JValue jValue && jValue.Value is string str) - { - failed.Add(str); - } - } - foreach (ImageCache item in images) + var pending = images.Where(image => !image.IsCache).ToList(); + var failed = StoreApiClient.Instance.ImageCache.SaveImageCaches((int)platform, pending); + foreach (var item in pending) { item.IsCache = !failed.Contains(item.Md5, StringComparer.OrdinalIgnoreCase); } diff --git a/Work/Store.Api/ApiException.cs b/Work/Store.Api/ApiException.cs new file mode 100644 index 0000000..1243c8d --- /dev/null +++ b/Work/Store.Api/ApiException.cs @@ -0,0 +1,17 @@ +using CsharpHttpHelper; +using Newtonsoft.Json.Linq; + +namespace Work.Store.Api; + +/// 保留业务错误数据,供批量接口识别部分失败项。 +public sealed class ApiException : HttpException +{ + public int? Code { get; } + public JToken ResponseData { get; } + + public ApiException(string message, int? code, JToken responseData) : base(message) + { + Code = code; + ResponseData = responseData; + } +} diff --git a/Work/Store.Api/ApiTransport.cs b/Work/Store.Api/ApiTransport.cs new file mode 100644 index 0000000..6b7220e --- /dev/null +++ b/Work/Store.Api/ApiTransport.cs @@ -0,0 +1,146 @@ +using CsharpHttpHelper; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text; +using System.Text.RegularExpressions; + +namespace Work.Store.Api; + +/// 管理 API 的统一 HTTP 出口,负责 JSON、Bearer 认证和错误处理。 +public sealed class ApiTransport +{ + private readonly string _baseUrl; + private string _token; + private Func _login; + internal object SessionGate { get; } = new object(); + + internal void SetSession(string token, Func login) + { + lock (SessionGate) + { + _token = token; + _login = login; + } + } + + public ApiTransport(string baseUrl) + { + if (!Uri.TryCreate(baseUrl, UriKind.Absolute, out var uri) || + (uri.Scheme != "http" && uri.Scheme != "https") || + uri.UserInfo.Length != 0 || uri.Query.Length != 0 || uri.Fragment.Length != 0) + throw new ArgumentException("必须提供无凭据、查询参数和片段的 HTTP(S) 根地址。", nameof(baseUrl)); + + _baseUrl = uri.AbsoluteUri.TrimEnd('/') + "/"; + } + + /// 仅存于内存;手动赋值会清除自动登录凭据,设为 null 退出本地会话。 + public string Token + { + get { lock (SessionGate) return _token; } + set + { + if (value != null && (string.IsNullOrWhiteSpace(value) || value.IndexOfAny(new[] { '\r', '\n' }) >= 0)) + throw new ArgumentException("Token 不能为空白或包含换行。", nameof(value)); + SetSession(value, null); + } + } + + /// 同步请求;无 data 时返回 default(T),界面调用需放在后台线程。 + public T Send(string route, MethodType method, object body = null, + IDictionary query = null, bool authenticated = true, + int timeoutMilliseconds = 30000) + { + if (string.IsNullOrWhiteSpace(route) || + !Regex.IsMatch(route, @"\A[A-Za-z0-9_-]+(/[A-Za-z0-9_-]+)*\z")) + throw new ArgumentException("接口必须使用相对路由,例如 Auth/Login。", nameof(route)); + if (body != null) Validator.ValidateObject(body, new ValidationContext(body), true); + if (timeoutMilliseconds <= 0) throw new ArgumentOutOfRangeException(nameof(timeoutMilliseconds)); + string token = authenticated ? Token : null; + if (authenticated && token == null) + throw new HttpException("请先登录。") { IsOnline = false }; + + var url = new StringBuilder(_baseUrl).Append(route); + if (query != null) + { + bool first = true; + foreach (var pair in query) + { + if (pair.Value == null) continue; + url.Append(first ? '?' : '&').Append(Uri.EscapeDataString(pair.Key)) + .Append('=').Append(Uri.EscapeDataString(pair.Value)); + first = false; + } + } + var item = new HttpItem + { + URL = url.ToString(), + Method = method, + ContentType = "application/json; charset=utf-8", + Accept = "application/json", + PostDataType = PostDataType.Byte, + PostdataByte = body == null ? null : Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(body)), + Encoding = Encoding.UTF8, + Timeout = timeoutMilliseconds, + ReadWriteTimeout = timeoutMilliseconds, + Allowautoredirect = false + }; + for (int attempt = 0; ; attempt++) + { + if (token != null) item.Header["Authorization"] = "Bearer " + token; + var response = HttpHelper.Request(item); + int status = (int)response.StatusCode; + if (status == 0) throw new HttpException("无法连接服务器或请求超时。"); + + JObject result = null; + JsonException parseError = null; + try { result = JObject.Parse(response.Html ?? ""); } + catch (JsonException ex) { parseError = ex; } + + var code = result?["code"]; + if (status == 401 || (code?.Type == JTokenType.Integer && code.ToString() == "401")) + { + if (authenticated) + { + lock (SessionGate) + { + // 并发旧请求复用已更新的 Token;同一请求最多重新发送一次。 + if (_token == token) + { + _token = null; + if (attempt == 0 && _login != null) + _token = _login(); + } + if (attempt == 0 && _token != null) + { + token = _token; + continue; + } + } + } + throw new HttpException(result?["msg"]?.ToString() ?? "登录已失效,请重新登录。") { IsOnline = false }; + } + if (status < 200 || status >= 300) + throw new HttpException($"请求失败(HTTP {status}):{result?["msg"]}"); + if (result == null) + throw new HttpException("服务器响应不是有效的 JSON 对象。", parseError); + // 先检查业务码,避免错误响应的 data 结构掩盖服务端错误。 + if (code?.Type != JTokenType.Integer || code.ToString() != "200") + throw new ApiException("请求失败:" + (result["msg"]?.ToString() ?? "响应缺少成功状态码。"), + code?.Type == JTokenType.Integer && int.TryParse(code.ToString(), out var businessCode) ? businessCode : (int?)null, + result["data"]); + if (result["data"] == null || result["data"].Type == JTokenType.Null) return default; + try { return result["data"].ToObject(); } + catch (JsonException ex) { throw new HttpException("服务器响应 data 格式无效。", ex); } + } + } + + public T GetRequired(string route, MethodType method, object body = null, + IDictionary query = null, bool authenticated = true, + int timeoutMilliseconds = 30000) where T : class + => Send(route, method, body, query, authenticated, timeoutMilliseconds) + ?? throw new HttpException("服务器响应缺少 data。"); +} + diff --git a/Work/Store.Api/AuthApi.cs b/Work/Store.Api/AuthApi.cs new file mode 100644 index 0000000..dc890d6 --- /dev/null +++ b/Work/Store.Api/AuthApi.cs @@ -0,0 +1,75 @@ +using CsharpHttpHelper; +using System; +using System.ComponentModel.DataAnnotations; + + +namespace Work.Store.Api; + +public sealed class LoginRequest +{ + [Required] + public string UserName { get; set; } + + /// UTF-8 密码的 32 位大写 MD5,由调用方计算。 + [Required] + public string Password { get; set; } + + [StringLength(100)] + public string DeviceId { get; set; } + + [StringLength(100)] + public string DeviceName { get; set; } +} + +public sealed class LoginResult +{ + public string TokenType { get; set; } = "Bearer"; + public string Token { get; set; } + public uint ExpiresInMinutes { get; set; } +} + +public sealed class AuthApi +{ + private readonly ApiTransport _transport; + private readonly string _deviceId; + + public AuthApi(string baseUrl) : this(new ApiTransport(baseUrl)) { } + + public AuthApi(ApiTransport transport, string deviceId = null) + { + _transport = transport ?? throw new ArgumentNullException(nameof(transport)); + _deviceId = deviceId; + } + + /// 同步登录,凭据快照仅存内存,供 401 时自动重登;界面调用需放在后台线程。 + public LoginResult Login(LoginRequest request) + { + if (request == null) throw new ArgumentNullException(nameof(request)); + var credentials = new LoginRequest + { + UserName = request.UserName, + Password = request.Password, + DeviceId = _deviceId ?? request.DeviceId, + DeviceName = request.DeviceName + }; + lock (_transport.SessionGate) + { + var result = LoginCore(credentials); + _transport.SetSession(result.Token, () => LoginCore(credentials).Token); + return result; + } + } + + private LoginResult LoginCore(LoginRequest request) + { + var result = _transport.GetRequired("Auth/Login", MethodType.Post, request, authenticated: false); + if (result == null || string.IsNullOrWhiteSpace(result.Token) || + result.Token.IndexOfAny(new[] { '\r', '\n' }) >= 0 || + !string.Equals(result.TokenType, "Bearer", StringComparison.OrdinalIgnoreCase)) + throw new HttpException("登录响应缺少有效的 Bearer Token。"); + + return result; + } +} + + diff --git a/Work/Store.Api/CloudGoodsApi.cs b/Work/Store.Api/CloudGoodsApi.cs new file mode 100644 index 0000000..76c9baf --- /dev/null +++ b/Work/Store.Api/CloudGoodsApi.cs @@ -0,0 +1,55 @@ +using AllBase.Model.Result; +using CsharpHttpHelper; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading.Tasks; +using Work.Services; + +namespace Work.Store.Api; + +/// 云商品接口,沿用服务端不带版本号的路由。 +public sealed class CloudGoodsApi +{ + private readonly ApiTransport _transport; + + public CloudGoodsApi(ApiTransport transport) + => _transport = transport ?? throw new ArgumentNullException(nameof(transport)); + + public Task> GetGoods(GoodsSearchRequest request) + => Task.Run(() => _transport.GetRequired>("CloudGoods/SearchGoods", MethodType.Post, request)); + + public string GetGoodsDetail(long goodsId) + => _transport.GetRequired("CloudGoods/GetGoodsRawData", MethodType.Post, + query: new Dictionary { ["goodsId"] = goodsId.ToString(CultureInfo.InvariantCulture) }); + + public Task> ReportViolations(List items) + => Task.Run(() => _transport.GetRequired>("CloudGoods/ReportViolations", MethodType.Post, items, + timeoutMilliseconds: 5000)); + + public List GetViolationStatus(List goodsIds) + => _transport.GetRequired>("CloudGoods/GetViolationStatus", MethodType.Post, goodsIds, + timeoutMilliseconds: 5000); + + public Task RecordViolationLogs(IReadOnlyList requests) + => Task.Run(() => _transport.Send("CloudGoods/RecordViolationLog", MethodType.Post, requests, + timeoutMilliseconds: 5000)); + + public Task> GetViolationReasons() + => Task.Run(() => _transport.GetRequired>("CloudGoods/GetViolationReasons", MethodType.Get)); + + public Task> GetViolationLogs(GetViolationLogsRequest request) + => Task.Run(() => _transport.GetRequired>("CloudGoods/GetViolationLogs", MethodType.Post, request, + timeoutMilliseconds: 5000)); + + public Task> SearchFavorites(UserFavoritesGoodsSearchRequest request) + => Task.Run(() => _transport.GetRequired>("CloudGoods/SearchFavorites", MethodType.Post, request)); + + public Task Favorite(string userName, List goodsIds) + => Task.Run(() => _transport.Send("CloudGoods/AddFavorite", MethodType.Post, + new { UserName = userName, GoodsIds = goodsIds })); + + public Task RemoveFavorites(string userName, List goodsIds) + => Task.Run(() => _transport.Send("CloudGoods/RemoveFavorites", MethodType.Post, + new { UserName = userName, GoodsIds = goodsIds })); +} diff --git a/Work/Store.Api/ImageCacheApi.cs b/Work/Store.Api/ImageCacheApi.cs new file mode 100644 index 0000000..2bfb036 --- /dev/null +++ b/Work/Store.Api/ImageCacheApi.cs @@ -0,0 +1,41 @@ +using CsharpHttpHelper; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using Work.Services; + +namespace Work.Store.Api; + +/// 图片缓存接口,返回批量保存失败的 MD5 列表。 +public sealed class ImageCacheApi +{ + private readonly ApiTransport _transport; + + public ImageCacheApi(ApiTransport transport) + => _transport = transport ?? throw new ArgumentNullException(nameof(transport)); + + public List GetImageCaches(List md5s) + => _transport.GetRequired>("ImageCache/GetImageCache", MethodType.Post, new { md5s }); + + public List SaveImageCaches(int platform, IReadOnlyList images) + { + if (images == null) throw new ArgumentNullException(nameof(images)); + if (images.Count == 0) return new List(); + try + { + _transport.Send("ImageCache/AddOrUpdate", MethodType.Post, + images.Select(image => new { md5 = image.Md5, Platform = platform, PlatformUrl = image.PlatformUrl }).ToArray()); + return new List(); + } + catch (ApiException ex) when (ex.Code == 500) + { + // 兼容旧服务的部分失败响应;没有明确失败项时保持整体失败。 + if (ex.ResponseData is JArray array && array.Count > 0 && array.All(item => item.Type == JTokenType.String)) + return array.ToObject>(); + if (ex.ResponseData?.Type == JTokenType.String && !string.IsNullOrWhiteSpace((string)ex.ResponseData)) + return new List { (string)ex.ResponseData }; + throw; + } + } +} diff --git a/Work/Store.Api/StoreApiClient.cs b/Work/Store.Api/StoreApiClient.cs new file mode 100644 index 0000000..82c2d99 --- /dev/null +++ b/Work/Store.Api/StoreApiClient.cs @@ -0,0 +1,50 @@ +using Microsoft.Win32; +using AllBase.Helper; +using System; +using System.Security.Cryptography; +using System.Text; + +namespace Work.Store.Api; + +/// 全局共用一个用户会话与请求出口,窗体通过 Instance 访问。 +public sealed class StoreApiClient +{ + private static readonly string BaseUrl = EnvironmentHelper.IsDevelopment + ? "http://localhost:14312/api/" + : "http://103.90.155.78:9001/api/"; + + public static StoreApiClient Instance { get; } = new StoreApiClient(); + + public ApiTransport Transport { get; } + public AuthApi Auth { get; } + public CloudGoodsApi CloudGoods { get; } + public ImageCacheApi ImageCache { get; } + /// 当前 Windows 安装的 SHA-256 设备指纹,登录时作为 DeviceId 发送。 + public string DeviceId { get; } + + private StoreApiClient() + { + DeviceId = CreateDeviceId(); + Transport = new ApiTransport(BaseUrl); + Auth = new AuthApi(Transport, DeviceId); + CloudGoods = new CloudGoodsApi(Transport); + ImageCache = new ImageCacheApi(Transport); + } + + private static string CreateDeviceId() + { + using (var machine = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) + using (var key = machine.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography")) + { + if (!Guid.TryParse(key?.GetValue("MachineGuid") as string, out var machineGuid) || machineGuid == Guid.Empty) + throw new InvalidOperationException("无法读取有效的 Windows MachineGuid,不能生成设备指纹。"); + + using (var sha256 = SHA256.Create()) + return BitConverter.ToString(sha256.ComputeHash( + Encoding.UTF8.GetBytes("Store.Api:" + machineGuid.ToString("D")))).Replace("-", ""); + } + } + + /// 清除内存中的 Token 和自动登录凭据,不调用服务端退出接口。 + public void ClearSession() => Transport.Token = null; +} diff --git a/Work/Work.csproj b/Work/Work.csproj index 0daef55..9fea9ce 100644 --- a/Work/Work.csproj +++ b/Work/Work.csproj @@ -51,6 +51,7 @@ ..\DllReference\NaiBa.Util.Winform.dll + ..\DllReference\System.Data.SQLite.DLL False From b54a5f5310561a132f8b0b18ef6de35eeb987d9a Mon Sep 17 00:00:00 2001 From: chfei Date: Thu, 5 Nov 2026 18:50:13 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E4=BA=91=E6=94=B6=E8=97=8F=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0AI=E6=A3=80=E6=B5=8B=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AllBase/Helper/GlobalConfig.cs | 10 + AllBase/HttpHelper.cs | 328 +++++++-------- AllBase/Model/Result/PlatformType.cs | 6 +- AllBase/Model/Result/Result.cs | 2 + .../ImportConverters/ConvertToPddFactory.cs | 6 +- .../BLL/ImportConverters/IGoodsConverter.cs | 2 +- TEMUKJD/KJD.csproj | 7 +- TEMUKJD/KJD.csproj.user | 6 + .../SystemForms/SystemForm.Designer.cs | 4 +- TEMUKJD/KJDForms/SystemForms/SystemForm.cs | 5 + .../SystemGoodsComplianceForm.Designer.cs | 186 +++++++++ .../SystemForms/SystemGoodsComplianceForm.cs | 64 +++ .../PDDForms/ProductForms/PDDProductForm.cs | 1 + TEMUKJD/UserComponent/UcNumericRange.cs | 159 ++++++++ TEMUKJD/UserForm/LoginForm.cs | 44 +- .../CloudGoodsFavoriteForm.Designer.cs | 377 ++++++++++-------- .../ProductForms}/CloudGoodsFavoriteForm.cs | 129 ++++-- .../ProductForms}/CloudGoodsFavoriteForm.resx | 0 .../ProductForms}/CloudGoodsForm.Designer.cs | 2 +- .../ProductForms}/CloudGoodsForm.cs | 3 +- .../ProductForms}/CloudGoodsForm.resx | 0 .../ProductForms/WXDBProdUploadForm.cs | 49 +-- Work/Services/CloudGoodsService.cs | 24 +- Work/Services/ComplianceProductReader.cs | 143 +++++++ Work/Services/GoodsComplianceService.cs | 265 ++++++++++++ Work/Store.Api/ApiException.cs | 4 +- Work/Store.Api/ApiTransport.cs | 181 ++++++--- Work/Store.Api/AuthApi.cs | 24 +- Work/Store.Api/CloudGoodsApi.cs | 93 +++-- Work/Store.Api/GoodsComplianceApi.cs | 70 ++++ Work/Store.Api/StoreApiClient.cs | 8 +- Work/Work.csproj | 4 + test/ApiTransportSmoke.cs | 112 ++++++ test/GoodsComplianceSmoke.cs | 70 ++++ test/HttpHelperAsyncSmoke.cs | 68 ++++ test/NumericRangeSmoke.cs | 45 +++ 36 files changed, 1978 insertions(+), 523 deletions(-) create mode 100644 TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.Designer.cs create mode 100644 TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.cs create mode 100644 TEMUKJD/UserComponent/UcNumericRange.cs rename TEMUKJD/{PDDForms/ProductSelectForms => WXForms/ProductForms}/CloudGoodsFavoriteForm.Designer.cs (74%) rename TEMUKJD/{PDDForms/ProductSelectForms => WXForms/ProductForms}/CloudGoodsFavoriteForm.cs (77%) rename TEMUKJD/{PDDForms/ProductSelectForms => WXForms/ProductForms}/CloudGoodsFavoriteForm.resx (100%) rename TEMUKJD/{PDDForms/ProductSelectForms => WXForms/ProductForms}/CloudGoodsForm.Designer.cs (99%) rename TEMUKJD/{PDDForms/ProductSelectForms => WXForms/ProductForms}/CloudGoodsForm.cs (99%) rename TEMUKJD/{PDDForms/ProductSelectForms => WXForms/ProductForms}/CloudGoodsForm.resx (100%) create mode 100644 Work/Services/ComplianceProductReader.cs create mode 100644 Work/Services/GoodsComplianceService.cs create mode 100644 Work/Store.Api/GoodsComplianceApi.cs create mode 100644 test/ApiTransportSmoke.cs create mode 100644 test/GoodsComplianceSmoke.cs create mode 100644 test/HttpHelperAsyncSmoke.cs create mode 100644 test/NumericRangeSmoke.cs diff --git a/AllBase/Helper/GlobalConfig.cs b/AllBase/Helper/GlobalConfig.cs index 1e41080..b9d1b34 100644 --- a/AllBase/Helper/GlobalConfig.cs +++ b/AllBase/Helper/GlobalConfig.cs @@ -127,8 +127,18 @@ namespace AllBase.Helper Alibaba = 6, } + public sealed class ComplianceOptions + { + public int? MainCount { get; set; } + public int? DetailCount { get; set; } = 0; + public bool IncludeSku { get; set; } + public string Keywords { get; set; } + } + public class GlobalSettings { + public ComplianceOptions GoodsCompliance { get; set; } = new ComplianceOptions(); + /// /// 微信使用软件设置的默认上货地址 /// diff --git a/AllBase/HttpHelper.cs b/AllBase/HttpHelper.cs index f90ca82..0a9d4f6 100644 --- a/AllBase/HttpHelper.cs +++ b/AllBase/HttpHelper.cs @@ -8,6 +8,7 @@ using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; namespace CsharpHttpHelper @@ -52,6 +53,16 @@ namespace CsharpHttpHelper }); } + public static Task GetAsync(string Url) + { + return RequestAsync(new HttpItem { URL = Url, Method = MethodType.Get }); + } + + public static Task PostAsync(string Url, string PostData) + { + return RequestAsync(new HttpItem { URL = Url, Method = MethodType.Post, Postdata = PostData }); + } + /// /// 下载文件 /// @@ -77,17 +88,13 @@ namespace CsharpHttpHelper public static HttpResult Request(HttpItem item) { - HttpResult result = new HttpHelper().GetHtml(item); - if (!string.IsNullOrEmpty(result.Html)) - { - if (result.Html[0] == 65279) - { - result.Html = result.Html.Remove(0, 1); - } - } - return result; + return new HttpHelper().GetHtml(item); } + public static Task RequestAsync(HttpItem item) + { + return new HttpHelper().GetHtmlCoreAsync(item); + } #region Cookie /// @@ -137,8 +144,6 @@ namespace CsharpHttpHelper private Encoding postencoding = Encoding.Default; //HttpWebRequest对象用来发起请求 private HttpWebRequest request = null; - //获取影响流的数据对象 - private HttpWebResponse response = null; /// /// 根据相传入的数据,得到相应页面数据 /// @@ -146,112 +151,49 @@ namespace CsharpHttpHelper /// 返回HttpResult类型 public HttpResult GetHtml(HttpItem item) { - //返回参数 - HttpResult result = new HttpResult(); - result.item = item; try { - //准备参数 SetRequest(item); + if (item.Method == MethodType.Post || item.Method == MethodType.Put) + { + byte[] body; + if (item.PostDataType == PostDataType.FilePath && !string.IsNullOrEmpty(item.Postdata)) + { + using var reader = new StreamReader(item.Postdata, postencoding); + body = postencoding.GetBytes(reader.ReadToEnd()); + } + else body = GetPostBytes(item); + if (body != null) + { + request.ContentLength = body.Length; + using var stream = request.GetRequestStream(); + stream.Write(body, 0, body.Length); + } + } } catch (Exception ex) { - result = new HttpResult(); - result.Cookie = string.Empty; - result.Header = null; - result.Html = ex.Message; - result.StatusDescription = "配置参数时出错:" + ex.Message; - return result; + return ConfigurationError(ex); } + + var result = new HttpResult { item = item }; try { - #region 得到请求的response - using (response = (HttpWebResponse)request.GetResponse()) - { - result.StatusCode = response.StatusCode; - result.StatusDescription = response.StatusDescription; - result.Header = response.Headers; - if (response.Cookies != null) result.CookieCollection = response.Cookies; - if (response.Headers["set-cookie"] != null) result.Cookie = response.Headers["set-cookie"]; - MemoryStream _stream = new MemoryStream(); - //GZIIP处理 - if (response.ContentEncoding != null && response.ContentEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase)) - { - //开始读取流并设置编码方式 - //new GZipStream(response.GetResponseStream(), CompressionMode.Decompress).CopyTo(_stream, 10240); - //.net4.0以下写法 - _stream = GetMemoryStream(new GZipStream(response.GetResponseStream(), CompressionMode.Decompress)); - - } - else - { - //开始读取流并设置编码方式 - //response.GetResponseStream().CopyTo(_stream, 10240); - //.net4.0以下写法 - _stream = GetMemoryStream(response.GetResponseStream()); - } - //获取Byte - byte[] ResponseByte = _stream.ToArray(); - _stream.Close(); - if (ResponseByte != null & ResponseByte.Length > 0) - { - //是否返回Byte类型数据 - if (item.ResultType == ResultType.Byte) result.ResultByte = ResponseByte; - //从这里开始我们要无视编码了 - if (encoding == null) - { - Match meta = Regex.Match(Encoding.Default.GetString(ResponseByte), " 1) ? meta.Groups[2].Value.ToLower().Trim() : string.Empty; - if (c.Length > 2) - { - try - { - if (c.IndexOf(" ") > 0) c = c.Substring(0, c.IndexOf(" ")); - encoding = Encoding.GetEncoding(c.Replace("\"", "").Replace("'", "").Replace(";", "").Replace("iso-8859-1", "gbk").Trim()); - } - catch - { - if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8; - else encoding = Encoding.GetEncoding(response.CharacterSet); - } - } - else - { - if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8; - else encoding = Encoding.GetEncoding(response.CharacterSet); - } - } - //得到返回的HTML - result.Html = encoding.GetString(ResponseByte); - } - else - { - //得到返回的HTML - result.Html = "本次请求并未返回任何数据"; - } - } - #endregion + using var response = (HttpWebResponse)request.GetResponse(); + SetResponseHeaders(result, response); + if (response.Cookies != null) result.CookieCollection = response.Cookies; + result.Cookie = response.Headers["set-cookie"]; + using var source = OpenResponseStream(response); + using var buffer = new MemoryStream(); + source.CopyTo(buffer); + DecodeBody(item, result, response, buffer.ToArray()); } - catch (WebException ex) + catch (WebException ex) when (ex.Response is HttpWebResponse) { - //这里是在发生异常时返回的错误信息 - response = (HttpWebResponse)ex.Response; - result.Html = ex.Message; - if (response != null) - { - using (response) - { - result.StatusCode = response.StatusCode; - result.StatusDescription = response.StatusDescription; - result.Header = response.Headers; - // 保留错误响应正文,供微信调用方识别业务错误和登录失效。 - using (var source = response.GetResponseStream()) - using (var decoded = string.Equals(response.ContentEncoding, "gzip", StringComparison.OrdinalIgnoreCase) - ? (Stream)new GZipStream(source, CompressionMode.Decompress) : source) - using (var reader = new StreamReader(decoded, item.Encoding ?? Encoding.UTF8)) - result.Html = reader.ReadToEnd(); - } - } + using var response = (HttpWebResponse)ex.Response; + SetResponseHeaders(result, response); + using var reader = new StreamReader(OpenResponseStream(response), item.Encoding ?? Encoding.UTF8); + result.Html = reader.ReadToEnd(); } catch (Exception ex) { @@ -260,22 +202,127 @@ namespace CsharpHttpHelper if (item.IsToLower) result.Html = result.Html.ToLower(); return result; } - /// - /// 4.0以下.net版本取数据使用 - /// - /// 流 - private MemoryStream GetMemoryStream(Stream streamResponse) + + public Task GetHtmlAsync(HttpItem item) { - MemoryStream _stream = new MemoryStream(); - int Length = 256; - Byte[] buffer = new Byte[Length]; - int bytesRead = streamResponse.Read(buffer, 0, Length); - while (bytesRead > 0) + // 每次异步调用独立保存请求和编码,允许同一实例并发使用。 + return new HttpHelper().GetHtmlCoreAsync(item); + } + + private async Task GetHtmlCoreAsync(HttpItem item) + { + using var timeout = new CancellationTokenSource(); + using var abort = timeout.Token.Register(() => request.Abort()); + try { - _stream.Write(buffer, 0, bytesRead); - bytesRead = streamResponse.Read(buffer, 0, Length); + SetRequest(item); + timeout.CancelAfter(request.Timeout); + if (item.Method == MethodType.Post || item.Method == MethodType.Put) + { + byte[] body; + if (item.PostDataType == PostDataType.FilePath && !string.IsNullOrEmpty(item.Postdata)) + { + using var reader = new StreamReader(item.Postdata, postencoding); + body = postencoding.GetBytes(await reader.ReadToEndAsync().ConfigureAwait(false)); + } + else body = GetPostBytes(item); + if (body != null) + { + request.ContentLength = body.Length; + using var stream = await request.GetRequestStreamAsync().ConfigureAwait(false); + await stream.WriteAsync(body, 0, body.Length).ConfigureAwait(false); + } + } + } + catch (Exception ex) + { + return ConfigurationError(ex); + } + + var result = new HttpResult { item = item }; + try + { + using var response = (HttpWebResponse)await request.GetResponseAsync().ConfigureAwait(false); + SetResponseHeaders(result, response); + if (response.Cookies != null) result.CookieCollection = response.Cookies; + result.Cookie = response.Headers["set-cookie"]; + using var source = OpenResponseStream(response); + using var buffer = new MemoryStream(); + await source.CopyToAsync(buffer).ConfigureAwait(false); + DecodeBody(item, result, response, buffer.ToArray()); + } + catch (WebException ex) when (ex.Response is HttpWebResponse) + { + using var response = (HttpWebResponse)ex.Response; + SetResponseHeaders(result, response); + using var reader = new StreamReader(OpenResponseStream(response), item.Encoding ?? Encoding.UTF8); + result.Html = await reader.ReadToEndAsync().ConfigureAwait(false); + } + catch (Exception ex) + { + result.Html = ex.Message; + } + if (item.IsToLower) result.Html = result.Html.ToLower(); + return result; + } + + private static HttpResult ConfigurationError(Exception ex) + { + return new HttpResult { Cookie = string.Empty, Html = ex.Message, StatusDescription = "配置参数时出错:" + ex.Message }; + } + + private static void SetResponseHeaders(HttpResult result, HttpWebResponse response) + { + result.StatusCode = response.StatusCode; + result.StatusDescription = response.StatusDescription; + result.Header = response.Headers; + } + + private static Stream OpenResponseStream(HttpWebResponse response) + { + var stream = response.GetResponseStream(); + return string.Equals(response.ContentEncoding, "gzip", StringComparison.OrdinalIgnoreCase) + ? new GZipStream(stream, CompressionMode.Decompress) : stream; + } + + private void DecodeBody(HttpItem item, HttpResult result, HttpWebResponse response, byte[] bytes) + { + if (bytes.Length > 0) + { + //是否返回Byte类型数据 + if (item.ResultType == ResultType.Byte) result.ResultByte = bytes; + //从这里开始我们要无视编码了 + if (encoding == null) + { + Match meta = Regex.Match(Encoding.Default.GetString(bytes), " 1) ? meta.Groups[2].Value.ToLower().Trim() : string.Empty; + if (c.Length > 2) + { + try + { + if (c.IndexOf(" ") > 0) c = c.Substring(0, c.IndexOf(" ")); + encoding = Encoding.GetEncoding(c.Replace("\"", "").Replace("'", "").Replace(";", "").Replace("iso-8859-1", "gbk").Trim()); + } + catch + { + if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8; + else encoding = Encoding.GetEncoding(response.CharacterSet); + } + } + else + { + if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8; + else encoding = Encoding.GetEncoding(response.CharacterSet); + } + } + //得到返回的HTML + result.Html = encoding.GetString(bytes); + } + else + { + //得到返回的HTML + result.Html = "本次请求并未返回任何数据"; } - return _stream; } /// /// 为请求准备参数 @@ -308,6 +355,7 @@ namespace CsharpHttpHelper request.UserAgent = item.UserAgent; // 编码 encoding = item.Encoding; + if (item.Method != MethodType.Get && item.PostEncoding != null) postencoding = item.PostEncoding; //设置安全凭证 request.Credentials = item.ICredentials; //设置Cookie @@ -316,8 +364,6 @@ namespace CsharpHttpHelper request.Referer = item.Referer; //是否执行跳转功能 request.AllowAutoRedirect = item.Allowautoredirect; - //设置Post数据 - SetPostData(item); //设置最大连接 if (item.Connectionlimit > 0) request.ServicePoint.ConnectionLimit = item.Connectionlimit; } @@ -373,43 +419,13 @@ namespace CsharpHttpHelper request.CookieContainer.Add(item.CookieCollection); } } - /// - /// 设置Post数据 - /// - /// Http参数 - private void SetPostData(HttpItem item) + private byte[] GetPostBytes(HttpItem item) { - //验证在得到结果时是否有传入数据 - if (request.Method.Trim().ToLower().Contains("post") || request.Method.Trim().ToLower().Contains("put")) - { - if (item.PostEncoding != null) - { - postencoding = item.PostEncoding; - } - byte[] buffer = null; - //写入Byte类型 - if (item.PostDataType == PostDataType.Byte && item.PostdataByte != null && item.PostdataByte.Length > 0) - { - //验证在得到结果时是否有传入数据 - buffer = item.PostdataByte; - }//写入文件 - else if (item.PostDataType == PostDataType.FilePath && !string.IsNullOrEmpty(item.Postdata)) - { - StreamReader r = new StreamReader(item.Postdata, postencoding); - buffer = postencoding.GetBytes(r.ReadToEnd()); - r.Close(); - } //写入字符串 - else if (!string.IsNullOrEmpty(item.Postdata)) - { - buffer = postencoding.GetBytes(item.Postdata); - } - if (buffer != null) - { - request.ContentLength = buffer.Length; - request.GetRequestStream().Write(buffer, 0, buffer.Length); - } - } + if (item.PostDataType == PostDataType.Byte && item.PostdataByte?.Length > 0) + return item.PostdataByte; + return string.IsNullOrEmpty(item.Postdata) ? null : postencoding.GetBytes(item.Postdata); } + /// /// 设置代理 /// diff --git a/AllBase/Model/Result/PlatformType.cs b/AllBase/Model/Result/PlatformType.cs index 02ff4c3..ca0fbae 100644 --- a/AllBase/Model/Result/PlatformType.cs +++ b/AllBase/Model/Result/PlatformType.cs @@ -8,8 +8,8 @@ namespace AllBase.Model.Result { public enum PlatformType { - None, - PDD, - WX, + None = 0, + PDD = 1, + WX = 2, } } diff --git a/AllBase/Model/Result/Result.cs b/AllBase/Model/Result/Result.cs index f8cfa9a..278775a 100644 --- a/AllBase/Model/Result/Result.cs +++ b/AllBase/Model/Result/Result.cs @@ -20,6 +20,8 @@ namespace AllBase.Model.Result public int Code { get; set; } public string Msg { get; set; } + public bool? Success { get; set; } + public string TraceId { get; set; } } public class Result : Result { diff --git a/TEMUKJD/BLL/ImportConverters/ConvertToPddFactory.cs b/TEMUKJD/BLL/ImportConverters/ConvertToPddFactory.cs index c3a495e..2cfb607 100644 --- a/TEMUKJD/BLL/ImportConverters/ConvertToPddFactory.cs +++ b/TEMUKJD/BLL/ImportConverters/ConvertToPddFactory.cs @@ -12,7 +12,7 @@ namespace TemuKJD.BLL.ImportConverts { public class ConverterToPddFactory { - private static List _converters = new List + private static List CreateConverters() => new List { new ConverterToPddV5(), new ConverterWXToPddV1(), @@ -24,7 +24,7 @@ namespace TemuKJD.BLL.ImportConverts public static PDDFrontProductInfo5 Convert(string filePath) { - foreach (IGoodsConverter item in _converters) + foreach (IGoodsConverter item in CreateConverters()) { try { @@ -47,7 +47,7 @@ namespace TemuKJD.BLL.ImportConverts public static PDDFrontProductInfo5 SetContentConvert(string content) { - foreach (IGoodsConverter item in _converters) + foreach (IGoodsConverter item in CreateConverters()) { try { diff --git a/TEMUKJD/BLL/ImportConverters/IGoodsConverter.cs b/TEMUKJD/BLL/ImportConverters/IGoodsConverter.cs index a9ba3bb..de13d78 100644 --- a/TEMUKJD/BLL/ImportConverters/IGoodsConverter.cs +++ b/TEMUKJD/BLL/ImportConverters/IGoodsConverter.cs @@ -8,7 +8,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; -using System.Web.UI.WebControls; + namespace TemuKJD.BLL.ImportConverters { diff --git a/TEMUKJD/KJD.csproj b/TEMUKJD/KJD.csproj index 677b131..b2d2d84 100644 --- a/TEMUKJD/KJD.csproj +++ b/TEMUKJD/KJD.csproj @@ -68,10 +68,10 @@ - + Form - + Form @@ -86,4 +86,7 @@ Resources.Designer.cs + + + diff --git a/TEMUKJD/KJD.csproj.user b/TEMUKJD/KJD.csproj.user index 0382c37..5149de2 100644 --- a/TEMUKJD/KJD.csproj.user +++ b/TEMUKJD/KJD.csproj.user @@ -44,6 +44,9 @@ Form + + Form + Form @@ -65,6 +68,9 @@ Form + + UserControl + Form diff --git a/TEMUKJD/KJDForms/SystemForms/SystemForm.Designer.cs b/TEMUKJD/KJDForms/SystemForms/SystemForm.Designer.cs index 7ad545c..58858b1 100644 --- a/TEMUKJD/KJDForms/SystemForms/SystemForm.Designer.cs +++ b/TEMUKJD/KJDForms/SystemForms/SystemForm.Designer.cs @@ -30,8 +30,9 @@ { System.Windows.Forms.TreeNode treeNode1 = new System.Windows.Forms.TreeNode("解除限制"); System.Windows.Forms.TreeNode violationNode = new System.Windows.Forms.TreeNode("违规检测"); + System.Windows.Forms.TreeNode complianceNode = new System.Windows.Forms.TreeNode("商品 AI 检测"); System.Windows.Forms.TreeNode treeNode2 = new System.Windows.Forms.TreeNode("系统设置", new System.Windows.Forms.TreeNode[] { - treeNode1, violationNode}); + treeNode1, violationNode, complianceNode}); this.panel1 = new System.Windows.Forms.Panel(); this.panel3 = new System.Windows.Forms.Panel(); this.panel2 = new System.Windows.Forms.Panel(); @@ -104,6 +105,7 @@ this.tv.Name = "tv"; treeNode1.Name = "解除限制"; treeNode1.Text = "解除限制"; + complianceNode.Name = "商品 AI 检测"; violationNode.Name = "违规检测"; violationNode.Text = "违规检测"; treeNode2.Name = "系统设置"; diff --git a/TEMUKJD/KJDForms/SystemForms/SystemForm.cs b/TEMUKJD/KJDForms/SystemForms/SystemForm.cs index ccfc34b..ea3cb35 100644 --- a/TEMUKJD/KJDForms/SystemForms/SystemForm.cs +++ b/TEMUKJD/KJDForms/SystemForms/SystemForm.cs @@ -16,6 +16,8 @@ namespace TemuKJD.KJDForms.SystemForms { public Lazy systemRemoveRestrictionsForm = new Lazy(); public Lazy systemViolationDetectionForm = new Lazy(); + private readonly Lazy goodsComplianceForm = new Lazy(); + public SystemForm() { InitializeComponent(); @@ -47,6 +49,9 @@ namespace TemuKJD.KJDForms.SystemForms case "解除限制": SetForm(systemRemoveRestrictionsForm.Value); break; + case "商品 AI 检测": + SetForm(goodsComplianceForm.Value); + break; case "违规检测": SetForm(systemViolationDetectionForm.Value); break; diff --git a/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.Designer.cs b/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.Designer.cs new file mode 100644 index 0000000..f00358c --- /dev/null +++ b/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.Designer.cs @@ -0,0 +1,186 @@ +using System.Drawing; +using System.Windows.Forms; + +namespace TemuKJD.KJDForms.SystemForms +{ + partial class SystemGoodsComplianceForm + { + private GroupBox imagesGroup; + private Label mainLabel; + private ComboBox main; + private NumericUpDown mainCount; + private Label mainUnit; + private Label detailLabel; + private ComboBox detail; + private NumericUpDown detailCount; + private Label detailUnit; + private CheckBox sku; + private GroupBox keywordsGroup; + private TextBox keywords; + private Label keywordsHint; + private Button btnSaveDetection; + + private void InitializeComponent() + { + this.imagesGroup = new GroupBox(); + this.mainLabel = new Label(); + this.main = new ComboBox(); + this.mainCount = new NumericUpDown(); + this.mainUnit = new Label(); + this.detailLabel = new Label(); + this.detail = new ComboBox(); + this.detailCount = new NumericUpDown(); + this.detailUnit = new Label(); + this.sku = new CheckBox(); + this.keywordsGroup = new GroupBox(); + this.keywords = new TextBox(); + this.keywordsHint = new Label(); + this.btnSaveDetection = new Button(); + this.imagesGroup.SuspendLayout(); + this.keywordsGroup.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mainCount)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.detailCount)).BeginInit(); + this.SuspendLayout(); + + this.imagesGroup.Name = "imagesGroup"; + this.imagesGroup.Location = new Point(20, 16); + this.imagesGroup.TabIndex = 0; + this.imagesGroup.Size = new Size(760, 153); + this.imagesGroup.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + this.imagesGroup.Text = "检测图片"; + this.imagesGroup.Controls.Add(this.mainLabel); + this.imagesGroup.Controls.Add(this.main); + this.imagesGroup.Controls.Add(this.mainCount); + this.imagesGroup.Controls.Add(this.mainUnit); + this.imagesGroup.Controls.Add(this.detailLabel); + this.imagesGroup.Controls.Add(this.detail); + this.imagesGroup.Controls.Add(this.detailCount); + this.imagesGroup.Controls.Add(this.detailUnit); + this.imagesGroup.Controls.Add(this.sku); + + this.mainLabel.Name = "mainLabel"; + this.mainLabel.Location = new Point(16, 34); + this.mainLabel.TabIndex = 0; + this.mainLabel.AutoSize = true; + this.mainLabel.Text = "商品主图"; + + this.main.Name = "main"; + this.main.Location = new Point(106, 30); + this.main.TabIndex = 1; + this.main.Width = 110; + this.main.DropDownStyle = ComboBoxStyle.DropDownList; + this.main.Items.AddRange(new object[] { "全部", "前几张" }); + this.main.SelectedIndexChanged += new System.EventHandler(this.ImageRangeChanged); + + this.mainCount.Name = "mainCount"; + this.mainCount.Location = new Point(228, 30); + this.mainCount.TabIndex = 2; + this.mainCount.Enabled = false; + this.mainCount.Minimum = 1M; + this.mainCount.Maximum = 999M; + this.mainCount.Value = 1M; + this.mainCount.Width = 60; + this.mainCount.AccessibleName = "商品主图检测张数"; + + this.mainUnit.Name = "mainUnit"; + this.mainUnit.Location = new Point(294, 34); + this.mainUnit.TabIndex = 3; + this.mainUnit.AutoSize = true; + this.mainUnit.Text = "张"; + + this.detailLabel.Name = "detailLabel"; + this.detailLabel.Location = new Point(16, 72); + this.detailLabel.TabIndex = 4; + this.detailLabel.AutoSize = true; + this.detailLabel.Text = "详情图"; + + this.detail.Name = "detail"; + this.detail.Location = new Point(106, 68); + this.detail.TabIndex = 5; + this.detail.Width = 110; + this.detail.DropDownStyle = ComboBoxStyle.DropDownList; + this.detail.Items.AddRange(new object[] { "不检测", "前几张", "全部" }); + this.detail.SelectedIndexChanged += new System.EventHandler(this.ImageRangeChanged); + + this.detailCount.Name = "detailCount"; + this.detailCount.Location = new Point(228, 68); + this.detailCount.TabIndex = 6; + this.detailCount.Enabled = false; + this.detailCount.Minimum = 1M; + this.detailCount.Maximum = 999M; + this.detailCount.Value = 1M; + this.detailCount.Width = 60; + this.detailCount.AccessibleName = "详情图检测张数"; + + this.detailUnit.Name = "detailUnit"; + this.detailUnit.Location = new Point(294, 72); + this.detailUnit.TabIndex = 7; + this.detailUnit.AutoSize = true; + this.detailUnit.Text = "张"; + + this.sku.Name = "sku"; + this.sku.Location = new Point(106, 112); + this.sku.TabIndex = 8; + this.sku.AutoSize = true; + this.sku.Text = "检测 SKU 图片"; + + this.keywordsGroup.Name = "keywordsGroup"; + this.keywordsGroup.Location = new Point(20, 183); + this.keywordsGroup.TabIndex = 1; + this.keywordsGroup.Size = new Size(760, 143); + this.keywordsGroup.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + this.keywordsGroup.Text = "检测关键词"; + this.keywordsGroup.Controls.Add(this.keywords); + this.keywordsGroup.Controls.Add(this.keywordsHint); + + this.keywords.Name = "keywords"; + this.keywords.Location = new Point(16, 27); + this.keywords.TabIndex = 0; + this.keywords.Size = new Size(728, 68); + this.keywords.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + this.keywords.Multiline = true; + this.keywords.AcceptsReturn = true; + this.keywords.ScrollBars = ScrollBars.Vertical; + this.keywords.AccessibleName = "检测关键词"; + + this.keywordsHint.Name = "keywordsHint"; + this.keywordsHint.Location = new Point(16, 105); + this.keywordsHint.TabIndex = 1; + this.keywordsHint.AutoSize = true; + this.keywordsHint.ForeColor = SystemColors.GrayText; + this.keywordsHint.Text = "多个关键词可用逗号、分号、空格或换行分隔。"; + + this.btnSaveDetection.Name = "btnSaveDetection"; + this.btnSaveDetection.Location = new Point(20, 341); + this.btnSaveDetection.TabIndex = 2; + this.btnSaveDetection.Size = new Size(96, 32); + this.btnSaveDetection.Text = "保存条件"; + this.btnSaveDetection.BackColor = Color.FromArgb(63, 126, 255); + this.btnSaveDetection.ForeColor = Color.White; + this.btnSaveDetection.FlatStyle = FlatStyle.Flat; + this.btnSaveDetection.UseVisualStyleBackColor = false; + this.btnSaveDetection.Cursor = Cursors.Hand; + this.btnSaveDetection.FlatAppearance.BorderSize = 0; + this.btnSaveDetection.Click += new System.EventHandler(this.SaveDetectionClick); + + this.AutoScaleMode = AutoScaleMode.None; + this.AutoScroll = true; + this.AutoScrollMinSize = new Size(365, 390); + this.BackColor = Color.White; + this.ClientSize = new Size(800, 450); + this.Font = new Font("微软雅黑", 9F); + this.Controls.Add(this.imagesGroup); + this.Controls.Add(this.keywordsGroup); + this.Controls.Add(this.btnSaveDetection); + this.Name = "SystemGoodsComplianceForm"; + this.Text = "商品 AI 检测"; + this.imagesGroup.ResumeLayout(false); + this.imagesGroup.PerformLayout(); + this.keywordsGroup.ResumeLayout(false); + this.keywordsGroup.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mainCount)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.detailCount)).EndInit(); + this.ResumeLayout(false); + } + } +} diff --git a/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.cs b/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.cs new file mode 100644 index 0000000..c37c529 --- /dev/null +++ b/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.cs @@ -0,0 +1,64 @@ +using System; +using System.Windows.Forms; +using Work.Services; +using AllBase.Helper; +using TemuKJD.BaseForms; + +namespace TemuKJD.KJDForms.SystemForms +{ + public partial class SystemGoodsComplianceForm : BaseForm + { + public SystemGoodsComplianceForm() + { + InitializeComponent(); + + VisibleChanged += (sender, e) => { if (Visible) LoadSettings(); }; + LoadSettings(); + } + + private void LoadSettings() + { + var options = GlobalConfig.GlobalSettings.GoodsCompliance ?? new ComplianceOptions(); + main.SelectedIndex = options.MainCount.HasValue ? 1 : 0; + detail.SelectedIndex = options.DetailCount == null ? 2 : options.DetailCount == 0 ? 0 : 1; + mainCount.Value = Math.Min(mainCount.Maximum, Math.Max(1, options.MainCount ?? 1)); + detailCount.Value = Math.Min(detailCount.Maximum, Math.Max(1, options.DetailCount ?? 1)); + sku.Checked = options.IncludeSku; + keywords.Text = options.Keywords; + } + + private void ImageRangeChanged(object sender, EventArgs e) + { + mainCount.Enabled = main.SelectedIndex == 1; + detailCount.Enabled = detail.SelectedIndex == 1; + } + + private void SaveDetectionClick(object sender, EventArgs e) + { + if (GoodsComplianceService.Keywords(keywords.Text).Length == 0) + { + MessageBox.Show(this, "请输入检测关键词。"); + keywords.Focus(); + return; + } + var settings = GlobalConfig.GlobalSettings; + var previous = settings.GoodsCompliance; + settings.GoodsCompliance = new ComplianceOptions + { + MainCount = main.SelectedIndex == 0 ? (int?)null : (int)mainCount.Value, + DetailCount = detail.SelectedIndex == 2 ? (int?)null : detail.SelectedIndex == 0 ? 0 : (int)detailCount.Value, + IncludeSku = sku.Checked, + Keywords = keywords.Text.Trim() + }; + try { StartUp.SaveGlobalSettings(); } + catch (Exception ex) + { + settings.GoodsCompliance = previous; + MessageBox.Show(this, "保存检测条件失败:" + ex.Message); + return; + } + MessageBox.Show(this, "已保存 AI 检测条件。"); + } + + } +} diff --git a/TEMUKJD/PDDForms/ProductForms/PDDProductForm.cs b/TEMUKJD/PDDForms/ProductForms/PDDProductForm.cs index 9fc1e30..eb62e23 100644 --- a/TEMUKJD/PDDForms/ProductForms/PDDProductForm.cs +++ b/TEMUKJD/PDDForms/ProductForms/PDDProductForm.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using TemuKJD.BaseForms; +using TemuKJD.WXForms.ProductForms; using AllBase; using TemuKJD.PDDForms.ProductSelectForms; using TemuKJD.PDDForms.ProductForms.CouponForms; diff --git a/TEMUKJD/UserComponent/UcNumericRange.cs b/TEMUKJD/UserComponent/UcNumericRange.cs new file mode 100644 index 0000000..2d1cdfd --- /dev/null +++ b/TEMUKJD/UserComponent/UcNumericRange.cs @@ -0,0 +1,159 @@ +using System; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace TemuKJD.UserComponent +{ + [DefaultEvent(nameof(ValueChanged))] + public class UcNumericRange : UserControl + { + private const decimal DefaultMaximum = 1000000m; + private const int NormalWidth = 220; + private const int CompactWidth = 112; + private readonly Label separator = new Label { Text = "至", AutoSize = true, Anchor = AnchorStyles.None }; + private readonly NumericUpDown lower = CreateEditor("起始值"); + private readonly NumericUpDown upper = CreateEditor("结束值"); + private bool updating; + + public UcNumericRange() + { + upper.Value = DefaultMaximum; + var layout = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 3, RowCount = 1 }; + layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); + layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50)); + layout.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); + layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50)); + layout.Controls.Add(CreateCell(lower), 0, 0); + layout.Controls.Add(separator, 1, 0); + layout.Controls.Add(CreateCell(upper), 2, 0); + Controls.Add(layout); + Size = new Size(NormalWidth, lower.PreferredHeight + 2); + lower.ValueChanged += (s, e) => UpdateRange(lower, upper); + upper.ValueChanged += (s, e) => UpdateRange(upper, lower); + } + + [Description("两个数值之间显示的文字。"), DefaultValue("至")] + public string SeparatorText + { + get => separator.Text; + set => separator.Text = value; + } + + [Description("使用窄布局并隐藏微调按钮,仍可用方向键调整数值。"), DefaultValue(false)] + public bool Compact + { + get => separator.Margin == Padding.Empty; + set + { + if (value == Compact) return; + separator.Margin = value ? Padding.Empty : new Padding(3, 0, 3, 0); + SetCompactBorder(lower, value); + SetCompactBorder(upper, value); + if (Dock == DockStyle.None && Width == (value ? NormalWidth : CompactWidth)) + Width = value ? CompactWidth : NormalWidth; + } + } + + private static void SetCompactBorder(NumericUpDown editor, bool compact) + { + var cell = (Panel)editor.Parent; + editor.BorderStyle = compact ? BorderStyle.None : BorderStyle.Fixed3D; + cell.BorderStyle = compact ? BorderStyle.FixedSingle : BorderStyle.None; + cell.BackColor = compact ? editor.BackColor : Color.Empty; + cell.PerformLayout(); + } + + private Panel CreateCell(NumericUpDown editor) + { + var cell = new Panel { Dock = DockStyle.Fill, Margin = Padding.Empty }; + cell.Controls.Add(editor); + var buttons = editor.Controls[0] is TextBox ? editor.Controls[1] : editor.Controls[0]; + // 紧凑模式将右侧按钮移出可见区域,保留原生数字输入和键盘操作。 + cell.Layout += (s, e) => + { + int clippedWidth = Compact ? editor.Width - buttons.Left : 0; + editor.Width = cell.ClientSize.Width + clippedWidth; + editor.Top = Compact ? Math.Max(0, (cell.ClientSize.Height - editor.Height) / 2) : 0; + }; + return cell; + } + + private static NumericUpDown CreateEditor(string name) + { + return new NumericUpDown { AccessibleName = name, Margin = Padding.Empty, Maximum = DefaultMaximum }; + } + + [Description("允许输入的最小值。"), DefaultValue(typeof(decimal), "0")] + public decimal Minimum + { + get => lower.Minimum; + set => SetLimits(value, Math.Max(value, Maximum)); + } + + [Description("允许输入的最大值。"), DefaultValue(typeof(decimal), "1000000")] + public decimal Maximum + { + get => lower.Maximum; + set => SetLimits(Math.Min(value, Minimum), value); + } + + [Description("选中范围的起始值。"), DefaultValue(typeof(decimal), "0")] + public decimal LowerValue + { + get => lower.Value; + set => lower.Value = value; + } + + [Description("选中范围的结束值。"), DefaultValue(typeof(decimal), "1000000")] + public decimal UpperValue + { + get => upper.Value; + set => upper.Value = value; + } + + [Description("数字输入框显示的小数位数。"), DefaultValue(0)] + public int DecimalPlaces + { + get => lower.DecimalPlaces; + set + { + if (value < 0 || value > 28) throw new ArgumentOutOfRangeException(nameof(value)); + lower.DecimalPlaces = upper.DecimalPlaces = value; + decimal increment = 1m; + for (int i = 0; i < value; i++) + increment /= 10m; + lower.Increment = upper.Increment = increment; + } + } + + public event EventHandler ValueChanged; + + private void SetLimits(decimal minimum, decimal maximum) + { + decimal oldLower = LowerValue, oldUpper = UpperValue; + updating = true; + try + { + lower.Minimum = upper.Minimum = minimum; + lower.Maximum = upper.Maximum = maximum; + } + finally { updating = false; } + if (LowerValue != oldLower || UpperValue != oldUpper) + ValueChanged?.Invoke(this, EventArgs.Empty); + } + + private void UpdateRange(NumericUpDown edited, NumericUpDown other) + { + if (updating) return; + updating = true; + try + { + if (LowerValue > UpperValue) + other.Value = edited.Value; + } + finally { updating = false; } + ValueChanged?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/TEMUKJD/UserForm/LoginForm.cs b/TEMUKJD/UserForm/LoginForm.cs index b5b6ef8..93215cf 100644 --- a/TEMUKJD/UserForm/LoginForm.cs +++ b/TEMUKJD/UserForm/LoginForm.cs @@ -288,28 +288,28 @@ namespace TemuKJD.UserForm - _ = Task.Run(() => - { - try - { - StoreApiClient.Instance.Auth.Login(new LoginRequest - { - UserName = BaseClass.UserName, - Password = BaseClass.PassWord - }); - } - catch (Exception ex) - { - try - { - LogSave.AddFile("Store API 登录失败:" + ex.Message); - } - catch - { - // 日志失败也不能影响主程序。 - } - } - }); + //_ = Task.Run(() => + //{ + // try + // { + // StoreApiClient.Instance.Auth.Login(new LoginRequest + // { + // UserName = BaseClass.UserName, + // Password = BaseClass.PassWord + // }); + // } + // catch (Exception ex) + // { + // try + // { + // LogSave.AddFile("Store API 登录失败:" + ex.Message); + // } + // catch + // { + // // 日志失败也不能影响主程序。 + // } + // } + //}); Hide(); mf.Hide(); diff --git a/TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsFavoriteForm.Designer.cs b/TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.Designer.cs similarity index 74% rename from TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsFavoriteForm.Designer.cs rename to TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.Designer.cs index 40e20e3..ea108f4 100644 --- a/TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsFavoriteForm.Designer.cs +++ b/TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.Designer.cs @@ -1,5 +1,5 @@ -namespace TemuKJD.PDDForms.ProductSelectForms +namespace TemuKJD.WXForms.ProductForms { partial class CloudGoodsFavoriteForm { @@ -29,25 +29,25 @@ namespace TemuKJD.PDDForms.ProductSelectForms /// private void InitializeComponent() { - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle9 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle10 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.cb_safe = new System.Windows.Forms.ComboBox(); + this.label4 = new System.Windows.Forms.Label(); + this.num_price = new TemuKJD.UserComponent.UcNumericRange(); + this.num_sold = new TemuKJD.UserComponent.UcNumericRange(); this.panel1 = new System.Windows.Forms.Panel(); + this.btnSafeDetection = new DevComponents.DotNetBar.PanelEx(); this.btn_removefavorite = new DevComponents.DotNetBar.PanelEx(); this.bt_importUploadList = new DevComponents.DotNetBar.PanelEx(); this.label5r = new System.Windows.Forms.Label(); - this.cb_Platform = new System.Windows.Forms.ComboBox(); + this.cb_Platformr = new System.Windows.Forms.ComboBox(); this.cmbCatSelect2 = new System.Windows.Forms.ComboBox(); this.cmbCatSelect1 = new System.Windows.Forms.ComboBox(); this.cmbCatSelect4 = new System.Windows.Forms.ComboBox(); this.cmbCatSelect3 = new System.Windows.Forms.ComboBox(); - this.label4 = new System.Windows.Forms.Label(); - this.num_maxprice = new System.Windows.Forms.NumericUpDown(); - this.num_minprice = new System.Windows.Forms.NumericUpDown(); this.label7 = new System.Windows.Forms.Label(); this.label3 = new System.Windows.Forms.Label(); - this.num_maxsold = new System.Windows.Forms.NumericUpDown(); - this.num_minsold = new System.Windows.Forms.NumericUpDown(); this.txt_title = new System.Windows.Forms.TextBox(); this.label1 = new System.Windows.Forms.Label(); this.label6 = new System.Windows.Forms.Label(); @@ -57,6 +57,9 @@ namespace TemuKJD.PDDForms.ProductSelectForms this.splitContainer2 = new System.Windows.Forms.SplitContainer(); this.pbLoading = new System.Windows.Forms.PictureBox(); this.dgvData = new TemuKJD.UserComponent.DataGridViewEx(); + this.ucSplitPage1 = new TemuKJD.UserControls.UcSplitPage(); + this.statusBar = new TemuKJD.UserControls.StatusBarUserControl(); + this.numericUpDown1 = new System.Windows.Forms.NumericUpDown(); this.colSelect = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.c_Platform = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.Column1 = new System.Windows.Forms.DataGridViewImageColumn(); @@ -64,23 +67,19 @@ namespace TemuKJD.PDDForms.ProductSelectForms this.Column2 = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.Column3 = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.Column4 = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.ucSplitPage1 = new TemuKJD.UserControls.UcSplitPage(); - this.statusBar = new TemuKJD.UserControls.StatusBarUserControl(); + this.colDetectionResult = new System.Windows.Forms.DataGridViewTextBoxColumn(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); this.splitContainer1.Panel1.SuspendLayout(); this.splitContainer1.Panel2.SuspendLayout(); this.splitContainer1.SuspendLayout(); this.panel1.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.num_maxprice)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.num_minprice)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.num_maxsold)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.num_minsold)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit(); this.splitContainer2.Panel1.SuspendLayout(); this.splitContainer2.Panel2.SuspendLayout(); this.splitContainer2.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.pbLoading)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.dgvData)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit(); this.SuspendLayout(); // // splitContainer1 @@ -94,20 +93,19 @@ namespace TemuKJD.PDDForms.ProductSelectForms // // splitContainer1.Panel1 // + this.splitContainer1.Panel1.Controls.Add(this.cb_safe); + this.splitContainer1.Panel1.Controls.Add(this.label4); + this.splitContainer1.Panel1.Controls.Add(this.num_price); + this.splitContainer1.Panel1.Controls.Add(this.num_sold); this.splitContainer1.Panel1.Controls.Add(this.panel1); this.splitContainer1.Panel1.Controls.Add(this.label5r); - this.splitContainer1.Panel1.Controls.Add(this.cb_Platform); + this.splitContainer1.Panel1.Controls.Add(this.cb_Platformr); this.splitContainer1.Panel1.Controls.Add(this.cmbCatSelect2); this.splitContainer1.Panel1.Controls.Add(this.cmbCatSelect1); this.splitContainer1.Panel1.Controls.Add(this.cmbCatSelect4); this.splitContainer1.Panel1.Controls.Add(this.cmbCatSelect3); - this.splitContainer1.Panel1.Controls.Add(this.label4); - this.splitContainer1.Panel1.Controls.Add(this.num_maxprice); - this.splitContainer1.Panel1.Controls.Add(this.num_minprice); this.splitContainer1.Panel1.Controls.Add(this.label7); this.splitContainer1.Panel1.Controls.Add(this.label3); - this.splitContainer1.Panel1.Controls.Add(this.num_maxsold); - this.splitContainer1.Panel1.Controls.Add(this.num_minsold); this.splitContainer1.Panel1.Controls.Add(this.txt_title); this.splitContainer1.Panel1.Controls.Add(this.label1); this.splitContainer1.Panel1.Controls.Add(this.label6); @@ -119,26 +117,133 @@ namespace TemuKJD.PDDForms.ProductSelectForms // this.splitContainer1.Panel2.Controls.Add(this.splitContainer2); this.splitContainer1.Panel2.Margin = new System.Windows.Forms.Padding(4); - this.splitContainer1.Size = new System.Drawing.Size(1643, 530); + this.splitContainer1.Size = new System.Drawing.Size(1719, 530); this.splitContainer1.TabIndex = 35; + // + // cb_safe + // + this.cb_safe.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cb_safe.DropDownWidth = 150; + this.cb_safe.FormattingEnabled = true; + this.cb_safe.Items.AddRange(new object[] { + "全部", + "安全", + "违规", + "未检测"}); + this.cb_safe.Location = new System.Drawing.Point(1141, 13); + this.cb_safe.Name = "cb_safe"; + this.cb_safe.Size = new System.Drawing.Size(97, 25); + this.cb_safe.TabIndex = 930; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(1104, 17); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(44, 17); + this.label4.TabIndex = 929; + this.label4.Text = "检测:"; + // + // num_price + // + this.num_price.Compact = true; + this.num_price.Location = new System.Drawing.Point(391, 15); + this.num_price.LowerValue = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.num_price.Maximum = new decimal(new int[] { + 9999999, + 0, + 0, + 0}); + this.num_price.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.num_price.Name = "num_price"; + this.num_price.SeparatorText = ""; + this.num_price.Size = new System.Drawing.Size(108, 23); + this.num_price.TabIndex = 922; + this.num_price.UpperValue = new decimal(new int[] { + 9999999, + 0, + 0, + 0}); + // + // num_sold + // + this.num_sold.Compact = true; + this.num_sold.Location = new System.Drawing.Point(218, 15); + this.num_sold.LowerValue = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.num_sold.Maximum = new decimal(new int[] { + 9999999, + 0, + 0, + 0}); + this.num_sold.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.num_sold.Name = "num_sold"; + this.num_sold.SeparatorText = ""; + this.num_sold.Size = new System.Drawing.Size(108, 23); + this.num_sold.TabIndex = 921; + this.num_sold.UpperValue = new decimal(new int[] { + 9999999, + 0, + 0, + 0}); // // panel1 // + this.panel1.Controls.Add(this.btnSafeDetection); this.panel1.Controls.Add(this.btn_removefavorite); this.panel1.Controls.Add(this.bt_importUploadList); this.panel1.Dock = System.Windows.Forms.DockStyle.Right; - this.panel1.Location = new System.Drawing.Point(1507, 0); + this.panel1.Location = new System.Drawing.Point(1509, 0); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(136, 50); + this.panel1.Size = new System.Drawing.Size(210, 50); this.panel1.TabIndex = 914; // + // btnSafeDetection + // + this.btnSafeDetection.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled; + this.btnSafeDetection.Cursor = System.Windows.Forms.Cursors.Hand; + this.btnSafeDetection.DisabledBackColor = System.Drawing.Color.Empty; + this.btnSafeDetection.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.btnSafeDetection.Location = new System.Drawing.Point(132, 14); + this.btnSafeDetection.Name = "btnSafeDetection"; + this.btnSafeDetection.Size = new System.Drawing.Size(58, 23); + this.btnSafeDetection.Style.Alignment = System.Drawing.StringAlignment.Center; + this.btnSafeDetection.Style.BackColor1.Color = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255))))); + this.btnSafeDetection.Style.BackColor2.Color = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255))))); + this.btnSafeDetection.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder; + this.btnSafeDetection.Style.BorderDashStyle = System.Drawing.Drawing2D.DashStyle.Dash; + this.btnSafeDetection.Style.BorderSide = DevComponents.DotNetBar.eBorderSide.None; + this.btnSafeDetection.Style.ForeColor.Color = System.Drawing.Color.White; + this.btnSafeDetection.Style.GradientAngle = 90; + this.btnSafeDetection.StyleMouseOver.Alignment = System.Drawing.StringAlignment.Center; + this.btnSafeDetection.StyleMouseOver.BackColor1.Color = System.Drawing.Color.FromArgb(((int)(((byte)(25)))), ((int)(((byte)(102)))), ((int)(((byte)(255))))); + this.btnSafeDetection.StyleMouseOver.BackColor2.Color = System.Drawing.Color.FromArgb(((int)(((byte)(25)))), ((int)(((byte)(102)))), ((int)(((byte)(255))))); + this.btnSafeDetection.TabIndex = 451; + this.btnSafeDetection.Text = "AI检测"; + this.btnSafeDetection.Click += new System.EventHandler(this.btnSafeDetection_Click); + // // btn_removefavorite // this.btn_removefavorite.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled; this.btn_removefavorite.Cursor = System.Windows.Forms.Cursors.Hand; this.btn_removefavorite.DisabledBackColor = System.Drawing.Color.Empty; this.btn_removefavorite.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btn_removefavorite.Location = new System.Drawing.Point(67, 14); + this.btn_removefavorite.Location = new System.Drawing.Point(68, 14); this.btn_removefavorite.Name = "btn_removefavorite"; this.btn_removefavorite.Size = new System.Drawing.Size(58, 23); this.btn_removefavorite.Style.Alignment = System.Drawing.StringAlignment.Center; @@ -183,31 +288,31 @@ namespace TemuKJD.PDDForms.ProductSelectForms // label5r // this.label5r.AutoSize = true; - this.label5r.Location = new System.Drawing.Point(1048, 16); + this.label5r.Location = new System.Drawing.Point(942, 16); this.label5r.Name = "label5r"; this.label5r.Size = new System.Drawing.Size(44, 17); this.label5r.TabIndex = 907; this.label5r.Text = "来源:"; // - // cb_Platform + // cb_Platformr // - this.cb_Platform.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cb_Platform.DropDownWidth = 150; - this.cb_Platform.FormattingEnabled = true; - this.cb_Platform.Items.AddRange(new object[] { + this.cb_Platformr.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cb_Platformr.DropDownWidth = 150; + this.cb_Platformr.FormattingEnabled = true; + this.cb_Platformr.Items.AddRange(new object[] { "全部", "多多", "微信"}); - this.cb_Platform.Location = new System.Drawing.Point(1098, 13); - this.cb_Platform.Name = "cb_Platform"; - this.cb_Platform.Size = new System.Drawing.Size(97, 25); - this.cb_Platform.TabIndex = 906; + this.cb_Platformr.Location = new System.Drawing.Point(992, 13); + this.cb_Platformr.Name = "cb_Platformr"; + this.cb_Platformr.Size = new System.Drawing.Size(97, 25); + this.cb_Platformr.TabIndex = 906; // // cmbCatSelect2 // this.cmbCatSelect2.DropDownWidth = 150; this.cmbCatSelect2.FormattingEnabled = true; - this.cmbCatSelect2.Location = new System.Drawing.Point(745, 12); + this.cmbCatSelect2.Location = new System.Drawing.Point(639, 12); this.cmbCatSelect2.Name = "cmbCatSelect2"; this.cmbCatSelect2.Size = new System.Drawing.Size(97, 25); this.cmbCatSelect2.TabIndex = 897; @@ -216,7 +321,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms // this.cmbCatSelect1.DropDownWidth = 150; this.cmbCatSelect1.FormattingEnabled = true; - this.cmbCatSelect1.Location = new System.Drawing.Point(648, 12); + this.cmbCatSelect1.Location = new System.Drawing.Point(542, 12); this.cmbCatSelect1.Name = "cmbCatSelect1"; this.cmbCatSelect1.Size = new System.Drawing.Size(97, 25); this.cmbCatSelect1.TabIndex = 896; @@ -225,7 +330,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms // this.cmbCatSelect4.DropDownWidth = 150; this.cmbCatSelect4.FormattingEnabled = true; - this.cmbCatSelect4.Location = new System.Drawing.Point(939, 12); + this.cmbCatSelect4.Location = new System.Drawing.Point(833, 12); this.cmbCatSelect4.Name = "cmbCatSelect4"; this.cmbCatSelect4.Size = new System.Drawing.Size(97, 25); this.cmbCatSelect4.TabIndex = 899; @@ -234,50 +339,15 @@ namespace TemuKJD.PDDForms.ProductSelectForms // this.cmbCatSelect3.DropDownWidth = 150; this.cmbCatSelect3.FormattingEnabled = true; - this.cmbCatSelect3.Location = new System.Drawing.Point(842, 12); + this.cmbCatSelect3.Location = new System.Drawing.Point(736, 12); this.cmbCatSelect3.Name = "cmbCatSelect3"; this.cmbCatSelect3.Size = new System.Drawing.Size(97, 25); this.cmbCatSelect3.TabIndex = 898; // - // label4 - // - this.label4.AutoSize = true; - this.label4.ForeColor = System.Drawing.Color.Gray; - this.label4.Location = new System.Drawing.Point(518, 17); - this.label4.Margin = new System.Windows.Forms.Padding(0); - this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(13, 17); - this.label4.TabIndex = 488; - this.label4.Text = "-"; - // - // num_maxprice - // - this.num_maxprice.Location = new System.Drawing.Point(534, 14); - this.num_maxprice.Maximum = new decimal(new int[] { - 99999, - 0, - 0, - 0}); - this.num_maxprice.Name = "num_maxprice"; - this.num_maxprice.Size = new System.Drawing.Size(70, 23); - this.num_maxprice.TabIndex = 487; - // - // num_minprice - // - this.num_minprice.Location = new System.Drawing.Point(447, 14); - this.num_minprice.Maximum = new decimal(new int[] { - 99999, - 0, - 0, - 0}); - this.num_minprice.Name = "num_minprice"; - this.num_minprice.Size = new System.Drawing.Size(70, 23); - this.num_minprice.TabIndex = 486; - // // label7 // this.label7.AutoSize = true; - this.label7.Location = new System.Drawing.Point(388, 17); + this.label7.Location = new System.Drawing.Point(332, 17); this.label7.Name = "label7"; this.label7.Size = new System.Drawing.Size(64, 17); this.label7.TabIndex = 485; @@ -294,30 +364,6 @@ namespace TemuKJD.PDDForms.ProductSelectForms this.label3.TabIndex = 484; this.label3.Text = "-"; // - // num_maxsold - // - this.num_maxsold.Location = new System.Drawing.Point(310, 14); - this.num_maxsold.Maximum = new decimal(new int[] { - 9999999, - 0, - 0, - 0}); - this.num_maxsold.Name = "num_maxsold"; - this.num_maxsold.Size = new System.Drawing.Size(70, 23); - this.num_maxsold.TabIndex = 483; - // - // num_minsold - // - this.num_minsold.Location = new System.Drawing.Point(223, 14); - this.num_minsold.Maximum = new decimal(new int[] { - 9999999, - 0, - 0, - 0}); - this.num_minsold.Name = "num_minsold"; - this.num_minsold.Size = new System.Drawing.Size(70, 23); - this.num_minsold.TabIndex = 482; - // // txt_title // this.txt_title.Location = new System.Drawing.Point(60, 14); @@ -337,7 +383,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms // label6 // this.label6.AutoSize = true; - this.label6.Location = new System.Drawing.Point(610, 17); + this.label6.Location = new System.Drawing.Point(504, 17); this.label6.Name = "label6"; this.label6.Size = new System.Drawing.Size(44, 17); this.label6.TabIndex = 440; @@ -349,7 +395,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms this.btnSearch.Cursor = System.Windows.Forms.Cursors.Hand; this.btnSearch.DisabledBackColor = System.Drawing.Color.Empty; this.btnSearch.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnSearch.Location = new System.Drawing.Point(1208, 14); + this.btnSearch.Location = new System.Drawing.Point(1257, 14); this.btnSearch.Name = "btnSearch"; this.btnSearch.Size = new System.Drawing.Size(58, 23); this.btnSearch.Style.Alignment = System.Drawing.StringAlignment.Center; @@ -372,7 +418,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms this.pnlEmpty.CanvasColor = System.Drawing.SystemColors.Control; this.pnlEmpty.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled; this.pnlEmpty.DisabledBackColor = System.Drawing.Color.Empty; - this.pnlEmpty.Location = new System.Drawing.Point(1272, 14); + this.pnlEmpty.Location = new System.Drawing.Point(1321, 15); this.pnlEmpty.Name = "pnlEmpty"; this.pnlEmpty.Size = new System.Drawing.Size(58, 23); this.pnlEmpty.Style.Alignment = System.Drawing.StringAlignment.Center; @@ -420,7 +466,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms // this.splitContainer2.Panel2.Controls.Add(this.ucSplitPage1); this.splitContainer2.Panel2.Controls.Add(this.statusBar); - this.splitContainer2.Size = new System.Drawing.Size(1643, 476); + this.splitContainer2.Size = new System.Drawing.Size(1719, 476); this.splitContainer2.SplitterDistance = 447; this.splitContainer2.TabIndex = 0; // @@ -428,7 +474,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms // this.pbLoading.Anchor = System.Windows.Forms.AnchorStyles.None; this.pbLoading.Image = global::TemuKJD.Properties.Resources.loading_blue; - this.pbLoading.Location = new System.Drawing.Point(796, 198); + this.pbLoading.Location = new System.Drawing.Point(834, 198); this.pbLoading.Name = "pbLoading"; this.pbLoading.Size = new System.Drawing.Size(50, 50); this.pbLoading.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; @@ -439,20 +485,20 @@ namespace TemuKJD.PDDForms.ProductSelectForms // this.dgvData.AllowUserToAddRows = false; this.dgvData.AllowUserToDeleteRows = false; - dataGridViewCellStyle9.BackColor = System.Drawing.Color.WhiteSmoke; - this.dgvData.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle9; + dataGridViewCellStyle1.BackColor = System.Drawing.Color.WhiteSmoke; + this.dgvData.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1; this.dgvData.AutoRowNumber = true; this.dgvData.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill; this.dgvData.BackgroundColor = System.Drawing.Color.White; this.dgvData.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255))))); - dataGridViewCellStyle10.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; - dataGridViewCellStyle10.BackColor = System.Drawing.SystemColors.Control; - dataGridViewCellStyle10.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - dataGridViewCellStyle10.ForeColor = System.Drawing.SystemColors.WindowText; - dataGridViewCellStyle10.SelectionBackColor = System.Drawing.SystemColors.Highlight; - dataGridViewCellStyle10.SelectionForeColor = System.Drawing.SystemColors.HighlightText; - dataGridViewCellStyle10.WrapMode = System.Windows.Forms.DataGridViewTriState.False; - this.dgvData.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle10; + dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; + dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Control; + dataGridViewCellStyle2.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.WindowText; + dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight; + dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText; + dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False; + this.dgvData.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2; this.dgvData.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.dgvData.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { this.colSelect, @@ -461,7 +507,8 @@ namespace TemuKJD.PDDForms.ProductSelectForms this.c_goodsid, this.Column2, this.Column3, - this.Column4}); + this.Column4, + this.colDetectionResult}); this.dgvData.Dock = System.Windows.Forms.DockStyle.Fill; this.dgvData.Location = new System.Drawing.Point(4, 0); this.dgvData.MultiSelectCheckBox = true; @@ -469,9 +516,44 @@ namespace TemuKJD.PDDForms.ProductSelectForms this.dgvData.ReadOnly = true; this.dgvData.RowTemplate.Height = 60; this.dgvData.ShowCellErrors = false; - this.dgvData.Size = new System.Drawing.Size(1635, 447); + this.dgvData.Size = new System.Drawing.Size(1711, 447); this.dgvData.TabIndex = 5; // + // ucSplitPage1 + // + this.ucSplitPage1.CurrentPage = 1; + this.ucSplitPage1.Dock = System.Windows.Forms.DockStyle.Right; + this.ucSplitPage1.IsShowPageSize = true; + this.ucSplitPage1.IsShowTotalCount = true; + this.ucSplitPage1.Location = new System.Drawing.Point(1257, 0); + this.ucSplitPage1.Margin = new System.Windows.Forms.Padding(0); + this.ucSplitPage1.MaxPageSizeValue = 2000; + this.ucSplitPage1.Name = "ucSplitPage1"; + this.ucSplitPage1.PageSize = 50; + this.ucSplitPage1.Size = new System.Drawing.Size(462, 25); + this.ucSplitPage1.TabIndex = 4; + this.ucSplitPage1.TotalCount = ((long)(0)); + this.ucSplitPage1.TotalPage = 1; + // + // statusBar + // + this.statusBar.BackColor = System.Drawing.Color.White; + this.statusBar.Dock = System.Windows.Forms.DockStyle.Fill; + this.statusBar.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.statusBar.Location = new System.Drawing.Point(0, 0); + this.statusBar.Margin = new System.Windows.Forms.Padding(0); + this.statusBar.Name = "statusBar"; + this.statusBar.Size = new System.Drawing.Size(1719, 25); + this.statusBar.TabIndex = 1; + this.statusBar.Visible = false; + // + // numericUpDown1 + // + this.numericUpDown1.Location = new System.Drawing.Point(918, 274); + this.numericUpDown1.Name = "numericUpDown1"; + this.numericUpDown1.Size = new System.Drawing.Size(120, 21); + this.numericUpDown1.TabIndex = 7; + // // colSelect // this.colSelect.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; @@ -537,38 +619,20 @@ namespace TemuKJD.PDDForms.ProductSelectForms this.Column4.ReadOnly = true; this.Column4.Width = 120; // - // ucSplitPage1 + // colDetectionResult // - this.ucSplitPage1.CurrentPage = 1; - this.ucSplitPage1.Dock = System.Windows.Forms.DockStyle.Right; - this.ucSplitPage1.IsShowPageSize = true; - this.ucSplitPage1.IsShowTotalCount = true; - this.ucSplitPage1.Location = new System.Drawing.Point(1181, 0); - this.ucSplitPage1.Margin = new System.Windows.Forms.Padding(0); - this.ucSplitPage1.MaxPageSizeValue = 2000; - this.ucSplitPage1.Name = "ucSplitPage1"; - this.ucSplitPage1.PageSize = 50; - this.ucSplitPage1.Size = new System.Drawing.Size(462, 25); - this.ucSplitPage1.TabIndex = 4; - this.ucSplitPage1.TotalCount = ((long)(0)); - this.ucSplitPage1.TotalPage = 1; - // - // statusBar - // - this.statusBar.BackColor = System.Drawing.Color.White; - this.statusBar.Dock = System.Windows.Forms.DockStyle.Fill; - this.statusBar.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.statusBar.Location = new System.Drawing.Point(0, 0); - this.statusBar.Margin = new System.Windows.Forms.Padding(0); - this.statusBar.Name = "statusBar"; - this.statusBar.Size = new System.Drawing.Size(1643, 25); - this.statusBar.TabIndex = 1; - this.statusBar.Visible = false; + this.colDetectionResult.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; + this.colDetectionResult.HeaderText = "AI检测结果"; + this.colDetectionResult.MinimumWidth = 50; + this.colDetectionResult.Name = "colDetectionResult"; + this.colDetectionResult.ReadOnly = true; + this.colDetectionResult.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + this.colDetectionResult.Width = 160; // // CloudGoodsFavoriteForm // this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; - this.ClientSize = new System.Drawing.Size(1643, 530); + this.ClientSize = new System.Drawing.Size(1719, 530); this.Controls.Add(this.splitContainer1); this.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); this.KeyPreview = true; @@ -581,16 +645,13 @@ namespace TemuKJD.PDDForms.ProductSelectForms ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); this.splitContainer1.ResumeLayout(false); this.panel1.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.num_maxprice)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.num_minprice)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.num_maxsold)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.num_minsold)).EndInit(); this.splitContainer2.Panel1.ResumeLayout(false); this.splitContainer2.Panel2.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit(); this.splitContainer2.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.pbLoading)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.dgvData)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit(); this.ResumeLayout(false); } @@ -609,11 +670,6 @@ namespace TemuKJD.PDDForms.ProductSelectForms private System.Windows.Forms.Label label6; private System.Windows.Forms.TextBox txt_title; private System.Windows.Forms.Label label1; - private System.Windows.Forms.NumericUpDown num_maxsold; - private System.Windows.Forms.NumericUpDown num_minsold; - private System.Windows.Forms.Label label4; - private System.Windows.Forms.NumericUpDown num_maxprice; - private System.Windows.Forms.NumericUpDown num_minprice; private System.Windows.Forms.Label label7; private System.Windows.Forms.Label label3; private System.Windows.Forms.ComboBox cmbCatSelect2; @@ -621,7 +677,16 @@ namespace TemuKJD.PDDForms.ProductSelectForms private System.Windows.Forms.ComboBox cmbCatSelect4; private System.Windows.Forms.ComboBox cmbCatSelect3; private System.Windows.Forms.Label label5r; - private System.Windows.Forms.ComboBox cb_Platform; + private System.Windows.Forms.ComboBox cb_Platformr; + private System.Windows.Forms.Panel panel1; + private DevComponents.DotNetBar.PanelEx btn_removefavorite; + private DevComponents.DotNetBar.PanelEx bt_importUploadList; + private DevComponents.DotNetBar.PanelEx btnSafeDetection; + private System.Windows.Forms.NumericUpDown numericUpDown1; + private UserComponent.UcNumericRange num_sold; + private UserComponent.UcNumericRange num_price; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.ComboBox cb_safe; private System.Windows.Forms.DataGridViewCheckBoxColumn colSelect; private System.Windows.Forms.DataGridViewTextBoxColumn c_Platform; private System.Windows.Forms.DataGridViewImageColumn Column1; @@ -629,8 +694,6 @@ namespace TemuKJD.PDDForms.ProductSelectForms private System.Windows.Forms.DataGridViewTextBoxColumn Column2; private System.Windows.Forms.DataGridViewTextBoxColumn Column3; private System.Windows.Forms.DataGridViewTextBoxColumn Column4; - private System.Windows.Forms.Panel panel1; - private DevComponents.DotNetBar.PanelEx btn_removefavorite; - private DevComponents.DotNetBar.PanelEx bt_importUploadList; + private System.Windows.Forms.DataGridViewTextBoxColumn colDetectionResult; } -} \ No newline at end of file +} diff --git a/TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsFavoriteForm.cs b/TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.cs similarity index 77% rename from TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsFavoriteForm.cs rename to TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.cs index 6590cc1..ed9a9d9 100644 --- a/TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsFavoriteForm.cs +++ b/TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.cs @@ -1,3 +1,4 @@ +using TemuKJD.BLL; using AllBase; using AllBase.Helper; using AllBase.Model.Result; @@ -19,28 +20,30 @@ using System.Net; using System.Net.Http; using System.Security.Policy; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using TemuKJD.BaseForms; using TemuKJD.Extension; using TemuKJD.ToolForms; using TemuKJD.UserControls; -using TemuKJD.WXForms.ProductForms; using Work; using Work.GoodDetail; using Work.Services; -namespace TemuKJD.PDDForms.ProductSelectForms +namespace TemuKJD.WXForms.ProductForms { public partial class CloudGoodsFavoriteForm : BaseForm { private GoodsSearchRequest search = new GoodsSearchRequest(); private List catBoxList = new List(); private BindingList dataList = new BindingList(); + private CancellationTokenSource detection; CheckBox chkSelectAll = null; public CloudGoodsFavoriteForm() { InitializeComponent(); + Disposed += (sender, e) => detection?.Cancel(); } private void AdminCardCodeForm_Load(object sender, EventArgs e) @@ -50,13 +53,14 @@ namespace TemuKJD.PDDForms.ProductSelectForms InitCategory(); InitBindingDataGridView(); - - num_minsold.Value = 0; - num_maxsold.Value = 9999999; - num_minprice.Value = 0; - num_maxprice.Value = 999; + num_sold.LowerValue = 1; + num_sold.UpperValue = 9999999; + num_price.LowerValue = 1; + num_price.UpperValue = 9999999; + + cb_Platformr.SelectedIndex = 0; + cb_safe.SelectedIndex = 0; - cb_Platform.SelectedIndex = 0; ucSplitPage1.TotalCount = 0; @@ -79,6 +83,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms this.Column3.DataPropertyName = nameof(CloudGoods.Price); this.Column4.DataPropertyName = nameof(CloudGoods.SoldQuantity); this.c_goodsid.DataPropertyName = nameof(CloudGoods.GoodsId); + this.colDetectionResult.DataPropertyName = nameof(CloudGoods.DetectionResult); this.c_Platform.DataPropertyName = nameof(CloudGoods.Platform); // 2. 设置列样式 // 时间列格式化 @@ -311,6 +316,8 @@ namespace TemuKJD.PDDForms.ProductSelectForms } private async void btnSearch_Click(object sender, EventArgs e) { + if (detection != null) return; + activeSearches++; try { this.pbLoading.Visible = true; @@ -318,12 +325,13 @@ namespace TemuKJD.PDDForms.ProductSelectForms UserFavoritesGoodsSearchRequest search = new UserFavoritesGoodsSearchRequest(); - search.Platform = cb_Platform.SelectedIndex == 1 ? PlatformType.PDD : cb_Platform.SelectedIndex == 2 ? PlatformType.WX : PlatformType.None; + search.Platform = cb_Platformr.SelectedIndex == 1 ? PlatformType.PDD : cb_Platformr.SelectedIndex == 2 ? PlatformType.WX : PlatformType.None; + search.SafeStatus = cb_safe.SelectedIndex; search.Title = txt_title.Text; - search.MinSoldQuantity = (int)num_minsold.Value; - search.MaxSoldQuantity = (int)num_maxsold.Value; - search.MinPrice = (long)num_minprice.Value * 100; - search.MaxPrice = (long)num_maxprice.Value * 100; + search.MinSoldQuantity = (int)num_sold.LowerValue; + search.MaxSoldQuantity = (int)num_sold.UpperValue; + search.MinPrice = (long)(num_price.LowerValue * 100); + search.MaxPrice = (long)(num_price.UpperValue * 100); if (cmbCatSelect1.SelectedItem is PddCatInfo cat1) { search.CatId1 = (int)(cat1.cat_id); @@ -349,7 +357,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms finally { this.pbLoading.Visible = false; - btnSearch.Enabled = true; + btnSearch.Enabled = --activeSearches == 0; } @@ -357,26 +365,36 @@ namespace TemuKJD.PDDForms.ProductSelectForms private void pnlEmpty_Click(object sender, EventArgs e) { txt_title.Text = ""; - num_minsold.Value = 0; - num_maxsold.Value = 99999; - num_minprice.Value = 0; - num_maxprice.Value = 999; + num_sold.LowerValue = 1; + num_sold.UpperValue = 9999999; + num_price.LowerValue = 1; + num_price.UpperValue = 9999999; + cmbCatSelect1.SelectedIndex = 0; - cb_Platform.SelectedIndex = 0; + cb_Platformr.SelectedIndex = 0; + cb_safe.SelectedIndex = 0; ucSplitPage1.PageChange -= UcSplitPage1_PageChange; ucSplitPage1.CurrentPage = 1; ucSplitPage1.PageChange += UcSplitPage1_PageChange; } + private int latestGoodsQuery; + private int activeSearches; + public async Task GetGoods(UserFavoritesGoodsSearchRequest request) { - dataList.Clear(); + var query = ++latestGoodsQuery; PageData ret = await CloudGoodsService.SearchFavorites(request); Invoke(new Action(() => { - ret.List.ForEach(a => dataList.Add(a)); + if (query != latestGoodsQuery) return; + dataList.Clear(); + foreach (var goods in ret.List) + { + dataList.Add(goods); + } ucSplitPage1.TotalPage = Math.Max(1, ret.TotalPages); ucSplitPage1.TotalCount = ret.Total; this.pbLoading.Visible = false; @@ -424,7 +442,7 @@ namespace TemuKJD.PDDForms.ProductSelectForms product_name = item.Title, cat_name = catName, product_get_type_text = item.Platform == 1 ? ("多多") : item.Platform == 2 ? ("微信") : ("未知"), - db_type = "云端数据", + db_type = "云端数据", }); succ++; } @@ -444,13 +462,13 @@ namespace TemuKJD.PDDForms.ProductSelectForms private async void btn_removefavorite_Click(object sender, EventArgs e) { int succ = 0; - int err = 0; + int err = 0; try { this.pbLoading.Visible = true; btn_removefavorite.Enabled = false; var goodsIds = dataList.Where(a => a.IsSelected).Select(a => a.GoodsId).ToList(); - if(goodsIds.Count==0) + if (goodsIds.Count == 0) { BaseMsgForm.ShowInfo($"请勾选商品"); return; @@ -473,6 +491,69 @@ namespace TemuKJD.PDDForms.ProductSelectForms btn_removefavorite.Enabled = true; } } + /// + /// 批量AI检测 + /// + /// + /// + private async void btnSafeDetection_Click(object sender, EventArgs e) + { + if (detection != null) + { + detection.Cancel(); + btnSafeDetection.Text = "正在停止"; + btnSafeDetection.Enabled = false; + return; + } + if (!btnSearch.Enabled || !btn_removefavorite.Enabled) + { + BaseMsgForm.ShowInfo("请等待当前操作完成后再检测。"); + return; + } + var selected = dataList.Where(x => x.IsSelected) + .Select(x => new ComplianceGoods + { + id = x.GoodsId.ToString(), + name = x.Title, + image = x.ThumbUrl, + type = ComplianceGoods.Cloud, + platform = x.Platform + }).ToList(); + var locked = splitContainer1.Panel1.Controls.Cast().Where(c => c != panel1) + .Concat(panel1.Controls.Cast().Where(c => c != btnSafeDetection)) + .Concat(new Control[] { splitContainer2 }).Where(c => c.Enabled).ToList(); + var originalText = btnSafeDetection.Text; + detection = new CancellationTokenSource(); + try + { + foreach (var control in locked) control.Enabled = false; + btnSafeDetection.Text = "停止检测"; + var result = await GoodsComplianceService.DetectSelectedAsync(selected, ComplianceProductReader.Read, (id, status) => + { + if (IsDisposed) return; + foreach (var goods in dataList.Where(x => x.GoodsId.ToString() == id)) + goods.DetectionResult = status; + }, detection.Token); + if (!IsDisposed) + { + var title = result.Unfinished > 0 ? "AI 检测已停止" : "AI 检测完成"; + BaseMsgForm.ShowInfo($"{title}:安全 {result.Safe},有风险 {result.Risk},失败 {result.Failed},未完成 {result.Unfinished}。"); + } + } + catch (Exception ex) { if (!IsDisposed) MessageBox.Show(this, ex.Message); } + finally + { + detection.Dispose(); + detection = null; + if (!IsDisposed) + { + foreach (var control in locked) control.Enabled = true; + btnSafeDetection.Text = originalText; + btnSafeDetection.Enabled = true; + } + } + } + } diff --git a/TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsFavoriteForm.resx b/TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.resx similarity index 100% rename from TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsFavoriteForm.resx rename to TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.resx diff --git a/TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsForm.Designer.cs b/TEMUKJD/WXForms/ProductForms/CloudGoodsForm.Designer.cs similarity index 99% rename from TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsForm.Designer.cs rename to TEMUKJD/WXForms/ProductForms/CloudGoodsForm.Designer.cs index aafd523..7e00a66 100644 --- a/TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsForm.Designer.cs +++ b/TEMUKJD/WXForms/ProductForms/CloudGoodsForm.Designer.cs @@ -1,5 +1,5 @@ -namespace TemuKJD.PDDForms.ProductSelectForms +namespace TemuKJD.WXForms.ProductForms { partial class CloudGoodsForm { diff --git a/TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsForm.cs b/TEMUKJD/WXForms/ProductForms/CloudGoodsForm.cs similarity index 99% rename from TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsForm.cs rename to TEMUKJD/WXForms/ProductForms/CloudGoodsForm.cs index b47d007..626fa97 100644 --- a/TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsForm.cs +++ b/TEMUKJD/WXForms/ProductForms/CloudGoodsForm.cs @@ -25,12 +25,11 @@ using TemuKJD.BaseForms; using TemuKJD.Extension; using TemuKJD.ToolForms; using TemuKJD.UserControls; -using TemuKJD.WXForms.ProductForms; using Work; using Work.GoodDetail; using Work.Services; -namespace TemuKJD.PDDForms.ProductSelectForms +namespace TemuKJD.WXForms.ProductForms { public partial class CloudGoodsForm : BaseForm { diff --git a/TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsForm.resx b/TEMUKJD/WXForms/ProductForms/CloudGoodsForm.resx similarity index 100% rename from TEMUKJD/PDDForms/ProductSelectForms/CloudGoodsForm.resx rename to TEMUKJD/WXForms/ProductForms/CloudGoodsForm.resx diff --git a/TEMUKJD/WXForms/ProductForms/WXDBProdUploadForm.cs b/TEMUKJD/WXForms/ProductForms/WXDBProdUploadForm.cs index 78e8db1..a4887a8 100644 --- a/TEMUKJD/WXForms/ProductForms/WXDBProdUploadForm.cs +++ b/TEMUKJD/WXForms/ProductForms/WXDBProdUploadForm.cs @@ -1540,54 +1540,7 @@ namespace TemuKJD.WXForms.ProductForms /// 校验微信禁售商品 /// public static KJDDBProduct GetProductDetail(string prodDbId, out string errMsg, bool noViolationProduct = false, bool noBanWx = false) - { - errMsg = ""; - - if (string.IsNullOrEmpty(prodDbId)) - { - errMsg = "产品编号为空"; - return new KJDDBProduct(); - } - - List list = new List(); - - var url = BaseClass.BuildObjString("product/get_pdd_product_detail_list"); - var data = UserLogin.GetSignaturex() + "&search_db_ids=" + prodDbId; - if (noViolationProduct) - data += "&wx_safe_status=安全商品,未校验"; - if (noBanWx) - data += "&ban_wx=0"; - if (BaseClass.SaLevel > 0 || BaseClass.UserAuthority.Contains("爆款数据") || BaseClass.UserAuthority.Contains("学员数据") || BaseClass.ParentAuthority.Contains("学员数据")) - data += "&special_prod_type=普通商品,爆款数据"; - - var html = GetHtml.PostUtf8(url, data); - if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) - html = GetHtml.PostUtf8(url, data); - if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) - html = GetHtml.PostUtf8(url, data); - if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) - { - return null; - } - - var jArr = JObject.Parse(html)["data"]; - if (jArr.Count() == 0) - return null; - var sProd = JsonHelper.SerializeObject(jArr); - - try - { - list = JsonHelper.DeserializeJsonToList(sProd); - var item = list.FirstOrDefault(); - if (item == null) - item = new KJDDBProduct(); - return item; - } - catch (Exception ex) - { - return null; - } - } + => ComplianceProductReader.GetProductDetail(prodDbId, out errMsg, noViolationProduct, noBanWx); public static KJDDBProduct GetCloudProductDetail(TemuDBProductSelect selectProd, out string errMsg, bool noViolationProduct = false, bool noBanWx = false) { diff --git a/Work/Services/CloudGoodsService.cs b/Work/Services/CloudGoodsService.cs index 0c64d64..e304683 100644 --- a/Work/Services/CloudGoodsService.cs +++ b/Work/Services/CloudGoodsService.cs @@ -418,7 +418,13 @@ namespace Work.Services public static async Task> SearchFavorites(UserFavoritesGoodsSearchRequest request) { request.UserName = GetUserName(); - return await StoreApiClient.Instance.CloudGoods.SearchFavorites(request); + var page = await StoreApiClient.Instance.CloudGoods.SearchFavorites(request); + foreach (var item in page.List) + { + item.DetectionResult = item.IsSafe == null ? "未检测" : item.IsSafe.Value ? "安全" : "违规"; + } + + return page; } /// @@ -576,6 +582,8 @@ namespace Work.Services /// 平台类型 /// public PlatformType Platform { get; set; } + /// 安全状态:0 全部(默认),1 安全,2 违规,3 未检测。 + public int SafeStatus { get; set; } /// /// 页码(默认1) @@ -629,6 +637,7 @@ namespace Work.Services } public class CloudGoods : BindableBase { + public bool IsSelected { get => GetValue(); @@ -719,5 +728,18 @@ namespace Work.Services /// 索引同步状态:0未同步 1已同步 /// public byte IndexStatus { get; set; } + + /// + /// 安全检测 + /// + public bool? IsSafe { get; set; } + + + [JsonIgnore] + public string? DetectionResult + { + get => GetValue(); + set => SetValue(value); + } } } diff --git a/Work/Services/ComplianceProductReader.cs b/Work/Services/ComplianceProductReader.cs new file mode 100644 index 0000000..74b7838 --- /dev/null +++ b/Work/Services/ComplianceProductReader.cs @@ -0,0 +1,143 @@ +using AllBase.Model; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using Newtonsoft.Json.Linq; +using System; +using System.IO; +using System.Linq; +using TemuKJD.BLL.ImportConverts; +using AllBase; +using System.Collections.Generic; +using TemuKJD.BLL; + +namespace Work.Services; + +public static class ComplianceProductReader +{ + public static Product Read(ComplianceGoods item) + { + KJDDBProduct db; + if (item.type == ComplianceGoods.Cloud) + db = new KJDDBProduct { product_json = CloudGoodsService.GetGoodsDetail(long.Parse(item.id)) }; + else if (item.type == ComplianceGoods.Library) + { + db = GetProductDetail(item.id, out var message); + if (!string.IsNullOrEmpty(message)) throw new InvalidDataException(message); + } + else throw new InvalidDataException("未知商品来源:" + item.type); + + if (string.IsNullOrWhiteSpace(db?.product_json)) throw new InvalidDataException("未取得商品详情。"); + var raw = db.product_json; + var root = JObject.Parse(raw); + PDDProductInfo info; + string error = null; + var converter = new PDDProdFormConverter(); + if (root.SelectToken("detail.galleries") is JArray) + info = root.ToObject(); + else if (db.product_get_type_text == "厂商直采") info = converter.JstPddProdToPddConvert(out error, db); + else if (db.product_get_type_text == "微信店采") info = converter.WXProdToPddConvert(out error, db); + else if (db.product_get_type_text == "微信小店") info = converter.WXClientToPddConvert(out error, db); + else if (db.product_get_type_text == "1688厂家") info = converter._1688ClientToPddConvert(out error, db); + else if (db.product_get_type_text == "1688外部数据") info = converter._1688OutToPddConvert(out error, db); + else + { + var front = ConverterToPddFactory.SetContentConvert(raw); + if (front != null) + { + var originalId = front.GoodsId > 0 ? front.GoodsId.ToString() : db.spu_id; + return new Product + { + ProductId = item.type == ComplianceGoods.Cloud ? item.id : originalId, + Platform = item.platform.HasValue ? (PlatformType)item.platform.Value : front.Platform, + Title = front.Title ?? item.name, Description = front.Description, + MainImages = front.MainImages, DetailImages = front.DetailImages, + Skus = front.Skus?.Select(x => new ProductSku { SkuId = x.SkuId, ImageUrl = x.SpecImage }).ToList() + }; + } + string converted; + if (raw.Contains("\"spec_keys\"") && raw.Contains("\"spec_values\"")) + converted = converter.PddFrontToPddBackConvert(raw, isSaveDb: false); + else if (raw.Contains("\"goods_id\"") && raw.Contains("\"gallery\"")) + converted = converter.PddFrontToPddBackConvert2(raw, isSaveDb: false); + else if (raw.Contains("\"topGallery\"") && raw.Contains("\"detailGallery\"")) + converted = converter.PddFrontToPddBackConvert3(raw, isSaveDb: false); + else if (raw.Contains("\"商品编号\"") && raw.Contains("\"商品链接\"")) + converted = converter.PddFrontToPddBackConvertV4(raw, isSaveDb: false); + else throw new InvalidDataException("不支持的商品详情格式。"); + if (converted == null || !converted.StartsWith("success", StringComparison.Ordinal)) + throw new InvalidDataException(converted ?? "商品详情转换失败。"); + info = JObject.Parse(converted.Substring(7)).ToObject(); + } + if (!string.IsNullOrEmpty(error) || info?.detail == null) + throw new InvalidDataException(error ?? "商品详情转换失败。"); + var detail = info.detail; + var platform = db.product_get_type_text switch + { + "微信店采" or "微信小店" or "微信外部数据" or "微信" => PlatformType.WX, + "厂商直采" or "1688厂家" or "1688外部数据" => PlatformType.None, + _ => PlatformType.PDD + }; + var productId = db.product_get_type_text == "厂商直采" + ? (string)root.SelectToken("pddData.itemSpuId") ?? db.spu_id + : detail.goods_id > 0 ? detail.goods_id.ToString() : db.spu_id; + return new Product + { + Platform = item.platform.HasValue ? (PlatformType)item.platform.Value : platform, + ProductId = item.type == ComplianceGoods.Cloud ? item.id : productId, + Title = detail.goods_name ?? item.name, Description = detail.goods_desc, + MainImages = detail.galleries?.Where(x => x.type == 1).Select(x => x.url).ToList(), + DetailImages = detail.galleries?.Where(x => x.type == 2).Select(x => x.url).ToList(), + Skus = detail.sku?.Select(x => new ProductSku { SkuId = x.sku_id.ToString(), ImageUrl = x.thumb_url }).ToList() + }; + } + + public static KJDDBProduct GetProductDetail(string prodDbId, out string errMsg, bool noViolationProduct = false, bool noBanWx = false) + { + errMsg = ""; + + if (string.IsNullOrEmpty(prodDbId)) + { + errMsg = "产品编号为空"; + return new KJDDBProduct(); + } + + List list = new List(); + + var url = BaseClass.BuildObjString("product/get_pdd_product_detail_list"); + var data = UserLogin.GetSignaturex() + "&search_db_ids=" + prodDbId; + if (noViolationProduct) + data += "&wx_safe_status=安全商品,未校验"; + if (noBanWx) + data += "&ban_wx=0"; + if (BaseClass.SaLevel > 0 || BaseClass.UserAuthority.Contains("爆款数据") || BaseClass.UserAuthority.Contains("学员数据") || BaseClass.ParentAuthority.Contains("学员数据")) + data += "&special_prod_type=普通商品,爆款数据"; + + var html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + { + return null; + } + + var jArr = JObject.Parse(html)["data"]; + if (jArr.Count() == 0) + return null; + var sProd = JsonHelper.SerializeObject(jArr); + + try + { + list = JsonHelper.DeserializeJsonToList(sProd); + var item = list.FirstOrDefault(); + if (item == null) + item = new KJDDBProduct(); + return item; + } + catch (Exception ex) + { + return null; + } + } +} diff --git a/Work/Services/GoodsComplianceService.cs b/Work/Services/GoodsComplianceService.cs new file mode 100644 index 0000000..5a675cf --- /dev/null +++ b/Work/Services/GoodsComplianceService.cs @@ -0,0 +1,265 @@ +using AllBase.Helper; +using AllBase; +using AllBase.Model; +using Work.Store.Api; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Net.Http; +using System.Linq; +using System.Text; + +namespace Work.Services; + +public sealed class ComplianceGoods +{ + public const string Cloud = "云端收藏"; + public const string Library = "我的商品库"; + + public string id { get; set; } + public string name { get; set; } + public string image { get; set; } + public string type { get; set; } + public byte? platform { get; set; } +} + +public sealed class ComplianceResult +{ + public string Text { get; internal set; } + public string[] Hits { get; internal set; } = Array.Empty(); + public int HitImageNumber { get; internal set; } + public string Error { get; internal set; } + public string Status => Error != null ? "失败,可重试" : Hits.Length == 0 ? "安全" : "有风险"; + public string Summary => Error != null ? "失败:" + Error : + Hits.Length == 0 ? Status : $"有风险:第 {HitImageNumber} 张图片命中关键词:{string.Join(",", Hits)}"; +} + +public sealed class ComplianceBatchResult +{ + public int Safe { get; internal set; } + public int Risk { get; internal set; } + public int Failed { get; internal set; } + public int Unfinished { get; internal set; } +} + +public static class GoodsComplianceService +{ + private static readonly HttpClient ImageClient = new HttpClient(new HttpClientHandler { UseCookies = false }) + { + Timeout = TimeSpan.FromSeconds(30), + MaxResponseContentBufferSize = 32 * 1024 * 1024 + }; + private static readonly SemaphoreSlim BatchGate = new SemaphoreSlim(1, 1); + + public static async Task DetectSelectedAsync(IReadOnlyList items, + Func readProduct, Action update, CancellationToken cancellationToken = default) + { + if (items == null || items.Count == 0) throw new ArgumentException("请选择商品。", nameof(items)); + if (readProduct == null) throw new ArgumentNullException(nameof(readProduct)); + if (update == null) throw new ArgumentNullException(nameof(update)); + if (!BatchGate.Wait(0)) throw new InvalidOperationException("已有商品正在检测。"); + try + { + var options = GlobalConfig.GlobalSettings.GoodsCompliance; + var words = ValidateOptions(options); + var path = QueuePath(); + var pending = ReadPending(path); + if (items.Any(InvalidItem)) throw new ArgumentException("缺少商品 ID 或来源。", nameof(items)); + var selected = items.GroupBy(x => Tuple.Create(x.type, x.id)).Select(g => g.First()).ToList(); + var existing = new HashSet>(pending.Select(x => Tuple.Create(x.type, x.id))); + pending.AddRange(selected.Where(x => existing.Add(Tuple.Create(x.type, x.id)))); + SavePending(path, pending); + + var batch = new ComplianceBatchResult { Unfinished = selected.Count }; + foreach (var item in selected) + { + if (cancellationToken.IsCancellationRequested) break; + update(item.id, "读取详情中"); + ComplianceResult result; + try + { + var product = await Task.Run(() => readProduct(item), cancellationToken); + result = await DetectProductAsync(product, options, words, + number => update(item.id, $"正在检测第 {number} 张图片"), cancellationToken); + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + update(item.id, "已停止"); + break; + } + catch (Exception ex) { result = new ComplianceResult { Error = ex.Message }; } + if (result.Error == null) + { + var remaining = pending.Where(x => x.type != item.type || x.id != item.id).ToList(); + try { SavePending(path, remaining); pending = remaining; } + catch (Exception ex) { result.Error = ex.Message; } + } + if (result.Error != null) batch.Failed++; + else if (result.Hits.Length > 0) batch.Risk++; + else batch.Safe++; + batch.Unfinished--; + update(item.id, result.Summary); + } + return batch; + } + finally { BatchGate.Release(); } + } + + private static string QueuePath() + { + var username = BaseClass.UserName; + if (string.IsNullOrWhiteSpace(username)) throw new InvalidOperationException("请先登录。"); + var user = Convert.ToBase64String(Encoding.UTF8.GetBytes(username)).Replace('/', '_').Replace('+', '-'); + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data", "config", "goods_compliance_" + user + ".json"); + } + + private static bool InvalidItem(ComplianceGoods item) => + item == null || string.IsNullOrWhiteSpace(item.id) || string.IsNullOrWhiteSpace(item.type); + + private static List ReadPending(string path) + { + if (!File.Exists(path)) return new List(); + var items = JsonConvert.DeserializeObject>(File.ReadAllText(path)); + if (items == null || items.Any(InvalidItem)) + throw new InvalidDataException("待检测配置无效,请检查 " + path); + return items; + } + + private static void SavePending(string path, List items) + { + Directory.CreateDirectory(Path.GetDirectoryName(path)); + var temp = path + ".tmp"; + File.WriteAllText(temp, JsonConvert.SerializeObject(items)); + if (File.Exists(path)) File.Replace(temp, path, null); + else File.Move(temp, path); + } + + public static async Task DetectAsync(IReadOnlyList products, ComplianceOptions options, + Action completed, Action started = null) + { + if (products == null) throw new ArgumentNullException(nameof(products)); + if (completed == null) throw new ArgumentNullException(nameof(completed)); + var words = ValidateOptions(options); + foreach (var product in products) + { + started?.Invoke(product); + completed(product, await DetectProductAsync(product, options, words)); + } + } + + private static string[] ValidateOptions(ComplianceOptions options) + { + var words = Keywords(options?.Keywords); + if (words.Length == 0) throw new ArgumentException("请先在系统设置的「商品 AI 检测」中保存 AI 检测关键词。"); + if (options.MainCount <= 0 || options.DetailCount < 0) + throw new ArgumentException("请检查检测关键词和图片数量。"); + return words; + } + + private static async Task DetectProductAsync(Product product, ComplianceOptions options, + string[] words, Action imageStarted = null, CancellationToken cancellationToken = default) + { + var result = new ComplianceResult(); + try + { + cancellationToken.ThrowIfCancellationRequested(); + if (string.IsNullOrWhiteSpace(product?.ProductId)) throw new InvalidDataException("缺少商品 ID。"); + var urls = SelectImages(product.MainImages, options.MainCount) + .Concat(SelectImages(product.DetailImages, options.DetailCount)); + if (options.IncludeSku) urls = urls.Concat(SelectImages(product.Skus?.Select(s => s?.ImageUrl), null)); + var texts = new List(); + int imageNumber = 0; + foreach (var url in urls.Distinct(StringComparer.Ordinal)) + { + cancellationToken.ThrowIfCancellationRequested(); + imageNumber++; + imageStarted?.Invoke(imageNumber); + try + { + var jpeg = await DownloadInspectionImageAsync(url); + cancellationToken.ThrowIfCancellationRequested(); + var lines = await StoreApiClient.Instance.CloudGoods.RecognizeAsync(BaseClass.GetMd5(url), jpeg); + var imageText = string.Join(Environment.NewLine, lines); + texts.AddRange(lines); + result.Hits = Matches(imageText, words); + if (result.Hits.Length > 0) + { + result.HitImageNumber = imageNumber; + break; + } + } + catch (Exception ex) when (!cancellationToken.IsCancellationRequested) + { + throw new InvalidDataException($"第 {imageNumber} 张图片检测失败:{ex.Message}", ex); + } + } + cancellationToken.ThrowIfCancellationRequested(); + if (imageNumber == 0) throw new InvalidDataException("所选检测范围内没有图片。"); + result.Text = string.Join(Environment.NewLine, texts); + try + { + await StoreApiClient.Instance.CloudGoods.SaveProductSafety(new SaveProductSafetyRequest + { + UserName = GetUserName(), + ProductId = product.ProductId, + IsSafe = result.Hits.Length == 0 + }); + } + catch (Exception ex) + { + result.Error = $"检测结果为{result.Status},上报失败:{ex.Message}"; + } + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; } + catch (Exception ex) { result.Error = ex.Message; } + return result; + } + + private static IEnumerable SelectImages(IEnumerable urls, int? count) => + (urls ?? Enumerable.Empty()).Where(url => !string.IsNullOrWhiteSpace(url)).Select(url => url.Trim()) + .Distinct(StringComparer.Ordinal).Take(count ?? int.MaxValue); + + private static async Task DownloadInspectionImageAsync(string url) + { + if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || + (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)) + throw new InvalidDataException("图片链接必须为 HTTP 或 HTTPS 地址。"); + var bytes = await ImageClient.GetByteArrayAsync(uri).ConfigureAwait(false); + const int maxSide = 600; + using (var input = new MemoryStream(bytes)) + using (var source = Image.FromStream(input)) + { + var scale = Math.Min(1d, (double)maxSide / Math.Max(source.Width, source.Height)); + int width = Math.Max(1, (int)Math.Round(source.Width * scale)); + int height = Math.Max(1, (int)Math.Round(source.Height * scale)); + using var canvas = new Bitmap(width, height, PixelFormat.Format24bppRgb); + using var output = new MemoryStream(); + using (var g = Graphics.FromImage(canvas)) + { + g.Clear(Color.White); + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.DrawImage(source, new Rectangle(0, 0, width, height), + 0, 0, source.Width, source.Height, GraphicsUnit.Pixel); + } + canvas.Save(output, ImageFormat.Jpeg); + return output.ToArray(); + } + } + + public static string[] Keywords(string value) => + System.Text.RegularExpressions.Regex.Split(value ?? "", @"[,,;;|\s]+") + .Where(x => x.Length > 0).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); + + public static string[] Matches(string recognized, string[] keywords) => + keywords.Where(x => recognized.IndexOf(x, StringComparison.OrdinalIgnoreCase) >= 0).ToArray(); + public static string GetUserName() + { + return string.IsNullOrEmpty(BaseClass.ParentUserName) ? BaseClass.UserName : BaseClass.ParentUserName; + } +} diff --git a/Work/Store.Api/ApiException.cs b/Work/Store.Api/ApiException.cs index 1243c8d..16532a5 100644 --- a/Work/Store.Api/ApiException.cs +++ b/Work/Store.Api/ApiException.cs @@ -8,10 +8,12 @@ public sealed class ApiException : HttpException { public int? Code { get; } public JToken ResponseData { get; } + public string TraceId { get; } - public ApiException(string message, int? code, JToken responseData) : base(message) + public ApiException(string message, int? code, JToken responseData, string traceId = null) : base(message) { Code = code; ResponseData = responseData; + TraceId = traceId; } } diff --git a/Work/Store.Api/ApiTransport.cs b/Work/Store.Api/ApiTransport.cs index 6b7220e..93c3c38 100644 --- a/Work/Store.Api/ApiTransport.cs +++ b/Work/Store.Api/ApiTransport.cs @@ -1,4 +1,5 @@ using CsharpHttpHelper; +using AllBase.Model.Result; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -6,6 +7,8 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Text; using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; namespace Work.Store.Api; @@ -14,16 +17,14 @@ public sealed class ApiTransport { private readonly string _baseUrl; private string _token; - private Func _login; - internal object SessionGate { get; } = new object(); + private LoginRequest _credentials; + internal SemaphoreSlim SessionGate { get; } = new SemaphoreSlim(1, 1); - internal void SetSession(string token, Func login) + // 调用方持有 SessionGate。 + internal void SetSession(string token, LoginRequest credentials) { - lock (SessionGate) - { - _token = token; - _login = login; - } + _token = token; + _credentials = credentials; } public ApiTransport(string baseUrl) @@ -39,29 +40,102 @@ public sealed class ApiTransport /// 仅存于内存;手动赋值会清除自动登录凭据,设为 null 退出本地会话。 public string Token { - get { lock (SessionGate) return _token; } + get + { + SessionGate.Wait(); + try { return _token; } + finally { SessionGate.Release(); } + } set { if (value != null && (string.IsNullOrWhiteSpace(value) || value.IndexOfAny(new[] { '\r', '\n' }) >= 0)) throw new ArgumentException("Token 不能为空白或包含换行。", nameof(value)); - SetSession(value, null); + SessionGate.Wait(); + try { SetSession(value, null); } + finally { SessionGate.Release(); } } } - /// 同步请求;无 data 时返回 default(T),界面调用需放在后台线程。 public T Send(string route, MethodType method, object body = null, IDictionary query = null, bool authenticated = true, int timeoutMilliseconds = 30000) + { + var item = CreateRequest(route, method, body, query, timeoutMilliseconds); + string token = null; + if (authenticated) + { + //SessionGate.Wait(); + //try { token = _token ?? throw new HttpException("请先登录。") { IsOnline = false }; } + //finally { SessionGate.Release(); } + } + for (int attempt = 0; ; attempt++) + { + if (token != null) item.Header["Authorization"] = "Bearer " + token; + var response = HttpHelper.Request(item); + try { return ReadResult(response); } + catch (HttpException ex) when (authenticated && !ex.IsOnline) + { + SessionGate.Wait(); + try + { + if (_token == token) + { + _token = null; + if (attempt == 0 && _credentials != null) + _token = AuthApi.LoginCore(this, _credentials).Token; + } + if (attempt != 0 || _token == null) throw; + token = _token; + } + finally { SessionGate.Release(); } + } + } + } + + public async Task SendAsync(string route, MethodType method, object body = null, + IDictionary query = null, bool authenticated = true, + int timeoutMilliseconds = 30000) + { + var item = CreateRequest(route, method, body, query, timeoutMilliseconds); + string token = null; + if (authenticated) + { + //await SessionGate.WaitAsync().ConfigureAwait(false); + //try { token = _token ?? throw new HttpException("请先登录。") { IsOnline = false }; } + //finally { SessionGate.Release(); } + } + for (int attempt = 0; ; attempt++) + { + if (token != null) item.Header["Authorization"] = "Bearer " + token; + var response = await HttpHelper.RequestAsync(item).ConfigureAwait(false); + try { return ReadResult(response); } + catch (HttpException ex) when (authenticated && !ex.IsOnline) + { + await SessionGate.WaitAsync().ConfigureAwait(false); + try + { + if (_token == token) + { + _token = null; + if (attempt == 0 && _credentials != null) + _token = (await AuthApi.LoginCoreAsync(this, _credentials).ConfigureAwait(false)).Token; + } + if (attempt != 0 || _token == null) throw; + token = _token; + } + finally { SessionGate.Release(); } + } + } + } + + private HttpItem CreateRequest(string route, MethodType method, object body, + IDictionary query, int timeoutMilliseconds) { if (string.IsNullOrWhiteSpace(route) || !Regex.IsMatch(route, @"\A[A-Za-z0-9_-]+(/[A-Za-z0-9_-]+)*\z")) throw new ArgumentException("接口必须使用相对路由,例如 Auth/Login。", nameof(route)); if (body != null) Validator.ValidateObject(body, new ValidationContext(body), true); if (timeoutMilliseconds <= 0) throw new ArgumentOutOfRangeException(nameof(timeoutMilliseconds)); - string token = authenticated ? Token : null; - if (authenticated && token == null) - throw new HttpException("请先登录。") { IsOnline = false }; - var url = new StringBuilder(_baseUrl).Append(route); if (query != null) { @@ -74,7 +148,7 @@ public sealed class ApiTransport first = false; } } - var item = new HttpItem + return new HttpItem { URL = url.ToString(), Method = method, @@ -87,54 +161,31 @@ public sealed class ApiTransport ReadWriteTimeout = timeoutMilliseconds, Allowautoredirect = false }; - for (int attempt = 0; ; attempt++) - { - if (token != null) item.Header["Authorization"] = "Bearer " + token; - var response = HttpHelper.Request(item); - int status = (int)response.StatusCode; - if (status == 0) throw new HttpException("无法连接服务器或请求超时。"); + } - JObject result = null; - JsonException parseError = null; - try { result = JObject.Parse(response.Html ?? ""); } - catch (JsonException ex) { parseError = ex; } + private static T ReadResult(HttpResult response) + { + int status = (int)response.StatusCode; + if (status == 0) throw new HttpException("无法连接服务器或请求超时。"); - var code = result?["code"]; - if (status == 401 || (code?.Type == JTokenType.Integer && code.ToString() == "401")) - { - if (authenticated) - { - lock (SessionGate) - { - // 并发旧请求复用已更新的 Token;同一请求最多重新发送一次。 - if (_token == token) - { - _token = null; - if (attempt == 0 && _login != null) - _token = _login(); - } - if (attempt == 0 && _token != null) - { - token = _token; - continue; - } - } - } - throw new HttpException(result?["msg"]?.ToString() ?? "登录已失效,请重新登录。") { IsOnline = false }; - } - if (status < 200 || status >= 300) - throw new HttpException($"请求失败(HTTP {status}):{result?["msg"]}"); - if (result == null) - throw new HttpException("服务器响应不是有效的 JSON 对象。", parseError); - // 先检查业务码,避免错误响应的 data 结构掩盖服务端错误。 - if (code?.Type != JTokenType.Integer || code.ToString() != "200") - throw new ApiException("请求失败:" + (result["msg"]?.ToString() ?? "响应缺少成功状态码。"), - code?.Type == JTokenType.Integer && int.TryParse(code.ToString(), out var businessCode) ? businessCode : (int?)null, - result["data"]); - if (result["data"] == null || result["data"].Type == JTokenType.Null) return default; - try { return result["data"].ToObject(); } - catch (JsonException ex) { throw new HttpException("服务器响应 data 格式无效。", ex); } - } + Result result = null; + JsonException parseError = null; + try { result = JsonConvert.DeserializeObject>(response.Html ?? ""); } + catch (JsonException ex) { parseError = ex; } + + if (status == 401 || (result?.Success == false && result.Code == 401)) + throw new HttpException(result?.Msg ?? "登录已失效,请重新登录。") { IsOnline = false }; + // 统一错误响应即使使用 HTTP 4xx/5xx,也保留业务码、数据和追踪 ID。 + if (result?.Success == false) + throw new ApiException("请求失败:" + (result.Msg ?? $"业务码 {result.Code}。"), + result.Code, result.Data, result.TraceId); + if (status < 200 || status >= 300) + throw new HttpException($"请求失败(HTTP {status}):{result?.Msg}"); + if (result?.Success == null) + throw new HttpException("服务器响应不是有效的 Result 格式。", parseError); + if (result.Data == null || result.Data.Type == JTokenType.Null) return default; + try { return result.Data.ToObject(); } + catch (JsonException ex) { throw new HttpException("服务器响应 data 格式无效。", ex); } } public T GetRequired(string route, MethodType method, object body = null, @@ -142,5 +193,11 @@ public sealed class ApiTransport int timeoutMilliseconds = 30000) where T : class => Send(route, method, body, query, authenticated, timeoutMilliseconds) ?? throw new HttpException("服务器响应缺少 data。"); + + public async Task GetRequiredAsync(string route, MethodType method, object body = null, + IDictionary query = null, bool authenticated = true, + int timeoutMilliseconds = 30000) where T : class + => await SendAsync(route, method, body, query, authenticated, timeoutMilliseconds).ConfigureAwait(false) + ?? throw new HttpException("服务器响应缺少 data。"); } diff --git a/Work/Store.Api/AuthApi.cs b/Work/Store.Api/AuthApi.cs index dc890d6..e1299e6 100644 --- a/Work/Store.Api/AuthApi.cs +++ b/Work/Store.Api/AuthApi.cs @@ -1,5 +1,6 @@ using CsharpHttpHelper; using System; +using System.Threading.Tasks; using System.ComponentModel.DataAnnotations; @@ -52,17 +53,30 @@ public sealed class AuthApi DeviceId = _deviceId ?? request.DeviceId, DeviceName = request.DeviceName }; - lock (_transport.SessionGate) + _transport.SessionGate.Wait(); + try { - var result = LoginCore(credentials); - _transport.SetSession(result.Token, () => LoginCore(credentials).Token); + var result = LoginCore(_transport, credentials); + _transport.SetSession(result.Token, credentials); return result; } + finally { _transport.SessionGate.Release(); } } - private LoginResult LoginCore(LoginRequest request) + internal static LoginResult LoginCore(ApiTransport transport, LoginRequest request) + { + var result = transport.GetRequired("Auth/Login", MethodType.Post, request, authenticated: false); + return ValidateToken(result); + } + + internal static async Task LoginCoreAsync(ApiTransport transport, LoginRequest request) + { + var result = await transport.GetRequiredAsync("Auth/Login", MethodType.Post, request, authenticated: false).ConfigureAwait(false); + return ValidateToken(result); + } + + private static LoginResult ValidateToken(LoginResult result) { - var result = _transport.GetRequired("Auth/Login", MethodType.Post, request, authenticated: false); if (result == null || string.IsNullOrWhiteSpace(result.Token) || result.Token.IndexOfAny(new[] { '\r', '\n' }) >= 0 || !string.Equals(result.TokenType, "Bearer", StringComparison.OrdinalIgnoreCase)) diff --git a/Work/Store.Api/CloudGoodsApi.cs b/Work/Store.Api/CloudGoodsApi.cs index 76c9baf..d36c4ca 100644 --- a/Work/Store.Api/CloudGoodsApi.cs +++ b/Work/Store.Api/CloudGoodsApi.cs @@ -1,55 +1,86 @@ -using AllBase.Model.Result; +using AllBase; +using AllBase.Helper; +using AllBase.Model.Result; using CsharpHttpHelper; +using NaiBa.Util.Helper; using System; using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; using Work.Services; +using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel; namespace Work.Store.Api; /// 云商品接口,沿用服务端不带版本号的路由。 public sealed class CloudGoodsApi { - private readonly ApiTransport _transport; + private readonly ApiTransport _transport; - public CloudGoodsApi(ApiTransport transport) - => _transport = transport ?? throw new ArgumentNullException(nameof(transport)); + public CloudGoodsApi(ApiTransport transport) + => _transport = transport ?? throw new ArgumentNullException(nameof(transport)); - public Task> GetGoods(GoodsSearchRequest request) - => Task.Run(() => _transport.GetRequired>("CloudGoods/SearchGoods", MethodType.Post, request)); + public Task> GetGoods(GoodsSearchRequest request) + => _transport.GetRequiredAsync>("CloudGoods/SearchGoods", MethodType.Post, request); - public string GetGoodsDetail(long goodsId) - => _transport.GetRequired("CloudGoods/GetGoodsRawData", MethodType.Post, - query: new Dictionary { ["goodsId"] = goodsId.ToString(CultureInfo.InvariantCulture) }); + public string GetGoodsDetail(long goodsId) + => _transport.GetRequired("CloudGoods/GetGoodsRawData", MethodType.Post, + query: new Dictionary { ["goodsId"] = goodsId.ToString(CultureInfo.InvariantCulture) }); - public Task> ReportViolations(List items) - => Task.Run(() => _transport.GetRequired>("CloudGoods/ReportViolations", MethodType.Post, items, - timeoutMilliseconds: 5000)); + public Task> ReportViolations(List items) + => _transport.GetRequiredAsync>("CloudGoods/ReportViolations", MethodType.Post, items, + timeoutMilliseconds: 5000); - public List GetViolationStatus(List goodsIds) - => _transport.GetRequired>("CloudGoods/GetViolationStatus", MethodType.Post, goodsIds, - timeoutMilliseconds: 5000); + public List GetViolationStatus(List goodsIds) + => _transport.GetRequired>("CloudGoods/GetViolationStatus", MethodType.Post, goodsIds, + timeoutMilliseconds: 5000); - public Task RecordViolationLogs(IReadOnlyList requests) - => Task.Run(() => _transport.Send("CloudGoods/RecordViolationLog", MethodType.Post, requests, - timeoutMilliseconds: 5000)); + public Task RecordViolationLogs(IReadOnlyList requests) + => _transport.SendAsync("CloudGoods/RecordViolationLog", MethodType.Post, requests, + timeoutMilliseconds: 5000); - public Task> GetViolationReasons() - => Task.Run(() => _transport.GetRequired>("CloudGoods/GetViolationReasons", MethodType.Get)); + public Task> GetViolationReasons() + => _transport.GetRequiredAsync>("CloudGoods/GetViolationReasons", MethodType.Get); - public Task> GetViolationLogs(GetViolationLogsRequest request) - => Task.Run(() => _transport.GetRequired>("CloudGoods/GetViolationLogs", MethodType.Post, request, - timeoutMilliseconds: 5000)); + public Task> GetViolationLogs(GetViolationLogsRequest request) + => _transport.GetRequiredAsync>("CloudGoods/GetViolationLogs", MethodType.Post, request, + timeoutMilliseconds: 5000); - public Task> SearchFavorites(UserFavoritesGoodsSearchRequest request) - => Task.Run(() => _transport.GetRequired>("CloudGoods/SearchFavorites", MethodType.Post, request)); + public Task> SearchFavorites(UserFavoritesGoodsSearchRequest request) + => _transport.GetRequiredAsync>("CloudGoods/SearchFavorites", MethodType.Post, request); - public Task Favorite(string userName, List goodsIds) - => Task.Run(() => _transport.Send("CloudGoods/AddFavorite", MethodType.Post, - new { UserName = userName, GoodsIds = goodsIds })); + public Task Favorite(string userName, List goodsIds) + => _transport.SendAsync("CloudGoods/AddFavorite", MethodType.Post, + new { UserName = userName, GoodsIds = goodsIds }); - public Task RemoveFavorites(string userName, List goodsIds) - => Task.Run(() => _transport.Send("CloudGoods/RemoveFavorites", MethodType.Post, - new { UserName = userName, GoodsIds = goodsIds })); + public Task RemoveFavorites(string userName, List goodsIds) + => _transport.SendAsync("CloudGoods/RemoveFavorites", MethodType.Post, + new { UserName = userName, GoodsIds = goodsIds }); + + + /// 识别上传图片中的文字和品牌 Logo。 + public async Task> RecognizeAsync(string urlMd5, byte[] jpeg) + { + int sleep = 0; + if (EnvironmentHelper.IsProduction) + { + sleep = RandomHelper.GetRandom(600, 1200); + } + var minimumDuration = Task.Delay(TimeSpan.FromMilliseconds(sleep)); + var request = new { UserName = BaseClass.UserName, Md5 = urlMd5, Image = Convert.ToBase64String(jpeg) }; + var recognitionTask = _transport.GetRequiredAsync>("CloudGoods/InspectImage", MethodType.Post, request); + await Task.WhenAll(minimumDuration, recognitionTask).ConfigureAwait(false); + return await recognitionTask.ConfigureAwait(false); + } + + public async Task SaveProductSafety(SaveProductSafetyRequest request) + { + var ret = await _transport.SendAsync("CloudGoods/SaveProductSafety", MethodType.Post, new + { + UserName = request.UserName, + ProductId = request.ProductId, + IsSafe = request.IsSafe + }); + + } } diff --git a/Work/Store.Api/GoodsComplianceApi.cs b/Work/Store.Api/GoodsComplianceApi.cs new file mode 100644 index 0000000..dd75edc --- /dev/null +++ b/Work/Store.Api/GoodsComplianceApi.cs @@ -0,0 +1,70 @@ +using AllBase; +using AllBase.Helper; +using CsharpHttpHelper; +using NaiBa.Util.Helper; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Threading.Tasks; + +namespace Work.Store.Api; + +public sealed class GoodsComplianceApi +{ + private readonly ApiTransport transport; + internal GoodsComplianceApi(ApiTransport transport) => this.transport = transport; + + ///// 上报平台商品的安全检测记录。 + //public Task SaveProductSafety(SaveProductSafetyRequest request) + //{ + // request.UserName = BaseClass.UserName; + // return transport.SendAsync("ProductInspection/SaveProductSafety", MethodType.Post, + // request ?? throw new ArgumentNullException(nameof(request))); + //} + + /// 按平台和商品 ID 批量查询安全记录。 + public Task> QueryProductSafety(QueryProductSafetyRequest request) + { + request.UserName = BaseClass.UserName; + return transport.GetRequiredAsync>("ProductInspection/QueryProductSafety", MethodType.Post, + request ?? throw new ArgumentNullException(nameof(request))); + } + + /// 识别上传图片中的文字和品牌 Logo。 + public async Task> RecognizeAsync(string urlMd5, byte[] jpeg) + { + int sleep = 0; + if (EnvironmentHelper.IsProduction) + { + sleep = RandomHelper.GetRandom(600, 1200); + } + var minimumDuration = Task.Delay(TimeSpan.FromMilliseconds(sleep)); + var request = new { UserName = BaseClass.UserName, Md5 = urlMd5, Image = Convert.ToBase64String(jpeg) }; + var recognitionTask = transport.GetRequiredAsync>("ProductInspection/InspectImage", MethodType.Post, request); + await Task.WhenAll(minimumDuration, recognitionTask).ConfigureAwait(false); + return await recognitionTask.ConfigureAwait(false); + } +} + +public sealed class SaveProductSafetyRequest +{ + [Required] public string UserName { get; set; } = ""; + [Required] public string ProductId { get; set; } = ""; + [Required] public bool? IsSafe { get; set; } +} + +public sealed class QueryProductSafetyRequest +{ + [Required] public string UserName { get; set; } = ""; + [Required] public string PlatformId { get; set; } = ""; + [Required] public string[] ProductIds { get; set; } = []; +} + +public sealed record ProductSafetyDto(string ProductId, bool IsSafe) +{ + [Required] public string UserName { get; set; } = ""; + public string ProductId { get; } = ProductId; + public bool IsSafe { get; } = IsSafe; +} diff --git a/Work/Store.Api/StoreApiClient.cs b/Work/Store.Api/StoreApiClient.cs index 82c2d99..68ba88d 100644 --- a/Work/Store.Api/StoreApiClient.cs +++ b/Work/Store.Api/StoreApiClient.cs @@ -9,15 +9,16 @@ namespace Work.Store.Api; /// 全局共用一个用户会话与请求出口,窗体通过 Instance 访问。 public sealed class StoreApiClient { - private static readonly string BaseUrl = EnvironmentHelper.IsDevelopment - ? "http://localhost:14312/api/" - : "http://103.90.155.78:9001/api/"; + private static readonly string BaseUrl = EnvironmentHelper.IsProduction + ? "http://103.90.155.78:9001/api/" + : "http://localhost:14312/api/"; public static StoreApiClient Instance { get; } = new StoreApiClient(); public ApiTransport Transport { get; } public AuthApi Auth { get; } public CloudGoodsApi CloudGoods { get; } + //public GoodsComplianceApi GoodsCompliance { get; } public ImageCacheApi ImageCache { get; } /// 当前 Windows 安装的 SHA-256 设备指纹,登录时作为 DeviceId 发送。 public string DeviceId { get; } @@ -28,6 +29,7 @@ public sealed class StoreApiClient Transport = new ApiTransport(BaseUrl); Auth = new AuthApi(Transport, DeviceId); CloudGoods = new CloudGoodsApi(Transport); + //GoodsCompliance = new GoodsComplianceApi(Transport); ImageCache = new ImageCacheApi(Transport); } diff --git a/Work/Work.csproj b/Work/Work.csproj index 9fea9ce..d33699a 100644 --- a/Work/Work.csproj +++ b/Work/Work.csproj @@ -78,4 +78,8 @@ + + + + \ No newline at end of file diff --git a/test/ApiTransportSmoke.cs b/test/ApiTransportSmoke.cs new file mode 100644 index 0000000..35a10e6 --- /dev/null +++ b/test/ApiTransportSmoke.cs @@ -0,0 +1,112 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using CsharpHttpHelper; +using Work.Store.Api; + +// Compile against Work.dll, AllBase.dll and Newtonsoft.Json.dll; run beside these assemblies. +internal static class ApiTransportSmoke +{ + private static void Check(bool passed) + { + if (!passed) throw new Exception("ApiTransport assertion failed."); + } + + private static async Task Respond(int status, string json, Func> request) + { + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + var server = Task.Run(() => + { + using (var client = listener.AcceptTcpClient()) + using (var stream = client.GetStream()) + using (var reader = new StreamReader(stream, Encoding.ASCII, false, 1024, true)) + { + while (!string.IsNullOrEmpty(reader.ReadLine())) { } + var body = Encoding.UTF8.GetBytes(json); + var header = Encoding.ASCII.GetBytes("HTTP/1.1 " + status + " Response\r\nContent-Type: application/json\r\nContent-Length: " + body.Length + "\r\nConnection: close\r\n\r\n"); + stream.Write(header, 0, header.Length); + stream.Write(body, 0, body.Length); + } + }); + try + { + var port = ((IPEndPoint)listener.LocalEndpoint).Port; + return await request(new ApiTransport("http://127.0.0.1:" + port)); + } + finally + { + listener.Stop(); + await server; + } + } + + private static Task Send(ApiTransport transport) + { + return transport.SendAsync("Test", MethodType.Get, authenticated: false, timeoutMilliseconds: 5000); + } + + public static async Task Main() + { + Check(await Respond(200, "{\"code\":0,\"success\":true,\"data\":\"ok\"}", Send) == "ok"); + Check(await Respond(200, "{\"code\":0,\"success\":true,\"data\":null}", Send) == null); + Check(await Respond(200, "{\"success\":true,\"data\":null}", transport => + transport.SendAsync("Test", MethodType.Get, authenticated: false)) == 0); + Check((await Respond(200, "{\"success\":true,\"data\":[1,2]}", transport => + transport.SendAsync("Test", MethodType.Get, authenticated: false)))[1] == 2); + foreach (var status in new[] { 200, 500 }) + { + await Respond(status, "{\"data\":null,\"code\":5000,\"msg\":\"semaphore limit\",\"traceId\":\"trace-1\",\"success\":false}", async transport => + { + try { await Send(transport); throw new Exception("Expected ApiException."); } + catch (ApiException ex) + { + Check(ex.Code == 5000 && ex.TraceId == "trace-1" && ex.Message.Contains("semaphore limit")); + } + return true; + }); + } + await Respond(200, "{\"code\":200,\"success\":false,\"data\":{}}", async transport => + { + try { await Send(transport); throw new Exception("Expected ApiException."); } + catch (ApiException ex) { Check(ex.Code == 200 && ex.ResponseData is Newtonsoft.Json.Linq.JObject); } + return true; + }); + foreach (var json in new[] { "not json", "[]", "null", "{\"code\":\"invalid\",\"success\":true}", + "{\"code\":200,\"data\":\"legacy\"}", "{\"code\":200,\"success\":null}", "{}" }) + { + await Respond(200, json, async transport => + { + try { await Send(transport); throw new Exception("Expected response format error."); } + catch (HttpException ex) { Check(ex.Message.Contains("Result")); } + return true; + }); + } + await Respond(401, "{\"code\":401,\"success\":false,\"msg\":\"expired\"}", async transport => + { + try { await Send(transport); throw new Exception("Expected authentication error."); } + catch (HttpException ex) { Check(!ex.IsOnline && ex.Message == "expired"); } + return true; + }); + Check(await Respond(200, "{\"success\":true,\"data\":\"ok\"}", transport => + Task.Run(() => transport.GetRequired("Test", MethodType.Get, authenticated: false))) == "ok"); + + var stalled = new TcpListener(IPAddress.Loopback, 0); + stalled.Start(); + try + { + var transport = new ApiTransport("http://" + stalled.LocalEndpoint); + try + { + await transport.SendAsync("Test", MethodType.Get, authenticated: false, timeoutMilliseconds: 250); + throw new Exception("Expected timeout."); + } + catch (HttpException ex) { Check(ex.Message.Contains("超时")); } + } + finally { stalled.Stop(); } + Console.WriteLine("ApiTransport smoke checks passed."); + } +} diff --git a/test/GoodsComplianceSmoke.cs b/test/GoodsComplianceSmoke.cs new file mode 100644 index 0000000..f837b38 --- /dev/null +++ b/test/GoodsComplianceSmoke.cs @@ -0,0 +1,70 @@ +using AllBase.Helper; +using AllBase; +using AllBase.Model; +using System.Collections.Generic; +using System; +using System.Linq; +using Work.Services; + +// Compile against the built Work.dll, AllBase.dll and Newtonsoft.Json.dll and run in an isolated directory. +internal static class GoodsComplianceSmoke +{ + private static void Check(bool passed) + { + if (!passed) throw new Exception("商品合规自测失败"); + } + + public static void Main() + { + var user = Guid.NewGuid().ToString(); + BaseClass.UserName = user; + GlobalConfig.GlobalSettings.GoodsCompliance = new ComplianceOptions { Keywords = "测试" }; + var cloud = new ComplianceGoods { id = "123", type = ComplianceGoods.Cloud }; + var library = new ComplianceGoods { id = "123", type = ComplianceGoods.Library }; + var attempted = new List(); + var failures = new List(); + var batch = GoodsComplianceService.DetectSelectedAsync(new[] { cloud, cloud, library }, item => + { + attempted.Add(item); + throw new InvalidOperationException("读取失败"); + }, (id, status) => { if (status == "失败:读取失败") failures.Add(id); }).GetAwaiter().GetResult(); + Check(attempted.SequenceEqual(new[] { cloud, library }) && failures.Count == 2); + var file = System.IO.Directory.GetFiles(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, + "data", "config"), "goods_compliance_" + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(user)) + ".json").Single(); + Check(Newtonsoft.Json.JsonConvert.DeserializeObject>(System.IO.File.ReadAllText(file)) + .Select(x => x.type).SequenceEqual(new[] { cloud.type, library.type })); + Check(batch.Safe == 0 && batch.Risk == 0 && batch.Failed == 2 && batch.Unfinished == 0); + using (var stop = new System.Threading.CancellationTokenSource()) + { + attempted.Clear(); + var stopped = GoodsComplianceService.DetectSelectedAsync(new[] { cloud, library }, item => + { + attempted.Add(item); + stop.Cancel(); + return new Product { ProductId = item.id }; + }, (id, status) => { }, stop.Token).GetAwaiter().GetResult(); + Check(attempted.SequenceEqual(new[] { cloud }) && stopped.Unfinished == 2 && stopped.Failed == 0); + Check(Newtonsoft.Json.JsonConvert.DeserializeObject>(System.IO.File.ReadAllText(file)).Count == 2); + } + var words = GoodsComplianceService.Keywords("广告,测试,abc 促销;ABC|示例"); + Check(words.Length == 5); + Check(GoodsComplianceService.Matches("ABC广告", words).SequenceEqual(new[] { "广告", "abc" })); + Check(GoodsComplianceService.Matches("普通商品", words).Length == 0); + var invalidImage = new Product { ProductId = "bad-image", MainImages = new List { "file:///image.jpg" } }; + var statuses = new List(); + GoodsComplianceService.DetectSelectedAsync(new[] { cloud }, item => invalidImage, + (id, status) => statuses.Add(status)).GetAwaiter().GetResult(); + Check(statuses.SequenceEqual(new[] { "读取详情中", "正在检测第 1 张图片", "失败:第 1 张图片检测失败:图片链接必须为 HTTP 或 HTTPS 地址。" })); + var products = new[] { new Product(), new Product { ProductId = "empty" }, invalidImage }; + var callbacks = new List(); + GoodsComplianceService.DetectAsync(products, new ComplianceOptions { Keywords = "测试" }, (product, result) => + { + Check(result.Error != null); + if (product == invalidImage) Check(result.Error.Contains("第 1 张图片") && result.Error.Contains("HTTP")); + callbacks.Add(product); + }).GetAwaiter().GetResult(); + Check(callbacks.SequenceEqual(products)); + System.IO.File.Delete(file); + Console.WriteLine("商品合规队列与匹配自测通过"); + } +} diff --git a/test/HttpHelperAsyncSmoke.cs b/test/HttpHelperAsyncSmoke.cs new file mode 100644 index 0000000..17c297a --- /dev/null +++ b/test/HttpHelperAsyncSmoke.cs @@ -0,0 +1,68 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using CsharpHttpHelper; + +internal static class HttpHelperAsyncSmoke +{ + public static async Task Main() + { + await CheckRequests(false); + await CheckRequests(true); + } + + private static async Task CheckRequests(bool asynchronous) + { + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + try + { + string url = "http://" + listener.LocalEndpoint + "/"; + var pending = asynchronous ? HttpHelper.PostAsync(url, "hello") : Task.Run(() => HttpHelper.Post(url, "hello")); + using (var client = await listener.AcceptTcpClientAsync()) + using (var stream = client.GetStream()) + using (var reader = new StreamReader(stream, Encoding.ASCII, false, 1024, true)) + { + if (!reader.ReadLine().StartsWith("POST ", StringComparison.OrdinalIgnoreCase)) + throw new Exception("Expected POST"); + while (!string.IsNullOrEmpty(reader.ReadLine())) { } + var reply = Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n"); + stream.Write(reply, 0, reply.Length); + var body = new char[5]; + if (reader.ReadBlock(body, 0, body.Length) != 5 || new string(body) != "hello") + throw new Exception("POST body mismatch"); + + reply = Encoding.ASCII.GetBytes("HTTP/1.1 400 Bad Request\r\nContent-Length: 7\r\nConnection: close\r\n\r\ninvalid"); + stream.Write(reply, 0, reply.Length); + } + var result = await pending; + if (result.StatusCode != HttpStatusCode.BadRequest || result.Html != "invalid") + throw new Exception("Missing error response"); + + var get = asynchronous ? HttpHelper.GetAsync(url) : Task.Run(() => HttpHelper.Get(url)); + using (var client = await listener.AcceptTcpClientAsync()) + using (var stream = client.GetStream()) + using (var reader = new StreamReader(stream, Encoding.ASCII, false, 1024, true)) + { + if (!reader.ReadLine().StartsWith("GET ", StringComparison.OrdinalIgnoreCase)) + throw new Exception("Expected GET"); + while (!string.IsNullOrEmpty(reader.ReadLine())) { } + var reply = Encoding.UTF8.GetBytes("HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 5\r\nConnection: close\r\n\r\n\uFEFFok"); + stream.Write(reply, 0, reply.Length); + } + if ((await get).Html != "\uFEFFok") throw new Exception("Expected GET response to retain its BOM"); + + var item = new HttpItem { URL = url, Timeout = 250 }; + var stalled = asynchronous ? HttpHelper.RequestAsync(item) : Task.Run(() => HttpHelper.Request(item)); + if (await Task.WhenAny(stalled, Task.Delay(5000)) != stalled || string.IsNullOrEmpty((await stalled).Html)) + throw new Exception("Expected a timeout result"); + } + finally + { + listener.Stop(); + } + } +} diff --git a/test/NumericRangeSmoke.cs b/test/NumericRangeSmoke.cs new file mode 100644 index 0000000..2ba2e38 --- /dev/null +++ b/test/NumericRangeSmoke.cs @@ -0,0 +1,45 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using TemuKJD.UserComponent; + +internal static class NumericRangeSmoke +{ + [STAThread] + public static void Main() + { + using (var range = new UcNumericRange()) + { + int width = range.Width; + var layout = (TableLayoutPanel)range.Controls[0]; + var separator = (Label)layout.GetControlFromPosition(1, 0); + var margin = separator.Margin; + var cell = layout.GetControlFromPosition(0, 0); + var editor = (NumericUpDown)cell.Controls[0]; + var text = (TextBox)(editor.Controls[0] is TextBox ? editor.Controls[0] : editor.Controls[1]); + var buttons = editor.Controls[0] == text ? editor.Controls[1] : editor.Controls[0]; + range.SeparatorText = "~"; + range.Compact = true; + var rightCell = layout.GetControlFromPosition(2, 0); + using (var bitmap = new Bitmap(range.Width, range.Height)) + { + range.DrawToBitmap(bitmap, range.ClientRectangle); + int y = rightCell.Top + rightCell.Height / 2; + int border = bitmap.GetPixel(rightCell.Right - 1, y).ToArgb(); + if (border == editor.BackColor.ToArgb() || border != bitmap.GetPixel(rightCell.Left, y).ToArgb()) + throw new Exception("结束值输入框右边框未绘制"); + } + if (range.Width != 112 || buttons.Left < cell.Width - 2 || text.Right < cell.Width - 4) + throw new Exception("紧凑模式未充分利用输入区域"); + if (separator.Text != "~" || range.Width >= width || separator.Margin != Padding.Empty) + throw new Exception("紧凑布局或分隔文字错误"); + range.Compact = false; + if (buttons.Right > cell.Width || text.Right > buttons.Left) throw new Exception("微调按钮未恢复"); + if (range.Width != width || separator.Margin != margin) + throw new Exception("普通布局未恢复"); + range.Width = 190; + range.Compact = true; + if (range.Width != 190) throw new Exception("自定义宽度被覆盖"); + } + } +} From 013d343fe995adbcc936d325a5264be8e92efe22 Mon Sep 17 00:00:00 2001 From: chfei Date: Fri, 6 Nov 2026 19:23:00 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E6=88=91=E7=9A=84=E5=95=86=E5=93=81?= =?UTF-8?q?=E5=BA=93=E5=A2=9E=E5=8A=A0AI=E8=AF=86=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AllBase/BaseClass.cs | 12 +- TEMUKJD/KJD.csproj.user | 2 +- .../SystemGoodsComplianceForm.Designer.cs | 499 ++++++++++++------ .../SystemForms/SystemGoodsComplianceForm.cs | 151 ++++-- .../SystemGoodsComplianceForm.resx | 120 +++++ .../CloudGoodsFavoriteForm.Designer.cs | 80 +-- .../ProductForms/CloudGoodsFavoriteForm.cs | 2 + .../WXUserCollectAuthProdListForm.Designer.cs | 240 +++++---- .../WXUserCollectAuthProdListForm.cs | 75 ++- .../WXUserCollectAuthProdListForm.resx | 6 +- Work/Services/GoodsComplianceService.cs | 68 ++- Work/UserLogin.cs | 2 + 12 files changed, 880 insertions(+), 377 deletions(-) create mode 100644 TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.resx diff --git a/AllBase/BaseClass.cs b/AllBase/BaseClass.cs index cfc611d..18d869e 100644 --- a/AllBase/BaseClass.cs +++ b/AllBase/BaseClass.cs @@ -117,10 +117,14 @@ namespace AllBase public static decimal PostagePerOrder { get; set; } = 2.2m; public static decimal RefundPostagePerOrder { get; set; } = 2.5m; public static decimal GiftOemAmountPerOrder { get; set; } = 0m; - /// - /// 采集最低销量 - /// - public static int MinSales = 0; + /// + /// 图片检测配额 + /// + public static int ImageAnalysisQuota { get; set; } + /// + /// 采集最低销量 + /// + public static int MinSales = 0; public static int MinPercent = 0; public static int MinLevel = 0; public static int AlimamaPageCount = 0; diff --git a/TEMUKJD/KJD.csproj.user b/TEMUKJD/KJD.csproj.user index ae62c83..5149de2 100644 --- a/TEMUKJD/KJD.csproj.user +++ b/TEMUKJD/KJD.csproj.user @@ -10,7 +10,7 @@ zh-CN false - Production + Development false diff --git a/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.Designer.cs b/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.Designer.cs index f00358c..fbbba11 100644 --- a/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.Designer.cs +++ b/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.Designer.cs @@ -22,165 +22,346 @@ namespace TemuKJD.KJDForms.SystemForms private void InitializeComponent() { - this.imagesGroup = new GroupBox(); - this.mainLabel = new Label(); - this.main = new ComboBox(); - this.mainCount = new NumericUpDown(); - this.mainUnit = new Label(); - this.detailLabel = new Label(); - this.detail = new ComboBox(); - this.detailCount = new NumericUpDown(); - this.detailUnit = new Label(); - this.sku = new CheckBox(); - this.keywordsGroup = new GroupBox(); - this.keywords = new TextBox(); - this.keywordsHint = new Label(); - this.btnSaveDetection = new Button(); - this.imagesGroup.SuspendLayout(); - this.keywordsGroup.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.mainCount)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.detailCount)).BeginInit(); - this.SuspendLayout(); + this.imagesGroup = new System.Windows.Forms.GroupBox(); + this.mainLabel = new System.Windows.Forms.Label(); + this.main = new System.Windows.Forms.ComboBox(); + this.mainCount = new System.Windows.Forms.NumericUpDown(); + this.mainUnit = new System.Windows.Forms.Label(); + this.detailLabel = new System.Windows.Forms.Label(); + this.detail = new System.Windows.Forms.ComboBox(); + this.detailCount = new System.Windows.Forms.NumericUpDown(); + this.detailUnit = new System.Windows.Forms.Label(); + this.sku = new System.Windows.Forms.CheckBox(); + this.keywordsGroup = new System.Windows.Forms.GroupBox(); + this.keywords = new System.Windows.Forms.TextBox(); + this.keywordsHint = new System.Windows.Forms.Label(); + this.btnSaveDetection = new System.Windows.Forms.Button(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.num_points = new System.Windows.Forms.NumericUpDown(); + this.lb_imagequtoa = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.btn_Exchange = new System.Windows.Forms.Button(); + this.label4 = new System.Windows.Forms.Label(); + this.imagesGroup.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mainCount)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.detailCount)).BeginInit(); + this.keywordsGroup.SuspendLayout(); + this.groupBox1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.num_points)).BeginInit(); + this.SuspendLayout(); + // + // imagesGroup + // + this.imagesGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.imagesGroup.Controls.Add(this.mainLabel); + this.imagesGroup.Controls.Add(this.main); + this.imagesGroup.Controls.Add(this.mainCount); + this.imagesGroup.Controls.Add(this.mainUnit); + this.imagesGroup.Controls.Add(this.detailLabel); + this.imagesGroup.Controls.Add(this.detail); + this.imagesGroup.Controls.Add(this.detailCount); + this.imagesGroup.Controls.Add(this.detailUnit); + this.imagesGroup.Controls.Add(this.sku); + this.imagesGroup.Location = new System.Drawing.Point(20, 71); + this.imagesGroup.Name = "imagesGroup"; + this.imagesGroup.Size = new System.Drawing.Size(760, 153); + this.imagesGroup.TabIndex = 0; + this.imagesGroup.TabStop = false; + this.imagesGroup.Text = "检测图片"; + // + // mainLabel + // + this.mainLabel.AutoSize = true; + this.mainLabel.Location = new System.Drawing.Point(16, 34); + this.mainLabel.Name = "mainLabel"; + this.mainLabel.Size = new System.Drawing.Size(56, 17); + this.mainLabel.TabIndex = 0; + this.mainLabel.Text = "商品主图"; + // + // main + // + this.main.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.main.Items.AddRange(new object[] { + "全部", + "前几张"}); + this.main.Location = new System.Drawing.Point(106, 30); + this.main.Name = "main"; + this.main.Size = new System.Drawing.Size(110, 25); + this.main.TabIndex = 1; + this.main.SelectedIndexChanged += new System.EventHandler(this.ImageRangeChanged); + // + // mainCount + // + this.mainCount.AccessibleName = "商品主图检测张数"; + this.mainCount.Enabled = false; + this.mainCount.Location = new System.Drawing.Point(228, 30); + this.mainCount.Maximum = new decimal(new int[] { + 999, + 0, + 0, + 0}); + this.mainCount.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.mainCount.Name = "mainCount"; + this.mainCount.Size = new System.Drawing.Size(60, 23); + this.mainCount.TabIndex = 2; + this.mainCount.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + // + // mainUnit + // + this.mainUnit.AutoSize = true; + this.mainUnit.Location = new System.Drawing.Point(294, 34); + this.mainUnit.Name = "mainUnit"; + this.mainUnit.Size = new System.Drawing.Size(20, 17); + this.mainUnit.TabIndex = 3; + this.mainUnit.Text = "张"; + // + // detailLabel + // + this.detailLabel.AutoSize = true; + this.detailLabel.Location = new System.Drawing.Point(16, 72); + this.detailLabel.Name = "detailLabel"; + this.detailLabel.Size = new System.Drawing.Size(44, 17); + this.detailLabel.TabIndex = 4; + this.detailLabel.Text = "详情图"; + // + // detail + // + this.detail.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.detail.Items.AddRange(new object[] { + "不检测", + "前几张", + "全部"}); + this.detail.Location = new System.Drawing.Point(106, 68); + this.detail.Name = "detail"; + this.detail.Size = new System.Drawing.Size(110, 25); + this.detail.TabIndex = 5; + this.detail.SelectedIndexChanged += new System.EventHandler(this.ImageRangeChanged); + // + // detailCount + // + this.detailCount.AccessibleName = "详情图检测张数"; + this.detailCount.Enabled = false; + this.detailCount.Location = new System.Drawing.Point(228, 68); + this.detailCount.Maximum = new decimal(new int[] { + 999, + 0, + 0, + 0}); + this.detailCount.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.detailCount.Name = "detailCount"; + this.detailCount.Size = new System.Drawing.Size(60, 23); + this.detailCount.TabIndex = 6; + this.detailCount.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + // + // detailUnit + // + this.detailUnit.AutoSize = true; + this.detailUnit.Location = new System.Drawing.Point(294, 72); + this.detailUnit.Name = "detailUnit"; + this.detailUnit.Size = new System.Drawing.Size(20, 17); + this.detailUnit.TabIndex = 7; + this.detailUnit.Text = "张"; + // + // sku + // + this.sku.AutoSize = true; + this.sku.Location = new System.Drawing.Point(106, 112); + this.sku.Name = "sku"; + this.sku.Size = new System.Drawing.Size(107, 21); + this.sku.TabIndex = 8; + this.sku.Text = "检测 SKU 图片"; + // + // keywordsGroup + // + this.keywordsGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.keywordsGroup.Controls.Add(this.keywords); + this.keywordsGroup.Controls.Add(this.keywordsHint); + this.keywordsGroup.Location = new System.Drawing.Point(20, 238); + this.keywordsGroup.Name = "keywordsGroup"; + this.keywordsGroup.Size = new System.Drawing.Size(760, 143); + this.keywordsGroup.TabIndex = 1; + this.keywordsGroup.TabStop = false; + this.keywordsGroup.Text = "检测关键词"; + // + // keywords + // + this.keywords.AcceptsReturn = true; + this.keywords.AccessibleName = "检测关键词"; + this.keywords.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.keywords.Location = new System.Drawing.Point(16, 27); + this.keywords.Multiline = true; + this.keywords.Name = "keywords"; + this.keywords.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.keywords.Size = new System.Drawing.Size(728, 68); + this.keywords.TabIndex = 0; + // + // keywordsHint + // + this.keywordsHint.AutoSize = true; + this.keywordsHint.ForeColor = System.Drawing.SystemColors.GrayText; + this.keywordsHint.Location = new System.Drawing.Point(16, 105); + this.keywordsHint.Name = "keywordsHint"; + this.keywordsHint.Size = new System.Drawing.Size(260, 17); + this.keywordsHint.TabIndex = 1; + this.keywordsHint.Text = "多个关键词可用逗号、分号、空格或换行分隔。"; + // + // btnSaveDetection + // + this.btnSaveDetection.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255))))); + this.btnSaveDetection.Cursor = System.Windows.Forms.Cursors.Hand; + this.btnSaveDetection.FlatAppearance.BorderSize = 0; + this.btnSaveDetection.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.btnSaveDetection.ForeColor = System.Drawing.Color.White; + this.btnSaveDetection.Location = new System.Drawing.Point(20, 396); + this.btnSaveDetection.Name = "btnSaveDetection"; + this.btnSaveDetection.Size = new System.Drawing.Size(96, 32); + this.btnSaveDetection.TabIndex = 2; + this.btnSaveDetection.Text = "保存条件"; + this.btnSaveDetection.UseVisualStyleBackColor = false; + this.btnSaveDetection.Click += new System.EventHandler(this.SaveDetectionClick); + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.label4); + this.groupBox1.Controls.Add(this.btn_Exchange); + this.groupBox1.Controls.Add(this.label3); + this.groupBox1.Controls.Add(this.num_points); + this.groupBox1.Controls.Add(this.lb_imagequtoa); + this.groupBox1.Controls.Add(this.label1); + this.groupBox1.Location = new System.Drawing.Point(20, 12); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(760, 53); + this.groupBox1.TabIndex = 3; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "积分兑换"; + // + // num_points + // + this.num_points.Location = new System.Drawing.Point(269, 20); + this.num_points.Maximum = new decimal(new int[] { + 1000, + 0, + 0, + 0}); + this.num_points.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.num_points.Name = "num_points"; + this.num_points.Size = new System.Drawing.Size(120, 23); + this.num_points.TabIndex = 2; + this.num_points.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + // + // lb_imagequtoa + // + this.lb_imagequtoa.AutoSize = true; + this.lb_imagequtoa.Location = new System.Drawing.Point(130, 23); + this.lb_imagequtoa.Name = "lb_imagequtoa"; + this.lb_imagequtoa.Size = new System.Drawing.Size(50, 17); + this.lb_imagequtoa.TabIndex = 1; + this.lb_imagequtoa.Text = "999999"; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(65, 23); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(68, 17); + this.label1.TabIndex = 0; + this.label1.Text = "检测图片:"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(195, 23); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(56, 17); + this.label3.TabIndex = 3; + this.label3.Text = "积分兑换"; + // + // btn_Exchange + // + this.btn_Exchange.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255))))); + this.btn_Exchange.Cursor = System.Windows.Forms.Cursors.Hand; + this.btn_Exchange.FlatAppearance.BorderSize = 0; + this.btn_Exchange.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.btn_Exchange.ForeColor = System.Drawing.Color.White; + this.btn_Exchange.Location = new System.Drawing.Point(395, 19); + this.btn_Exchange.Name = "btn_Exchange"; + this.btn_Exchange.Size = new System.Drawing.Size(68, 24); + this.btn_Exchange.TabIndex = 4; + this.btn_Exchange.Text = "兑换"; + this.btn_Exchange.UseVisualStyleBackColor = false; + this.btn_Exchange.Click += new System.EventHandler(this.btn_Exchange_Click); + // + // label4 + // + this.label4.AutoSize = true; + this.label4.ForeColor = System.Drawing.SystemColors.ControlDark; + this.label4.Location = new System.Drawing.Point(478, 23); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(165, 17); + this.label4.TabIndex = 5; + this.label4.Text = "兑换比例:1积分=100张图片"; + // + // SystemGoodsComplianceForm + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + this.AutoScroll = true; + this.AutoScrollMinSize = new System.Drawing.Size(365, 390); + this.BackColor = System.Drawing.Color.White; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.imagesGroup); + this.Controls.Add(this.keywordsGroup); + this.Controls.Add(this.btnSaveDetection); + this.Font = new System.Drawing.Font("微软雅黑", 9F); + this.Name = "SystemGoodsComplianceForm"; + this.Text = "商品 AI 检测"; + this.imagesGroup.ResumeLayout(false); + this.imagesGroup.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mainCount)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.detailCount)).EndInit(); + this.keywordsGroup.ResumeLayout(false); + this.keywordsGroup.PerformLayout(); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.num_points)).EndInit(); + this.ResumeLayout(false); - this.imagesGroup.Name = "imagesGroup"; - this.imagesGroup.Location = new Point(20, 16); - this.imagesGroup.TabIndex = 0; - this.imagesGroup.Size = new Size(760, 153); - this.imagesGroup.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; - this.imagesGroup.Text = "检测图片"; - this.imagesGroup.Controls.Add(this.mainLabel); - this.imagesGroup.Controls.Add(this.main); - this.imagesGroup.Controls.Add(this.mainCount); - this.imagesGroup.Controls.Add(this.mainUnit); - this.imagesGroup.Controls.Add(this.detailLabel); - this.imagesGroup.Controls.Add(this.detail); - this.imagesGroup.Controls.Add(this.detailCount); - this.imagesGroup.Controls.Add(this.detailUnit); - this.imagesGroup.Controls.Add(this.sku); - - this.mainLabel.Name = "mainLabel"; - this.mainLabel.Location = new Point(16, 34); - this.mainLabel.TabIndex = 0; - this.mainLabel.AutoSize = true; - this.mainLabel.Text = "商品主图"; - - this.main.Name = "main"; - this.main.Location = new Point(106, 30); - this.main.TabIndex = 1; - this.main.Width = 110; - this.main.DropDownStyle = ComboBoxStyle.DropDownList; - this.main.Items.AddRange(new object[] { "全部", "前几张" }); - this.main.SelectedIndexChanged += new System.EventHandler(this.ImageRangeChanged); - - this.mainCount.Name = "mainCount"; - this.mainCount.Location = new Point(228, 30); - this.mainCount.TabIndex = 2; - this.mainCount.Enabled = false; - this.mainCount.Minimum = 1M; - this.mainCount.Maximum = 999M; - this.mainCount.Value = 1M; - this.mainCount.Width = 60; - this.mainCount.AccessibleName = "商品主图检测张数"; - - this.mainUnit.Name = "mainUnit"; - this.mainUnit.Location = new Point(294, 34); - this.mainUnit.TabIndex = 3; - this.mainUnit.AutoSize = true; - this.mainUnit.Text = "张"; - - this.detailLabel.Name = "detailLabel"; - this.detailLabel.Location = new Point(16, 72); - this.detailLabel.TabIndex = 4; - this.detailLabel.AutoSize = true; - this.detailLabel.Text = "详情图"; - - this.detail.Name = "detail"; - this.detail.Location = new Point(106, 68); - this.detail.TabIndex = 5; - this.detail.Width = 110; - this.detail.DropDownStyle = ComboBoxStyle.DropDownList; - this.detail.Items.AddRange(new object[] { "不检测", "前几张", "全部" }); - this.detail.SelectedIndexChanged += new System.EventHandler(this.ImageRangeChanged); - - this.detailCount.Name = "detailCount"; - this.detailCount.Location = new Point(228, 68); - this.detailCount.TabIndex = 6; - this.detailCount.Enabled = false; - this.detailCount.Minimum = 1M; - this.detailCount.Maximum = 999M; - this.detailCount.Value = 1M; - this.detailCount.Width = 60; - this.detailCount.AccessibleName = "详情图检测张数"; - - this.detailUnit.Name = "detailUnit"; - this.detailUnit.Location = new Point(294, 72); - this.detailUnit.TabIndex = 7; - this.detailUnit.AutoSize = true; - this.detailUnit.Text = "张"; - - this.sku.Name = "sku"; - this.sku.Location = new Point(106, 112); - this.sku.TabIndex = 8; - this.sku.AutoSize = true; - this.sku.Text = "检测 SKU 图片"; - - this.keywordsGroup.Name = "keywordsGroup"; - this.keywordsGroup.Location = new Point(20, 183); - this.keywordsGroup.TabIndex = 1; - this.keywordsGroup.Size = new Size(760, 143); - this.keywordsGroup.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; - this.keywordsGroup.Text = "检测关键词"; - this.keywordsGroup.Controls.Add(this.keywords); - this.keywordsGroup.Controls.Add(this.keywordsHint); - - this.keywords.Name = "keywords"; - this.keywords.Location = new Point(16, 27); - this.keywords.TabIndex = 0; - this.keywords.Size = new Size(728, 68); - this.keywords.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; - this.keywords.Multiline = true; - this.keywords.AcceptsReturn = true; - this.keywords.ScrollBars = ScrollBars.Vertical; - this.keywords.AccessibleName = "检测关键词"; - - this.keywordsHint.Name = "keywordsHint"; - this.keywordsHint.Location = new Point(16, 105); - this.keywordsHint.TabIndex = 1; - this.keywordsHint.AutoSize = true; - this.keywordsHint.ForeColor = SystemColors.GrayText; - this.keywordsHint.Text = "多个关键词可用逗号、分号、空格或换行分隔。"; - - this.btnSaveDetection.Name = "btnSaveDetection"; - this.btnSaveDetection.Location = new Point(20, 341); - this.btnSaveDetection.TabIndex = 2; - this.btnSaveDetection.Size = new Size(96, 32); - this.btnSaveDetection.Text = "保存条件"; - this.btnSaveDetection.BackColor = Color.FromArgb(63, 126, 255); - this.btnSaveDetection.ForeColor = Color.White; - this.btnSaveDetection.FlatStyle = FlatStyle.Flat; - this.btnSaveDetection.UseVisualStyleBackColor = false; - this.btnSaveDetection.Cursor = Cursors.Hand; - this.btnSaveDetection.FlatAppearance.BorderSize = 0; - this.btnSaveDetection.Click += new System.EventHandler(this.SaveDetectionClick); - - this.AutoScaleMode = AutoScaleMode.None; - this.AutoScroll = true; - this.AutoScrollMinSize = new Size(365, 390); - this.BackColor = Color.White; - this.ClientSize = new Size(800, 450); - this.Font = new Font("微软雅黑", 9F); - this.Controls.Add(this.imagesGroup); - this.Controls.Add(this.keywordsGroup); - this.Controls.Add(this.btnSaveDetection); - this.Name = "SystemGoodsComplianceForm"; - this.Text = "商品 AI 检测"; - this.imagesGroup.ResumeLayout(false); - this.imagesGroup.PerformLayout(); - this.keywordsGroup.ResumeLayout(false); - this.keywordsGroup.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.mainCount)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.detailCount)).EndInit(); - this.ResumeLayout(false); } - } + + private GroupBox groupBox1; + private NumericUpDown num_points; + private Label lb_imagequtoa; + private Label label1; + private Label label4; + private Button btn_Exchange; + private Label label3; + } } diff --git a/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.cs b/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.cs index c37c529..2cda08b 100644 --- a/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.cs +++ b/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.cs @@ -1,64 +1,109 @@ -using System; -using System.Windows.Forms; -using Work.Services; +using AllBase; using AllBase.Helper; +using AllBase.Model.Result; +using Newtonsoft.Json; +using System; +using System.Runtime.Remoting.Metadata.W3cXsd2001; +using System.Threading.Tasks; +using System.Windows.Forms; using TemuKJD.BaseForms; +using Work; +using Work.Services; namespace TemuKJD.KJDForms.SystemForms { - public partial class SystemGoodsComplianceForm : BaseForm - { - public SystemGoodsComplianceForm() - { - InitializeComponent(); + public partial class SystemGoodsComplianceForm : BaseForm + { + public SystemGoodsComplianceForm() + { + InitializeComponent(); - VisibleChanged += (sender, e) => { if (Visible) LoadSettings(); }; - LoadSettings(); - } + VisibleChanged += (sender, e) => { if (Visible) LoadSettings(); }; + LoadSettings(); + } - private void LoadSettings() - { - var options = GlobalConfig.GlobalSettings.GoodsCompliance ?? new ComplianceOptions(); - main.SelectedIndex = options.MainCount.HasValue ? 1 : 0; - detail.SelectedIndex = options.DetailCount == null ? 2 : options.DetailCount == 0 ? 0 : 1; - mainCount.Value = Math.Min(mainCount.Maximum, Math.Max(1, options.MainCount ?? 1)); - detailCount.Value = Math.Min(detailCount.Maximum, Math.Max(1, options.DetailCount ?? 1)); - sku.Checked = options.IncludeSku; - keywords.Text = options.Keywords; - } + private void LoadSettings() + { + lb_imagequtoa.Text = BaseClass.ImageAnalysisQuota.ToString(); - private void ImageRangeChanged(object sender, EventArgs e) - { - mainCount.Enabled = main.SelectedIndex == 1; - detailCount.Enabled = detail.SelectedIndex == 1; - } - private void SaveDetectionClick(object sender, EventArgs e) - { - if (GoodsComplianceService.Keywords(keywords.Text).Length == 0) - { - MessageBox.Show(this, "请输入检测关键词。"); - keywords.Focus(); - return; - } - var settings = GlobalConfig.GlobalSettings; - var previous = settings.GoodsCompliance; - settings.GoodsCompliance = new ComplianceOptions - { - MainCount = main.SelectedIndex == 0 ? (int?)null : (int)mainCount.Value, - DetailCount = detail.SelectedIndex == 2 ? (int?)null : detail.SelectedIndex == 0 ? 0 : (int)detailCount.Value, - IncludeSku = sku.Checked, - Keywords = keywords.Text.Trim() - }; - try { StartUp.SaveGlobalSettings(); } - catch (Exception ex) - { - settings.GoodsCompliance = previous; - MessageBox.Show(this, "保存检测条件失败:" + ex.Message); - return; - } - MessageBox.Show(this, "已保存 AI 检测条件。"); - } - } + var options = GlobalConfig.GlobalSettings.GoodsCompliance ?? new ComplianceOptions(); + main.SelectedIndex = options.MainCount.HasValue ? 1 : 0; + detail.SelectedIndex = options.DetailCount == null ? 2 : options.DetailCount == 0 ? 0 : 1; + mainCount.Value = Math.Min(mainCount.Maximum, Math.Max(1, options.MainCount ?? 1)); + detailCount.Value = Math.Min(detailCount.Maximum, Math.Max(1, options.DetailCount ?? 1)); + sku.Checked = options.IncludeSku; + keywords.Text = options.Keywords; + } + + private void ImageRangeChanged(object sender, EventArgs e) + { + mainCount.Enabled = main.SelectedIndex == 1; + detailCount.Enabled = detail.SelectedIndex == 1; + } + + private void SaveDetectionClick(object sender, EventArgs e) + { + if (GoodsComplianceService.Keywords(keywords.Text).Length == 0) + { + MessageBox.Show(this, "请输入检测关键词。"); + keywords.Focus(); + return; + } + var settings = GlobalConfig.GlobalSettings; + var previous = settings.GoodsCompliance; + settings.GoodsCompliance = new ComplianceOptions + { + MainCount = main.SelectedIndex == 0 ? (int?)null : (int)mainCount.Value, + DetailCount = detail.SelectedIndex == 2 ? (int?)null : detail.SelectedIndex == 0 ? 0 : (int)detailCount.Value, + IncludeSku = sku.Checked, + Keywords = keywords.Text.Trim() + }; + try { StartUp.SaveGlobalSettings(); } + catch (Exception ex) + { + settings.GoodsCompliance = previous; + MessageBox.Show(this, "保存检测条件失败:" + ex.Message); + return; + } + MessageBox.Show(this, "已保存 AI 检测条件。"); + } + + private async void btn_Exchange_Click(object sender, EventArgs e) + { + try + { + btn_Exchange.Enabled = false; + int points = (int)num_points.Value; + if (BaseMsgForm.ShowConfirm($"是否要兑换 {points} 积分,共:{points * 100}张图片检测?", "提示").DialogResult != DialogResult.OK) + { + return; + } + var url = BaseClass.BuildObjString("user/BuyImageAnalysisQuota"); + var data = UserLogin.GetSignPostData(new + { + Amount = points, + }); + + string html = await GetHtml.PostJsonAsync(url, data); + var ret = JsonConvert.DeserializeObject>(html); + if (ret.Code == 200) + { + BaseClass.ImageAnalysisQuota += ret.Data; + lb_imagequtoa.Text = BaseClass.ImageAnalysisQuota.ToString(); + BaseMsgForm.ShowInfo("兑换成功"); + } + } + catch (Exception ex) + { + BaseMsgForm.ShowInfo("兑换失败:" + ex.Message); + } + finally + { + btn_Exchange.Enabled = true; + } + + } + } } diff --git a/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.resx b/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/TEMUKJD/KJDForms/SystemForms/SystemGoodsComplianceForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.Designer.cs b/TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.Designer.cs index ea108f4..1543b12 100644 --- a/TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.Designer.cs +++ b/TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.Designer.cs @@ -57,9 +57,6 @@ namespace TemuKJD.WXForms.ProductForms this.splitContainer2 = new System.Windows.Forms.SplitContainer(); this.pbLoading = new System.Windows.Forms.PictureBox(); this.dgvData = new TemuKJD.UserComponent.DataGridViewEx(); - this.ucSplitPage1 = new TemuKJD.UserControls.UcSplitPage(); - this.statusBar = new TemuKJD.UserControls.StatusBarUserControl(); - this.numericUpDown1 = new System.Windows.Forms.NumericUpDown(); this.colSelect = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.c_Platform = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.Column1 = new System.Windows.Forms.DataGridViewImageColumn(); @@ -68,6 +65,9 @@ namespace TemuKJD.WXForms.ProductForms this.Column3 = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.Column4 = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colDetectionResult = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.ucSplitPage1 = new TemuKJD.UserControls.UcSplitPage(); + this.statusBar = new TemuKJD.UserControls.StatusBarUserControl(); + this.numericUpDown1 = new System.Windows.Forms.NumericUpDown(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); this.splitContainer1.Panel1.SuspendLayout(); this.splitContainer1.Panel2.SuspendLayout(); @@ -223,8 +223,8 @@ namespace TemuKJD.WXForms.ProductForms this.btnSafeDetection.Name = "btnSafeDetection"; this.btnSafeDetection.Size = new System.Drawing.Size(58, 23); this.btnSafeDetection.Style.Alignment = System.Drawing.StringAlignment.Center; - this.btnSafeDetection.Style.BackColor1.Color = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255))))); - this.btnSafeDetection.Style.BackColor2.Color = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255))))); + this.btnSafeDetection.Style.BackColor1.Color = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(128)))), ((int)(((byte)(0))))); + this.btnSafeDetection.Style.BackColor2.Color = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(128)))), ((int)(((byte)(0))))); this.btnSafeDetection.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder; this.btnSafeDetection.Style.BorderDashStyle = System.Drawing.Drawing2D.DashStyle.Dash; this.btnSafeDetection.Style.BorderSide = DevComponents.DotNetBar.eBorderSide.None; @@ -519,41 +519,6 @@ namespace TemuKJD.WXForms.ProductForms this.dgvData.Size = new System.Drawing.Size(1711, 447); this.dgvData.TabIndex = 5; // - // ucSplitPage1 - // - this.ucSplitPage1.CurrentPage = 1; - this.ucSplitPage1.Dock = System.Windows.Forms.DockStyle.Right; - this.ucSplitPage1.IsShowPageSize = true; - this.ucSplitPage1.IsShowTotalCount = true; - this.ucSplitPage1.Location = new System.Drawing.Point(1257, 0); - this.ucSplitPage1.Margin = new System.Windows.Forms.Padding(0); - this.ucSplitPage1.MaxPageSizeValue = 2000; - this.ucSplitPage1.Name = "ucSplitPage1"; - this.ucSplitPage1.PageSize = 50; - this.ucSplitPage1.Size = new System.Drawing.Size(462, 25); - this.ucSplitPage1.TabIndex = 4; - this.ucSplitPage1.TotalCount = ((long)(0)); - this.ucSplitPage1.TotalPage = 1; - // - // statusBar - // - this.statusBar.BackColor = System.Drawing.Color.White; - this.statusBar.Dock = System.Windows.Forms.DockStyle.Fill; - this.statusBar.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.statusBar.Location = new System.Drawing.Point(0, 0); - this.statusBar.Margin = new System.Windows.Forms.Padding(0); - this.statusBar.Name = "statusBar"; - this.statusBar.Size = new System.Drawing.Size(1719, 25); - this.statusBar.TabIndex = 1; - this.statusBar.Visible = false; - // - // numericUpDown1 - // - this.numericUpDown1.Location = new System.Drawing.Point(918, 274); - this.numericUpDown1.Name = "numericUpDown1"; - this.numericUpDown1.Size = new System.Drawing.Size(120, 21); - this.numericUpDown1.TabIndex = 7; - // // colSelect // this.colSelect.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; @@ -629,6 +594,41 @@ namespace TemuKJD.WXForms.ProductForms this.colDetectionResult.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; this.colDetectionResult.Width = 160; // + // ucSplitPage1 + // + this.ucSplitPage1.CurrentPage = 1; + this.ucSplitPage1.Dock = System.Windows.Forms.DockStyle.Right; + this.ucSplitPage1.IsShowPageSize = true; + this.ucSplitPage1.IsShowTotalCount = true; + this.ucSplitPage1.Location = new System.Drawing.Point(1257, 0); + this.ucSplitPage1.Margin = new System.Windows.Forms.Padding(0); + this.ucSplitPage1.MaxPageSizeValue = 2000; + this.ucSplitPage1.Name = "ucSplitPage1"; + this.ucSplitPage1.PageSize = 50; + this.ucSplitPage1.Size = new System.Drawing.Size(462, 25); + this.ucSplitPage1.TabIndex = 4; + this.ucSplitPage1.TotalCount = ((long)(0)); + this.ucSplitPage1.TotalPage = 1; + // + // statusBar + // + this.statusBar.BackColor = System.Drawing.Color.White; + this.statusBar.Dock = System.Windows.Forms.DockStyle.Fill; + this.statusBar.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.statusBar.Location = new System.Drawing.Point(0, 0); + this.statusBar.Margin = new System.Windows.Forms.Padding(0); + this.statusBar.Name = "statusBar"; + this.statusBar.Size = new System.Drawing.Size(1719, 25); + this.statusBar.TabIndex = 1; + this.statusBar.Visible = false; + // + // numericUpDown1 + // + this.numericUpDown1.Location = new System.Drawing.Point(918, 274); + this.numericUpDown1.Name = "numericUpDown1"; + this.numericUpDown1.Size = new System.Drawing.Size(120, 21); + this.numericUpDown1.TabIndex = 7; + // // CloudGoodsFavoriteForm // this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; diff --git a/TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.cs b/TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.cs index ed9a9d9..bfe3b51 100644 --- a/TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.cs +++ b/TEMUKJD/WXForms/ProductForms/CloudGoodsFavoriteForm.cs @@ -498,6 +498,8 @@ namespace TemuKJD.WXForms.ProductForms /// private async void btnSafeDetection_Click(object sender, EventArgs e) { + BaseMsgForm.ShowInfo($"当前AI检测剩余:{BaseClass.ImageAnalysisQuota}张"); + if (detection != null) { detection.Cancel(); diff --git a/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.Designer.cs b/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.Designer.cs index 2aeb735..f9fa647 100644 --- a/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.Designer.cs +++ b/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.Designer.cs @@ -30,23 +30,23 @@ namespace TemuKJD.WXForms.ProductForms private void InitializeComponent() { this.components = new System.ComponentModel.Container(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle4 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle5 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle6 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle7 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle8 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle9 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle10 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle11 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle12 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle13 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle14 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle15 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle16 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle17 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle18 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle19 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle20 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle21 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle22 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle23 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle24 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle25 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle26 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle27 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle28 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle29 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle30 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle31 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle32 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle33 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle34 = new System.Windows.Forms.DataGridViewCellStyle(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(WXUserCollectAuthProdListForm)); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.btn_import_wx = new DevComponents.DotNetBar.PanelEx(); @@ -95,6 +95,8 @@ namespace TemuKJD.WXForms.ProductForms this.dtpStart = new System.Windows.Forms.DateTimePicker(); this.dtpEnd = new System.Windows.Forms.DateTimePicker(); this.label10 = new System.Windows.Forms.Label(); + this.txt_listing_time = new System.Windows.Forms.TextBox(); + this.btnSafeDetection = new DevComponents.DotNetBar.PanelEx(); this.label7 = new System.Windows.Forms.Label(); this.splitContainer2 = new System.Windows.Forms.SplitContainer(); this.lblRelation = new System.Windows.Forms.Label(); @@ -152,10 +154,10 @@ namespace TemuKJD.WXForms.ProductForms this.col_edit = new System.Windows.Forms.DataGridViewLinkColumn(); this.col_delete = new System.Windows.Forms.DataGridViewLinkColumn(); this.col_select_product = new System.Windows.Forms.DataGridViewLinkColumn(); + this.colDetectionResult = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.ucSplitPage1 = new TemuKJD.UserControls.UcSplitPage(); this.statusBar = new TemuKJD.UserControls.StatusBarUserControl(); this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); - this.txt_listing_time = new System.Windows.Forms.TextBox(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); this.splitContainer1.Panel1.SuspendLayout(); this.splitContainer1.Panel2.SuspendLayout(); @@ -611,6 +613,7 @@ namespace TemuKJD.WXForms.ProductForms this.tableLayoutPanel1.Controls.Add(this.dtpEnd, 3, 2); this.tableLayoutPanel1.Controls.Add(this.label10, 10, 2); this.tableLayoutPanel1.Controls.Add(this.txt_listing_time, 11, 2); + this.tableLayoutPanel1.Controls.Add(this.btnSafeDetection, 5, 2); this.tableLayoutPanel1.Location = new System.Drawing.Point(4, 6); this.tableLayoutPanel1.Name = "tableLayoutPanel1"; this.tableLayoutPanel1.RowCount = 3; @@ -987,7 +990,44 @@ namespace TemuKJD.WXForms.ProductForms this.label10.Size = new System.Drawing.Size(56, 17); this.label10.TabIndex = 983; this.label10.Text = "上架日期"; - this.toolTip1.SetToolTip(this.label10, "导入数据时超出时间(天)范围的不导入"); + // + // txt_listing_time + // + this.txt_listing_time.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txt_listing_time.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.txt_listing_time.Font = new System.Drawing.Font("微软雅黑", 10F); + this.txt_listing_time.Location = new System.Drawing.Point(888, 73); + this.txt_listing_time.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); + this.txt_listing_time.Name = "txt_listing_time"; + this.txt_listing_time.Size = new System.Drawing.Size(101, 25); + this.txt_listing_time.TabIndex = 984; + this.txt_listing_time.Text = "365"; + // + // btnSafeDetection + // + this.btnSafeDetection.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnSafeDetection.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled; + this.btnSafeDetection.Cursor = System.Windows.Forms.Cursors.Hand; + this.btnSafeDetection.DisabledBackColor = System.Drawing.Color.Empty; + this.btnSafeDetection.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.btnSafeDetection.Location = new System.Drawing.Point(388, 73); + this.btnSafeDetection.Name = "btnSafeDetection"; + this.btnSafeDetection.Size = new System.Drawing.Size(96, 25); + this.btnSafeDetection.Style.Alignment = System.Drawing.StringAlignment.Center; + this.btnSafeDetection.Style.BackColor1.Color = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255))))); + this.btnSafeDetection.Style.BackColor2.Color = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255))))); + this.btnSafeDetection.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder; + this.btnSafeDetection.Style.BorderDashStyle = System.Drawing.Drawing2D.DashStyle.Dash; + this.btnSafeDetection.Style.BorderSide = DevComponents.DotNetBar.eBorderSide.None; + this.btnSafeDetection.Style.ForeColor.Color = System.Drawing.Color.White; + this.btnSafeDetection.Style.GradientAngle = 90; + this.btnSafeDetection.StyleMouseOver.Alignment = System.Drawing.StringAlignment.Center; + this.btnSafeDetection.StyleMouseOver.BackColor1.Color = System.Drawing.Color.FromArgb(((int)(((byte)(25)))), ((int)(((byte)(102)))), ((int)(((byte)(255))))); + this.btnSafeDetection.StyleMouseOver.BackColor2.Color = System.Drawing.Color.FromArgb(((int)(((byte)(25)))), ((int)(((byte)(102)))), ((int)(((byte)(255))))); + this.btnSafeDetection.TabIndex = 1198; + this.btnSafeDetection.Text = "AI检测"; + this.btnSafeDetection.Click += new System.EventHandler(this.btnSafeDetection_Click); // // label7 // @@ -1036,14 +1076,14 @@ namespace TemuKJD.WXForms.ProductForms this.splitContainer2.Panel2.Controls.Add(this.ucSplitPage1); this.splitContainer2.Panel2.Controls.Add(this.statusBar); this.splitContainer2.Size = new System.Drawing.Size(1438, 567); - this.splitContainer2.SplitterDistance = 538; + this.splitContainer2.SplitterDistance = 537; this.splitContainer2.TabIndex = 0; // // lblRelation // this.lblRelation.Anchor = System.Windows.Forms.AnchorStyles.Right; this.lblRelation.AutoSize = true; - this.lblRelation.Location = new System.Drawing.Point(246, 69); + this.lblRelation.Location = new System.Drawing.Point(246, 68); this.lblRelation.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0); this.lblRelation.Name = "lblRelation"; this.lblRelation.Size = new System.Drawing.Size(68, 17); @@ -1055,7 +1095,7 @@ namespace TemuKJD.WXForms.ProductForms // this.pbLoading.Anchor = System.Windows.Forms.AnchorStyles.None; this.pbLoading.Image = global::TemuKJD.Properties.Resources.loading_blue; - this.pbLoading.Location = new System.Drawing.Point(694, 244); + this.pbLoading.Location = new System.Drawing.Point(694, 243); this.pbLoading.Name = "pbLoading"; this.pbLoading.Size = new System.Drawing.Size(50, 50); this.pbLoading.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; @@ -1066,7 +1106,7 @@ namespace TemuKJD.WXForms.ProductForms // this.label16.Anchor = System.Windows.Forms.AnchorStyles.Right; this.label16.AutoSize = true; - this.label16.Location = new System.Drawing.Point(22, 197); + this.label16.Location = new System.Drawing.Point(22, 196); this.label16.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0); this.label16.Name = "label16"; this.label16.Size = new System.Drawing.Size(69, 17); @@ -1077,7 +1117,7 @@ namespace TemuKJD.WXForms.ProductForms // txtRealCoid // this.txtRealCoid.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.txtRealCoid.Location = new System.Drawing.Point(91, 129); + this.txtRealCoid.Location = new System.Drawing.Point(91, 128); this.txtRealCoid.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.txtRealCoid.Name = "txtRealCoid"; this.txtRealCoid.Size = new System.Drawing.Size(133, 23); @@ -1087,7 +1127,7 @@ namespace TemuKJD.WXForms.ProductForms // txtSKC // this.txtSKC.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.txtSKC.Location = new System.Drawing.Point(91, 194); + this.txtSKC.Location = new System.Drawing.Point(91, 193); this.txtSKC.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.txtSKC.Name = "txtSKC"; this.txtSKC.Size = new System.Drawing.Size(133, 23); @@ -1098,7 +1138,7 @@ namespace TemuKJD.WXForms.ProductForms // this.label13.Anchor = System.Windows.Forms.AnchorStyles.Right; this.label13.AutoSize = true; - this.label13.Location = new System.Drawing.Point(23, 163); + this.label13.Location = new System.Drawing.Point(23, 162); this.label13.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0); this.label13.Name = "label13"; this.label13.Size = new System.Drawing.Size(68, 17); @@ -1109,7 +1149,7 @@ namespace TemuKJD.WXForms.ProductForms // txtSecretStyleCode // this.txtSecretStyleCode.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.txtSecretStyleCode.Location = new System.Drawing.Point(91, 37); + this.txtSecretStyleCode.Location = new System.Drawing.Point(91, 36); this.txtSecretStyleCode.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.txtSecretStyleCode.Name = "txtSecretStyleCode"; this.txtSecretStyleCode.Size = new System.Drawing.Size(133, 23); @@ -1120,7 +1160,7 @@ namespace TemuKJD.WXForms.ProductForms // this.label1.Anchor = System.Windows.Forms.AnchorStyles.Right; this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(22, 133); + this.label1.Location = new System.Drawing.Point(22, 132); this.label1.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(69, 17); @@ -1131,7 +1171,7 @@ namespace TemuKJD.WXForms.ProductForms // chkWXComplateProd // this.chkWXComplateProd.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.chkWXComplateProd.Location = new System.Drawing.Point(25, 233); + this.chkWXComplateProd.Location = new System.Drawing.Point(25, 232); this.chkWXComplateProd.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); this.chkWXComplateProd.Name = "chkWXComplateProd"; this.chkWXComplateProd.Size = new System.Drawing.Size(87, 21); @@ -1144,7 +1184,7 @@ namespace TemuKJD.WXForms.ProductForms // this.lblGroup.Anchor = System.Windows.Forms.AnchorStyles.Right; this.lblGroup.AutoSize = true; - this.lblGroup.Location = new System.Drawing.Point(270, 37); + this.lblGroup.Location = new System.Drawing.Point(270, 36); this.lblGroup.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0); this.lblGroup.Name = "lblGroup"; this.lblGroup.Size = new System.Drawing.Size(44, 17); @@ -1156,7 +1196,7 @@ namespace TemuKJD.WXForms.ProductForms // this.cmbGroupFilter.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); this.cmbGroupFilter.FormattingEnabled = true; - this.cmbGroupFilter.Location = new System.Drawing.Point(314, 34); + this.cmbGroupFilter.Location = new System.Drawing.Point(314, 33); this.cmbGroupFilter.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.cmbGroupFilter.Name = "cmbGroupFilter"; this.cmbGroupFilter.Size = new System.Drawing.Size(146, 25); @@ -1167,7 +1207,7 @@ namespace TemuKJD.WXForms.ProductForms // this.label15.Anchor = System.Windows.Forms.AnchorStyles.Right; this.label15.AutoSize = true; - this.label15.Location = new System.Drawing.Point(47, 40); + this.label15.Location = new System.Drawing.Point(47, 39); this.label15.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0); this.label15.Name = "label15"; this.label15.Size = new System.Drawing.Size(44, 17); @@ -1178,7 +1218,7 @@ namespace TemuKJD.WXForms.ProductForms // txtExtCode // this.txtExtCode.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.txtExtCode.Location = new System.Drawing.Point(91, 67); + this.txtExtCode.Location = new System.Drawing.Point(91, 66); this.txtExtCode.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.txtExtCode.Name = "txtExtCode"; this.txtExtCode.Size = new System.Drawing.Size(133, 23); @@ -1189,7 +1229,7 @@ namespace TemuKJD.WXForms.ProductForms // this.label4.Anchor = System.Windows.Forms.AnchorStyles.Right; this.label4.AutoSize = true; - this.label4.Location = new System.Drawing.Point(47, 70); + this.label4.Location = new System.Drawing.Point(47, 69); this.label4.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0); this.label4.Name = "label4"; this.label4.Size = new System.Drawing.Size(44, 17); @@ -1206,7 +1246,7 @@ namespace TemuKJD.WXForms.ProductForms "是", "否", "导出未拉回"}); - this.cmbExportToOutServer.Location = new System.Drawing.Point(91, 159); + this.cmbExportToOutServer.Location = new System.Drawing.Point(91, 158); this.cmbExportToOutServer.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.cmbExportToOutServer.Name = "cmbExportToOutServer"; this.cmbExportToOutServer.Size = new System.Drawing.Size(133, 25); @@ -1221,7 +1261,7 @@ namespace TemuKJD.WXForms.ProductForms "", "是", "否"}); - this.cmbRelation.Location = new System.Drawing.Point(314, 69); + this.cmbRelation.Location = new System.Drawing.Point(314, 68); this.cmbRelation.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.cmbRelation.Name = "cmbRelation"; this.cmbRelation.Size = new System.Drawing.Size(146, 25); @@ -1235,7 +1275,7 @@ namespace TemuKJD.WXForms.ProductForms this.cmbSort.Items.AddRange(new object[] { "入库时间", "认领总数"}); - this.cmbSort.Location = new System.Drawing.Point(91, 96); + this.cmbSort.Location = new System.Drawing.Point(91, 95); this.cmbSort.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.cmbSort.Name = "cmbSort"; this.cmbSort.Size = new System.Drawing.Size(133, 25); @@ -1247,7 +1287,7 @@ namespace TemuKJD.WXForms.ProductForms // this.label6.Anchor = System.Windows.Forms.AnchorStyles.Right; this.label6.AutoSize = true; - this.label6.Location = new System.Drawing.Point(47, 100); + this.label6.Location = new System.Drawing.Point(47, 99); this.label6.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0); this.label6.Name = "label6"; this.label6.Size = new System.Drawing.Size(44, 17); @@ -1259,20 +1299,20 @@ namespace TemuKJD.WXForms.ProductForms // this.dgvDbProduct.AllowUserToAddRows = false; this.dgvDbProduct.AllowUserToDeleteRows = false; - dataGridViewCellStyle1.BackColor = System.Drawing.Color.WhiteSmoke; - this.dgvDbProduct.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1; + dataGridViewCellStyle18.BackColor = System.Drawing.Color.WhiteSmoke; + this.dgvDbProduct.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle18; this.dgvDbProduct.AutoRowNumber = true; this.dgvDbProduct.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill; this.dgvDbProduct.BackgroundColor = System.Drawing.Color.White; this.dgvDbProduct.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(126)))), ((int)(((byte)(255))))); - dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; - dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Control; - dataGridViewCellStyle2.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.WindowText; - dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight; - dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText; - dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False; - this.dgvDbProduct.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2; + dataGridViewCellStyle19.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; + dataGridViewCellStyle19.BackColor = System.Drawing.SystemColors.Control; + dataGridViewCellStyle19.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + dataGridViewCellStyle19.ForeColor = System.Drawing.SystemColors.WindowText; + dataGridViewCellStyle19.SelectionBackColor = System.Drawing.SystemColors.Highlight; + dataGridViewCellStyle19.SelectionForeColor = System.Drawing.SystemColors.HighlightText; + dataGridViewCellStyle19.WrapMode = System.Windows.Forms.DataGridViewTriState.False; + this.dgvDbProduct.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle19; this.dgvDbProduct.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.dgvDbProduct.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { this.colSelect, @@ -1310,14 +1350,15 @@ namespace TemuKJD.WXForms.ProductForms this.colMdyTime, this.col_edit, this.col_delete, - this.col_select_product}); + this.col_select_product, + this.colDetectionResult}); this.dgvDbProduct.Dock = System.Windows.Forms.DockStyle.Fill; this.dgvDbProduct.Location = new System.Drawing.Point(4, 0); this.dgvDbProduct.MultiSelectCheckBox = true; this.dgvDbProduct.Name = "dgvDbProduct"; this.dgvDbProduct.ReadOnly = true; this.dgvDbProduct.RowTemplate.Height = 23; - this.dgvDbProduct.Size = new System.Drawing.Size(1430, 538); + this.dgvDbProduct.Size = new System.Drawing.Size(1430, 537); this.dgvDbProduct.TabIndex = 6; this.dgvDbProduct.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.dgvDbProduct_CellContentClick); this.dgvDbProduct.Scroll += new System.Windows.Forms.ScrollEventHandler(this.dgvDbProduct_Scroll); @@ -1355,8 +1396,8 @@ namespace TemuKJD.WXForms.ProductForms // // col_product_name // - dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.col_product_name.DefaultCellStyle = dataGridViewCellStyle3; + dataGridViewCellStyle20.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.col_product_name.DefaultCellStyle = dataGridViewCellStyle20; this.col_product_name.FillWeight = 140F; this.col_product_name.HeaderText = "标题"; this.col_product_name.Name = "col_product_name"; @@ -1399,8 +1440,8 @@ namespace TemuKJD.WXForms.ProductForms // // col_spu_id // - dataGridViewCellStyle4.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.col_spu_id.DefaultCellStyle = dataGridViewCellStyle4; + dataGridViewCellStyle21.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.col_spu_id.DefaultCellStyle = dataGridViewCellStyle21; this.col_spu_id.HeaderText = "真实商品Id"; this.col_spu_id.Name = "col_spu_id"; this.col_spu_id.ReadOnly = true; @@ -1422,8 +1463,8 @@ namespace TemuKJD.WXForms.ProductForms // // colMainCate // - dataGridViewCellStyle5.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.colMainCate.DefaultCellStyle = dataGridViewCellStyle5; + dataGridViewCellStyle22.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.colMainCate.DefaultCellStyle = dataGridViewCellStyle22; this.colMainCate.FillWeight = 50F; this.colMainCate.HeaderText = "主类目"; this.colMainCate.Name = "colMainCate"; @@ -1431,8 +1472,8 @@ namespace TemuKJD.WXForms.ProductForms // // col_cat_name // - dataGridViewCellStyle6.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.col_cat_name.DefaultCellStyle = dataGridViewCellStyle6; + dataGridViewCellStyle23.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.col_cat_name.DefaultCellStyle = dataGridViewCellStyle23; this.col_cat_name.FillWeight = 60F; this.col_cat_name.HeaderText = "类目"; this.col_cat_name.Name = "col_cat_name"; @@ -1447,8 +1488,8 @@ namespace TemuKJD.WXForms.ProductForms // // col_ext_code // - dataGridViewCellStyle7.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.col_ext_code.DefaultCellStyle = dataGridViewCellStyle7; + dataGridViewCellStyle24.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.col_ext_code.DefaultCellStyle = dataGridViewCellStyle24; this.col_ext_code.HeaderText = "货号"; this.col_ext_code.Name = "col_ext_code"; this.col_ext_code.ReadOnly = true; @@ -1456,8 +1497,8 @@ namespace TemuKJD.WXForms.ProductForms // // colStyleCode // - dataGridViewCellStyle8.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.colStyleCode.DefaultCellStyle = dataGridViewCellStyle8; + dataGridViewCellStyle25.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.colStyleCode.DefaultCellStyle = dataGridViewCellStyle25; this.colStyleCode.HeaderText = "资料编码/真实上家"; this.colStyleCode.Name = "colStyleCode"; this.colStyleCode.ReadOnly = true; @@ -1465,8 +1506,8 @@ namespace TemuKJD.WXForms.ProductForms // // colGroup // - dataGridViewCellStyle9.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.colGroup.DefaultCellStyle = dataGridViewCellStyle9; + dataGridViewCellStyle26.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.colGroup.DefaultCellStyle = dataGridViewCellStyle26; this.colGroup.HeaderText = "分组"; this.colGroup.Name = "colGroup"; this.colGroup.ReadOnly = true; @@ -1474,8 +1515,8 @@ namespace TemuKJD.WXForms.ProductForms // // colPriceText // - dataGridViewCellStyle10.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.colPriceText.DefaultCellStyle = dataGridViewCellStyle10; + dataGridViewCellStyle27.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.colPriceText.DefaultCellStyle = dataGridViewCellStyle27; this.colPriceText.FillWeight = 60F; this.colPriceText.HeaderText = "母店价格"; this.colPriceText.Name = "colPriceText"; @@ -1525,8 +1566,8 @@ namespace TemuKJD.WXForms.ProductForms // // col_notes // - dataGridViewCellStyle11.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.col_notes.DefaultCellStyle = dataGridViewCellStyle11; + dataGridViewCellStyle28.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.col_notes.DefaultCellStyle = dataGridViewCellStyle28; this.col_notes.FillWeight = 70F; this.col_notes.HeaderText = "备注"; this.col_notes.Name = "col_notes"; @@ -1534,8 +1575,8 @@ namespace TemuKJD.WXForms.ProductForms // // colHeadWords // - dataGridViewCellStyle12.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.colHeadWords.DefaultCellStyle = dataGridViewCellStyle12; + dataGridViewCellStyle29.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.colHeadWords.DefaultCellStyle = dataGridViewCellStyle29; this.colHeadWords.HeaderText = "标题前缀"; this.colHeadWords.Name = "colHeadWords"; this.colHeadWords.ReadOnly = true; @@ -1543,8 +1584,8 @@ namespace TemuKJD.WXForms.ProductForms // // colEndWords // - dataGridViewCellStyle13.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.colEndWords.DefaultCellStyle = dataGridViewCellStyle13; + dataGridViewCellStyle30.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.colEndWords.DefaultCellStyle = dataGridViewCellStyle30; this.colEndWords.HeaderText = "标题后缀"; this.colEndWords.Name = "colEndWords"; this.colEndWords.ReadOnly = true; @@ -1552,16 +1593,16 @@ namespace TemuKJD.WXForms.ProductForms // // colFissionWords // - dataGridViewCellStyle14.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.colFissionWords.DefaultCellStyle = dataGridViewCellStyle14; + dataGridViewCellStyle31.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.colFissionWords.DefaultCellStyle = dataGridViewCellStyle31; this.colFissionWords.HeaderText = "裂变标题"; this.colFissionWords.Name = "colFissionWords"; this.colFissionWords.ReadOnly = true; // // colGetType // - dataGridViewCellStyle15.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.colGetType.DefaultCellStyle = dataGridViewCellStyle15; + dataGridViewCellStyle32.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.colGetType.DefaultCellStyle = dataGridViewCellStyle32; this.colGetType.FillWeight = 70F; this.colGetType.HeaderText = "采集来源"; this.colGetType.Name = "colGetType"; @@ -1584,8 +1625,8 @@ namespace TemuKJD.WXForms.ProductForms // // colAddTime // - dataGridViewCellStyle16.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.colAddTime.DefaultCellStyle = dataGridViewCellStyle16; + dataGridViewCellStyle33.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.colAddTime.DefaultCellStyle = dataGridViewCellStyle33; this.colAddTime.FillWeight = 70F; this.colAddTime.HeaderText = "入库时间"; this.colAddTime.Name = "colAddTime"; @@ -1593,8 +1634,8 @@ namespace TemuKJD.WXForms.ProductForms // // colMdyTime // - dataGridViewCellStyle17.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.colMdyTime.DefaultCellStyle = dataGridViewCellStyle17; + dataGridViewCellStyle34.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.colMdyTime.DefaultCellStyle = dataGridViewCellStyle34; this.colMdyTime.FillWeight = 70F; this.colMdyTime.HeaderText = "更新时间"; this.colMdyTime.Name = "colMdyTime"; @@ -1627,6 +1668,16 @@ namespace TemuKJD.WXForms.ProductForms this.col_select_product.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic; this.col_select_product.Visible = false; // + // colDetectionResult + // + this.colDetectionResult.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; + this.colDetectionResult.HeaderText = "AI检测结果"; + this.colDetectionResult.MinimumWidth = 50; + this.colDetectionResult.Name = "colDetectionResult"; + this.colDetectionResult.ReadOnly = true; + this.colDetectionResult.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + this.colDetectionResult.Width = 160; + // // ucSplitPage1 // this.ucSplitPage1.CurrentPage = 1; @@ -1639,7 +1690,7 @@ namespace TemuKJD.WXForms.ProductForms this.ucSplitPage1.Name = "ucSplitPage1"; this.ucSplitPage1.Padding = new System.Windows.Forms.Padding(0, 0, 1, 0); this.ucSplitPage1.PageSize = 100; - this.ucSplitPage1.Size = new System.Drawing.Size(453, 25); + this.ucSplitPage1.Size = new System.Drawing.Size(453, 26); this.ucSplitPage1.TabIndex = 6; this.ucSplitPage1.TotalCount = ((long)(0)); this.ucSplitPage1.TotalPage = 1; @@ -1652,22 +1703,9 @@ namespace TemuKJD.WXForms.ProductForms this.statusBar.Location = new System.Drawing.Point(0, 0); this.statusBar.Margin = new System.Windows.Forms.Padding(0); this.statusBar.Name = "statusBar"; - this.statusBar.Size = new System.Drawing.Size(1438, 25); + this.statusBar.Size = new System.Drawing.Size(1438, 26); this.statusBar.TabIndex = 5; // - // txt_listing_time - // - this.txt_listing_time.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.txt_listing_time.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.txt_listing_time.Font = new System.Drawing.Font("微软雅黑", 10F); - this.txt_listing_time.Location = new System.Drawing.Point(888, 73); - this.txt_listing_time.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); - this.txt_listing_time.Name = "txt_listing_time"; - this.txt_listing_time.Size = new System.Drawing.Size(101, 25); - this.txt_listing_time.TabIndex = 984; - this.txt_listing_time.Text = "365"; - // // WXUserCollectAuthProdListForm // this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; @@ -1764,6 +1802,7 @@ namespace TemuKJD.WXForms.ProductForms private System.Windows.Forms.Label label8; private System.Windows.Forms.ComboBox cmbHasFission; private DevComponents.DotNetBar.PanelEx btnAITitleFission; + private System.Windows.Forms.DataGridViewTextBoxColumn colDetectionResult; private System.Windows.Forms.DataGridViewCheckBoxColumn colSelect; private System.Windows.Forms.DataGridViewTextBoxColumn col_db_id; private System.Windows.Forms.DataGridViewTextBoxColumn colFromShopId; @@ -1801,17 +1840,18 @@ namespace TemuKJD.WXForms.ProductForms private System.Windows.Forms.DataGridViewLinkColumn col_delete; private System.Windows.Forms.DataGridViewLinkColumn col_select_product; private DevComponents.DotNetBar.PanelEx btnBatchEditNotes; - private System.Windows.Forms.DateTimePicker dtpStart; - private System.Windows.Forms.DateTimePicker dtpEnd; - private UserComponent.UcCheckBox chkTime; - private System.Windows.Forms.Label label9; private DevComponents.DotNetBar.PanelEx btnEditNotes; public System.Windows.Forms.ComboBox cmbNotes; public System.Windows.Forms.ComboBox cmbBatchEditNotes; private DevComponents.DotNetBar.PanelEx btnBatchEditCate; private DevComponents.DotNetBar.PanelEx btn_import_wx; - private System.Windows.Forms.Label label10; private System.Windows.Forms.ToolTip toolTip1; + private System.Windows.Forms.Label label9; + private UserComponent.UcCheckBox chkTime; + private System.Windows.Forms.DateTimePicker dtpStart; + private System.Windows.Forms.DateTimePicker dtpEnd; + private System.Windows.Forms.Label label10; private System.Windows.Forms.TextBox txt_listing_time; + private DevComponents.DotNetBar.PanelEx btnSafeDetection; } } \ No newline at end of file diff --git a/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.cs b/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.cs index 4eb93c9..d8bb012 100644 --- a/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.cs +++ b/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.cs @@ -28,12 +28,14 @@ using TemuKJD.Extension; using TemuKJD.PDDForms.ProductSelectForms; using TemuKJD.ToolForms; using Work; +using Work.Services; namespace TemuKJD.WXForms.ProductForms { public partial class WXUserCollectAuthProdListForm : BaseGeneralForm { ShopLoginInfo shop = null; + private CancellationTokenSource detection; public CancellationTokenSource cancellationTokenSource { get; set; } = new CancellationTokenSource(); ConcurrentList lstTemuDbProduct = new ConcurrentList(); @@ -42,11 +44,13 @@ namespace TemuKJD.WXForms.ProductForms public WXUserCollectAuthProdListForm() { InitializeComponent(); + Disposed += (sender, e) => detection?.Cancel(); } public WXUserCollectAuthProdListForm(ShopLoginInfo _shop) { InitializeComponent(); + Disposed += (sender, e) => detection?.Cancel(); shop = _shop; } @@ -275,7 +279,8 @@ namespace TemuKJD.WXForms.ProductForms prod.mdy_time.ToString("yy-MM-dd HH:mm"), "编辑", "删除", - "认领" + "认领", + "未检测" ); dgvDbProduct.Rows[rowIndex].Height = dgvDbProduct.Columns["colMainImg"].Width; @@ -981,6 +986,7 @@ namespace TemuKJD.WXForms.ProductForms private void WXUserProdListForm_FormClosing(object sender, FormClosingEventArgs e) { + detection?.Cancel(); this.Hide(); e.Cancel = true; } @@ -1275,6 +1281,72 @@ namespace TemuKJD.WXForms.ProductForms SearchDbProd(1); } + private async void btnSafeDetection_Click(object sender, EventArgs e) + { + if (detection != null) + { + detection.Cancel(); + btnSafeDetection.Text = "正在停止"; + btnSafeDetection.Enabled = false; + return; + } + if (pbLoading.Visible || !btnSelect.Enabled || !btn_import_wx.Enabled) + { + BaseMsgForm.ShowInfo("请等待当前操作完成后再检测。"); + return; + } + var rows = dgvDbProduct.Rows.Cast() + .Where(row => row.Cells["colSelect"].Value.ParseToBool()).ToList(); + var ids = rows.Select(row => row.Cells["col_db_id"].Value.ParseToInt()).ToList(); + var selected = lstTemuDbProduct.Where(prod => ids.Contains(prod.id)) + .Select(prod => new ComplianceGoods + { + id = prod.id.ToString(), + name = prod.product_name, + image = prod.main_img, + type = ComplianceGoods.Library + }).ToList(); + if (selected.Count == 0) + { + BaseMsgForm.ShowInfo("请选择商品"); + return; + } + BaseMsgForm.ShowInfo($"当前AI检测剩余:{BaseClass.ImageAnalysisQuota}张"); + var locked = splitContainer1.Panel1.Controls.Cast().Where(c => c != btnSafeDetection) + .Concat(new Control[] { splitContainer2 }).Where(c => c.Enabled).ToList(); + var originalText = btnSafeDetection.Text; + detection = new CancellationTokenSource(); + try + { + foreach (var control in locked) control.Enabled = false; + btnSafeDetection.Text = "停止检测"; + var result = await GoodsComplianceService.DetectSelectedAsync(selected, ComplianceProductReader.Read, (id, status) => + { + if (IsDisposed) return; + foreach (var row in rows.Where(r => r.Cells["col_db_id"].Value.ParseToString() == id)) + row.Cells["colDetectionResult"].Value = status; + }, detection.Token); + if (!IsDisposed) + { + var title = result.Unfinished > 0 ? "AI 检测已停止" : "AI 检测完成"; + BaseMsgForm.ShowInfo($"{title}:安全 {result.Safe},有风险 {result.Risk},失败 {result.Failed},未完成 {result.Unfinished}。"); + } + } + catch (Exception ex) { if (!IsDisposed) MessageBox.Show(this, ex.Message); } + finally + { + detection.Dispose(); + detection = null; + if (!IsDisposed) + { + foreach (var control in locked) control.Enabled = true; + btnSafeDetection.Text = originalText; + btnSafeDetection.Enabled = true; + } + } + } + + private void btnAITitleFission_Click(object sender, EventArgs e) { var lstProdId = dgvDbProduct.Rows.Cast().Where(row @@ -1375,6 +1447,5 @@ namespace TemuKJD.WXForms.ProductForms SearchDbProd(this.ucSplitPage1.CurrentPage); } - } } diff --git a/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.resx b/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.resx index 90af2bd..d683021 100644 --- a/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.resx +++ b/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 17, 17 - True @@ -228,6 +225,9 @@ True + + 17, 17 + diff --git a/Work/Services/GoodsComplianceService.cs b/Work/Services/GoodsComplianceService.cs index 5a675cf..09dad1f 100644 --- a/Work/Services/GoodsComplianceService.cs +++ b/Work/Services/GoodsComplianceService.cs @@ -77,16 +77,46 @@ public static class GoodsComplianceService SavePending(path, pending); var batch = new ComplianceBatchResult { Unfinished = selected.Count }; - foreach (var item in selected) + foreach (ComplianceGoods item in selected) { if (cancellationToken.IsCancellationRequested) break; update(item.id, "读取详情中"); ComplianceResult result; try { + //item.type var product = await Task.Run(() => readProduct(item), cancellationToken); result = await DetectProductAsync(product, options, words, number => update(item.id, $"正在检测第 {number} 张图片"), cancellationToken); + if (result.Error == null) + { + try + { + if (item.type == ComplianceGoods.Library) + { + var url = BaseClass.BuildPDDObjString("product/SaveProductSafety"); + var data = UserLogin.GetSignature(); + data += "&user_name=" + BaseClass.UserName; + data += "&id=" + item.id; + string html = await GetHtml.PostJsonAsync(url, data); + + + } + else + { + await StoreApiClient.Instance.CloudGoods.SaveProductSafety(new SaveProductSafetyRequest + { + UserName = GetUserName(), + ProductId = product.ProductId, + IsSafe = result.Hits.Length == 0 + }); + } + } + catch (Exception ex) + { + result.Error = $"检测结果为{result.Status},上报失败:{ex.Message}"; + } + } } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { @@ -149,7 +179,24 @@ public static class GoodsComplianceService foreach (var product in products) { started?.Invoke(product); - completed(product, await DetectProductAsync(product, options, words)); + var result = await DetectProductAsync(product, options, words); + if (result.Error == null) + { + try + { + await StoreApiClient.Instance.CloudGoods.SaveProductSafety(new SaveProductSafetyRequest + { + UserName = GetUserName(), + ProductId = product.ProductId, + IsSafe = result.Hits.Length == 0 + }); + } + catch (Exception ex) + { + result.Error = $"检测结果为{result.Status},上报失败:{ex.Message}"; + } + } + completed(product, result); } } @@ -185,6 +232,9 @@ public static class GoodsComplianceService var jpeg = await DownloadInspectionImageAsync(url); cancellationToken.ThrowIfCancellationRequested(); var lines = await StoreApiClient.Instance.CloudGoods.RecognizeAsync(BaseClass.GetMd5(url), jpeg); + + BaseClass.ImageAnalysisQuota -= 1; + var imageText = string.Join(Environment.NewLine, lines); texts.AddRange(lines); result.Hits = Matches(imageText, words); @@ -202,19 +252,7 @@ public static class GoodsComplianceService cancellationToken.ThrowIfCancellationRequested(); if (imageNumber == 0) throw new InvalidDataException("所选检测范围内没有图片。"); result.Text = string.Join(Environment.NewLine, texts); - try - { - await StoreApiClient.Instance.CloudGoods.SaveProductSafety(new SaveProductSafetyRequest - { - UserName = GetUserName(), - ProductId = product.ProductId, - IsSafe = result.Hits.Length == 0 - }); - } - catch (Exception ex) - { - result.Error = $"检测结果为{result.Status},上报失败:{ex.Message}"; - } + } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; } catch (Exception ex) { result.Error = ex.Message; } diff --git a/Work/UserLogin.cs b/Work/UserLogin.cs index 5504f3b..224513b 100644 --- a/Work/UserLogin.cs +++ b/Work/UserLogin.cs @@ -101,6 +101,8 @@ namespace Work BaseClass.OemId = BaseRegex.GetStringJson(ref html, "oem_id"); BaseClass.OemName = BaseRegex.GetStringJson(ref html, "oem_name"); BaseClass.IsOemOwner = BaseRegex.GetBoolJson(ref html, "is_oem_owner"); + + BaseClass.ImageAnalysisQuota = Math.Max(0, BaseRegex.GetIntJson(ref html, "image_analysis_quota")); #if DEBUG if (!string.IsNullOrEmpty(BaseRegex.GetStringJson(ref html, "prod_api_url")) && !BaseClass.LoginApiUrl.Contains("localhost") && !BaseClass.LoginApiUrl.Contains("127.0.0.1")) { From be76e62ddd65ed502b3d335c93ecb33189eeb411 Mon Sep 17 00:00:00 2001 From: chfei Date: Mon, 9 Nov 2026 10:43:26 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E6=88=91=E7=9A=84=E5=95=86=E5=93=81?= =?UTF-8?q?=E5=BA=93=20AI=E8=AF=86=E5=88=AB=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AllBase/Model/Result/KJDDBProduct.cs | 4 +- .../WXUserCollectAuthProdListForm.Designer.cs | 232 ++++++++++-------- .../WXUserCollectAuthProdListForm.cs | 44 ++-- 3 files changed, 163 insertions(+), 117 deletions(-) diff --git a/AllBase/Model/Result/KJDDBProduct.cs b/AllBase/Model/Result/KJDDBProduct.cs index d1f737c..6e46ea0 100644 --- a/AllBase/Model/Result/KJDDBProduct.cs +++ b/AllBase/Model/Result/KJDDBProduct.cs @@ -118,5 +118,7 @@ namespace AllBase.Model.Result public string has_wx_format { get; set; } public string add_user_name { get; set; } public int? is_del { get; set; } - } + public bool? is_safe { get; set; } + + } } diff --git a/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.Designer.cs b/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.Designer.cs index f9fa647..1828a2d 100644 --- a/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.Designer.cs +++ b/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.Designer.cs @@ -66,6 +66,8 @@ namespace TemuKJD.WXForms.ProductForms this.btnGetProdWxCate = new DevComponents.DotNetBar.PanelEx(); this.btnDel = new DevComponents.DotNetBar.PanelEx(); this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.cb_safe = new System.Windows.Forms.ComboBox(); + this.label11 = new System.Windows.Forms.Label(); this.cmbNotes = new System.Windows.Forms.ComboBox(); this.label9 = new System.Windows.Forms.Label(); this.cmbHasFission = new System.Windows.Forms.ComboBox(); @@ -94,9 +96,9 @@ namespace TemuKJD.WXForms.ProductForms this.chkTime = new TemuKJD.UserComponent.UcCheckBox(this.components); this.dtpStart = new System.Windows.Forms.DateTimePicker(); this.dtpEnd = new System.Windows.Forms.DateTimePicker(); - this.label10 = new System.Windows.Forms.Label(); - this.txt_listing_time = new System.Windows.Forms.TextBox(); this.btnSafeDetection = new DevComponents.DotNetBar.PanelEx(); + this.txt_listing_time = new System.Windows.Forms.TextBox(); + this.label10 = new System.Windows.Forms.Label(); this.label7 = new System.Windows.Forms.Label(); this.splitContainer2 = new System.Windows.Forms.SplitContainer(); this.lblRelation = new System.Windows.Forms.Label(); @@ -149,12 +151,12 @@ namespace TemuKJD.WXForms.ProductForms this.colGetType = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colSpProdType = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colWxSafe = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colDetectionResult = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colAddTime = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colMdyTime = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.col_edit = new System.Windows.Forms.DataGridViewLinkColumn(); this.col_delete = new System.Windows.Forms.DataGridViewLinkColumn(); this.col_select_product = new System.Windows.Forms.DataGridViewLinkColumn(); - this.colDetectionResult = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.ucSplitPage1 = new TemuKJD.UserControls.UcSplitPage(); this.statusBar = new TemuKJD.UserControls.StatusBarUserControl(); this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); @@ -583,6 +585,8 @@ namespace TemuKJD.WXForms.ProductForms this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel1.Controls.Add(this.cb_safe, 5, 2); + this.tableLayoutPanel1.Controls.Add(this.label11, 4, 2); this.tableLayoutPanel1.Controls.Add(this.cmbNotes, 7, 0); this.tableLayoutPanel1.Controls.Add(this.label9, 2, 2); this.tableLayoutPanel1.Controls.Add(this.cmbHasFission, 11, 1); @@ -611,9 +615,9 @@ namespace TemuKJD.WXForms.ProductForms this.tableLayoutPanel1.Controls.Add(this.chkTime, 0, 2); this.tableLayoutPanel1.Controls.Add(this.dtpStart, 1, 2); this.tableLayoutPanel1.Controls.Add(this.dtpEnd, 3, 2); - this.tableLayoutPanel1.Controls.Add(this.label10, 10, 2); - this.tableLayoutPanel1.Controls.Add(this.txt_listing_time, 11, 2); - this.tableLayoutPanel1.Controls.Add(this.btnSafeDetection, 5, 2); + this.tableLayoutPanel1.Controls.Add(this.btnSafeDetection, 11, 2); + this.tableLayoutPanel1.Controls.Add(this.txt_listing_time, 7, 2); + this.tableLayoutPanel1.Controls.Add(this.label10, 6, 2); this.tableLayoutPanel1.Location = new System.Drawing.Point(4, 6); this.tableLayoutPanel1.Name = "tableLayoutPanel1"; this.tableLayoutPanel1.RowCount = 3; @@ -623,12 +627,40 @@ namespace TemuKJD.WXForms.ProductForms this.tableLayoutPanel1.Size = new System.Drawing.Size(992, 105); this.tableLayoutPanel1.TabIndex = 679; // + // cb_safe + // + this.cb_safe.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.cb_safe.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cb_safe.FormattingEnabled = true; + this.cb_safe.Items.AddRange(new object[] { + "全部", + "安全", + "违规", + "未检测"}); + this.cb_safe.Location = new System.Drawing.Point(386, 73); + this.cb_safe.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); + this.cb_safe.Name = "cb_safe"; + this.cb_safe.Size = new System.Drawing.Size(99, 25); + this.cb_safe.TabIndex = 1201; + // + // label11 + // + this.label11.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.label11.AutoSize = true; + this.label11.Location = new System.Drawing.Point(342, 77); + this.label11.Margin = new System.Windows.Forms.Padding(3, 7, 0, 0); + this.label11.Name = "label11"; + this.label11.Size = new System.Drawing.Size(44, 17); + this.label11.TabIndex = 1199; + this.label11.Text = "AI检测"; + // // cmbNotes // this.cmbNotes.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.cmbNotes.FormattingEnabled = true; - this.cmbNotes.Location = new System.Drawing.Point(554, 3); + this.cmbNotes.Location = new System.Drawing.Point(555, 3); this.cmbNotes.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.cmbNotes.Name = "cmbNotes"; this.cmbNotes.Size = new System.Drawing.Size(99, 25); @@ -654,17 +686,17 @@ namespace TemuKJD.WXForms.ProductForms "", "是", "否"}); - this.cmbHasFission.Location = new System.Drawing.Point(888, 38); + this.cmbHasFission.Location = new System.Drawing.Point(889, 38); this.cmbHasFission.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.cmbHasFission.Name = "cmbHasFission"; - this.cmbHasFission.Size = new System.Drawing.Size(101, 25); + this.cmbHasFission.Size = new System.Drawing.Size(100, 25); this.cmbHasFission.TabIndex = 982; // // label8 // this.label8.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.label8.AutoSize = true; - this.label8.Location = new System.Drawing.Point(832, 42); + this.label8.Location = new System.Drawing.Point(833, 42); this.label8.Margin = new System.Windows.Forms.Padding(3, 7, 0, 0); this.label8.Name = "label8"; this.label8.Size = new System.Drawing.Size(56, 17); @@ -677,7 +709,7 @@ namespace TemuKJD.WXForms.ProductForms | System.Windows.Forms.AnchorStyles.Right))); this.txtCatBoundLessThan.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.txtCatBoundLessThan.Font = new System.Drawing.Font("微软雅黑", 10F); - this.txtCatBoundLessThan.Location = new System.Drawing.Point(723, 38); + this.txtCatBoundLessThan.Location = new System.Drawing.Point(724, 38); this.txtCatBoundLessThan.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.txtCatBoundLessThan.Name = "txtCatBoundLessThan"; this.txtCatBoundLessThan.Size = new System.Drawing.Size(99, 25); @@ -687,7 +719,7 @@ namespace TemuKJD.WXForms.ProductForms // chkCatBoundLessThan // this.chkCatBoundLessThan.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.chkCatBoundLessThan.Location = new System.Drawing.Point(659, 40); + this.chkCatBoundLessThan.Location = new System.Drawing.Point(660, 40); this.chkCatBoundLessThan.Margin = new System.Windows.Forms.Padding(3, 5, 0, 0); this.chkCatBoundLessThan.Name = "chkCatBoundLessThan"; this.chkCatBoundLessThan.Size = new System.Drawing.Size(64, 21); @@ -777,7 +809,7 @@ namespace TemuKJD.WXForms.ProductForms | System.Windows.Forms.AnchorStyles.Right))); this.txtCatBoundMoreThan.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.txtCatBoundMoreThan.Font = new System.Drawing.Font("微软雅黑", 10F); - this.txtCatBoundMoreThan.Location = new System.Drawing.Point(554, 38); + this.txtCatBoundMoreThan.Location = new System.Drawing.Point(555, 38); this.txtCatBoundMoreThan.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.txtCatBoundMoreThan.Name = "txtCatBoundMoreThan"; this.txtCatBoundMoreThan.Size = new System.Drawing.Size(99, 25); @@ -787,7 +819,7 @@ namespace TemuKJD.WXForms.ProductForms // chkCatBoundMoreThan // this.chkCatBoundMoreThan.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.chkCatBoundMoreThan.Location = new System.Drawing.Point(490, 40); + this.chkCatBoundMoreThan.Location = new System.Drawing.Point(491, 40); this.chkCatBoundMoreThan.Margin = new System.Windows.Forms.Padding(3, 5, 0, 0); this.chkCatBoundMoreThan.Name = "chkCatBoundMoreThan"; this.chkCatBoundMoreThan.Size = new System.Drawing.Size(64, 21); @@ -829,7 +861,7 @@ namespace TemuKJD.WXForms.ProductForms // this.label17.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.label17.AutoSize = true; - this.label17.Location = new System.Drawing.Point(342, 42); + this.label17.Location = new System.Drawing.Point(343, 42); this.label17.Margin = new System.Windows.Forms.Padding(3, 7, 0, 0); this.label17.Name = "label17"; this.label17.Size = new System.Drawing.Size(43, 17); @@ -845,7 +877,7 @@ namespace TemuKJD.WXForms.ProductForms this.cmbCat3.DropDownWidth = 1; this.cmbCat3.FormattingEnabled = true; this.cmbCat3.IntegralHeight = false; - this.cmbCat3.Location = new System.Drawing.Point(385, 38); + this.cmbCat3.Location = new System.Drawing.Point(386, 38); this.cmbCat3.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.cmbCat3.Name = "cmbCat3"; this.cmbCat3.Size = new System.Drawing.Size(99, 25); @@ -858,7 +890,7 @@ namespace TemuKJD.WXForms.ProductForms // this.lblSourceType.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.lblSourceType.AutoSize = true; - this.lblSourceType.Location = new System.Drawing.Point(353, 7); + this.lblSourceType.Location = new System.Drawing.Point(354, 7); this.lblSourceType.Margin = new System.Windows.Forms.Padding(3, 7, 0, 0); this.lblSourceType.Name = "lblSourceType"; this.lblSourceType.Size = new System.Drawing.Size(32, 17); @@ -875,7 +907,7 @@ namespace TemuKJD.WXForms.ProductForms "多多商品", "1688商品", "微信商品"}); - this.cmbSourceType.Location = new System.Drawing.Point(385, 3); + this.cmbSourceType.Location = new System.Drawing.Point(386, 3); this.cmbSourceType.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.cmbSourceType.Name = "cmbSourceType"; this.cmbSourceType.Size = new System.Drawing.Size(99, 25); @@ -887,7 +919,7 @@ namespace TemuKJD.WXForms.ProductForms // this.lblNotes.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.lblNotes.AutoSize = true; - this.lblNotes.Location = new System.Drawing.Point(518, 7); + this.lblNotes.Location = new System.Drawing.Point(519, 7); this.lblNotes.Margin = new System.Windows.Forms.Padding(3, 7, 0, 0); this.lblNotes.Name = "lblNotes"; this.lblNotes.Size = new System.Drawing.Size(36, 17); @@ -898,7 +930,7 @@ namespace TemuKJD.WXForms.ProductForms // this.label12.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.label12.AutoSize = true; - this.label12.Location = new System.Drawing.Point(663, 7); + this.label12.Location = new System.Drawing.Point(664, 7); this.label12.Margin = new System.Windows.Forms.Padding(3, 7, 0, 0); this.label12.Name = "label12"; this.label12.Size = new System.Drawing.Size(60, 17); @@ -914,7 +946,7 @@ namespace TemuKJD.WXForms.ProductForms "", "是", "否"}); - this.cmbWxCat.Location = new System.Drawing.Point(723, 3); + this.cmbWxCat.Location = new System.Drawing.Point(724, 3); this.cmbWxCat.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.cmbWxCat.Name = "cmbWxCat"; this.cmbWxCat.Size = new System.Drawing.Size(99, 25); @@ -924,7 +956,7 @@ namespace TemuKJD.WXForms.ProductForms // this.lblWxSafe.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.lblWxSafe.AutoSize = true; - this.lblWxSafe.Location = new System.Drawing.Point(828, 7); + this.lblWxSafe.Location = new System.Drawing.Point(829, 7); this.lblWxSafe.Margin = new System.Windows.Forms.Padding(3, 7, 0, 0); this.lblWxSafe.Name = "lblWxSafe"; this.lblWxSafe.Size = new System.Drawing.Size(60, 17); @@ -941,10 +973,10 @@ namespace TemuKJD.WXForms.ProductForms "安全+未校验", "安全商品", "未校验"}); - this.cmbWxSafe.Location = new System.Drawing.Point(888, 3); + this.cmbWxSafe.Location = new System.Drawing.Point(889, 3); this.cmbWxSafe.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.cmbWxSafe.Name = "cmbWxSafe"; - this.cmbWxSafe.Size = new System.Drawing.Size(101, 25); + this.cmbWxSafe.Size = new System.Drawing.Size(100, 25); this.cmbWxSafe.TabIndex = 676; this.cmbWxSafe.Text = "安全+未校验"; // @@ -980,30 +1012,6 @@ namespace TemuKJD.WXForms.ProductForms this.dtpEnd.Size = new System.Drawing.Size(99, 23); this.dtpEnd.TabIndex = 982; // - // label10 - // - this.label10.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.label10.AutoSize = true; - this.label10.Location = new System.Drawing.Point(832, 77); - this.label10.Margin = new System.Windows.Forms.Padding(3, 7, 0, 0); - this.label10.Name = "label10"; - this.label10.Size = new System.Drawing.Size(56, 17); - this.label10.TabIndex = 983; - this.label10.Text = "上架日期"; - // - // txt_listing_time - // - this.txt_listing_time.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.txt_listing_time.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.txt_listing_time.Font = new System.Drawing.Font("微软雅黑", 10F); - this.txt_listing_time.Location = new System.Drawing.Point(888, 73); - this.txt_listing_time.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); - this.txt_listing_time.Name = "txt_listing_time"; - this.txt_listing_time.Size = new System.Drawing.Size(101, 25); - this.txt_listing_time.TabIndex = 984; - this.txt_listing_time.Text = "365"; - // // btnSafeDetection // this.btnSafeDetection.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); @@ -1011,7 +1019,7 @@ namespace TemuKJD.WXForms.ProductForms this.btnSafeDetection.Cursor = System.Windows.Forms.Cursors.Hand; this.btnSafeDetection.DisabledBackColor = System.Drawing.Color.Empty; this.btnSafeDetection.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnSafeDetection.Location = new System.Drawing.Point(388, 73); + this.btnSafeDetection.Location = new System.Drawing.Point(893, 73); this.btnSafeDetection.Name = "btnSafeDetection"; this.btnSafeDetection.Size = new System.Drawing.Size(96, 25); this.btnSafeDetection.Style.Alignment = System.Drawing.StringAlignment.Center; @@ -1025,10 +1033,34 @@ namespace TemuKJD.WXForms.ProductForms this.btnSafeDetection.StyleMouseOver.Alignment = System.Drawing.StringAlignment.Center; this.btnSafeDetection.StyleMouseOver.BackColor1.Color = System.Drawing.Color.FromArgb(((int)(((byte)(25)))), ((int)(((byte)(102)))), ((int)(((byte)(255))))); this.btnSafeDetection.StyleMouseOver.BackColor2.Color = System.Drawing.Color.FromArgb(((int)(((byte)(25)))), ((int)(((byte)(102)))), ((int)(((byte)(255))))); - this.btnSafeDetection.TabIndex = 1198; + this.btnSafeDetection.TabIndex = 1202; this.btnSafeDetection.Text = "AI检测"; this.btnSafeDetection.Click += new System.EventHandler(this.btnSafeDetection_Click); // + // txt_listing_time + // + this.txt_listing_time.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txt_listing_time.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.txt_listing_time.Font = new System.Drawing.Font("微软雅黑", 10F); + this.txt_listing_time.Location = new System.Drawing.Point(555, 73); + this.txt_listing_time.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); + this.txt_listing_time.Name = "txt_listing_time"; + this.txt_listing_time.Size = new System.Drawing.Size(99, 25); + this.txt_listing_time.TabIndex = 984; + this.txt_listing_time.Text = "365"; + // + // label10 + // + this.label10.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.label10.AutoSize = true; + this.label10.Location = new System.Drawing.Point(499, 77); + this.label10.Margin = new System.Windows.Forms.Padding(3, 7, 0, 0); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(56, 17); + this.label10.TabIndex = 983; + this.label10.Text = "上架日期"; + // // label7 // this.label7.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); @@ -1346,12 +1378,12 @@ namespace TemuKJD.WXForms.ProductForms this.colGetType, this.colSpProdType, this.colWxSafe, + this.colDetectionResult, this.colAddTime, this.colMdyTime, this.col_edit, this.col_delete, - this.col_select_product, - this.colDetectionResult}); + this.col_select_product}); this.dgvDbProduct.Dock = System.Windows.Forms.DockStyle.Fill; this.dgvDbProduct.Location = new System.Drawing.Point(4, 0); this.dgvDbProduct.MultiSelectCheckBox = true; @@ -1623,6 +1655,16 @@ namespace TemuKJD.WXForms.ProductForms this.colWxSafe.Name = "colWxSafe"; this.colWxSafe.ReadOnly = true; // + // colDetectionResult + // + this.colDetectionResult.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; + this.colDetectionResult.HeaderText = "AI检测结果"; + this.colDetectionResult.MinimumWidth = 50; + this.colDetectionResult.Name = "colDetectionResult"; + this.colDetectionResult.ReadOnly = true; + this.colDetectionResult.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + this.colDetectionResult.Width = 160; + // // colAddTime // dataGridViewCellStyle33.WrapMode = System.Windows.Forms.DataGridViewTriState.True; @@ -1668,16 +1710,6 @@ namespace TemuKJD.WXForms.ProductForms this.col_select_product.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic; this.col_select_product.Visible = false; // - // colDetectionResult - // - this.colDetectionResult.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; - this.colDetectionResult.HeaderText = "AI检测结果"; - this.colDetectionResult.MinimumWidth = 50; - this.colDetectionResult.Name = "colDetectionResult"; - this.colDetectionResult.ReadOnly = true; - this.colDetectionResult.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; - this.colDetectionResult.Width = 160; - // // ucSplitPage1 // this.ucSplitPage1.CurrentPage = 1; @@ -1802,43 +1834,6 @@ namespace TemuKJD.WXForms.ProductForms private System.Windows.Forms.Label label8; private System.Windows.Forms.ComboBox cmbHasFission; private DevComponents.DotNetBar.PanelEx btnAITitleFission; - private System.Windows.Forms.DataGridViewTextBoxColumn colDetectionResult; - private System.Windows.Forms.DataGridViewCheckBoxColumn colSelect; - private System.Windows.Forms.DataGridViewTextBoxColumn col_db_id; - private System.Windows.Forms.DataGridViewTextBoxColumn colFromShopId; - private System.Windows.Forms.DataGridViewTextBoxColumn colFromShopName; - private System.Windows.Forms.DataGridViewTextBoxColumn col_product_name; - private System.Windows.Forms.DataGridViewTextBoxColumn colImgUrl; - private System.Windows.Forms.DataGridViewImageColumn colMainImg; - private System.Windows.Forms.DataGridViewTextBoxColumn colSkuReplaceImgUrl; - private System.Windows.Forms.DataGridViewImageColumn colSkuReplaceImg; - private System.Windows.Forms.DataGridViewTextBoxColumn col_spu_id; - private System.Windows.Forms.DataGridViewTextBoxColumn colSpuIdShow; - private System.Windows.Forms.DataGridViewTextBoxColumn col_skc_id; - private System.Windows.Forms.DataGridViewTextBoxColumn colMainCate; - private System.Windows.Forms.DataGridViewTextBoxColumn col_cat_name; - private System.Windows.Forms.DataGridViewTextBoxColumn colCatBound; - private System.Windows.Forms.DataGridViewTextBoxColumn col_ext_code; - private System.Windows.Forms.DataGridViewTextBoxColumn colStyleCode; - private System.Windows.Forms.DataGridViewTextBoxColumn colGroup; - private System.Windows.Forms.DataGridViewTextBoxColumn colPriceText; - private System.Windows.Forms.DataGridViewTextBoxColumn colExtCodePrice; - private System.Windows.Forms.DataGridViewLinkColumn colEdit; - private System.Windows.Forms.DataGridViewTextBoxColumn colAddTimes; - private System.Windows.Forms.DataGridViewTextBoxColumn colSelectTotalCount; - private System.Windows.Forms.DataGridViewTextBoxColumn colSaleCount; - private System.Windows.Forms.DataGridViewTextBoxColumn col_notes; - private System.Windows.Forms.DataGridViewTextBoxColumn colHeadWords; - private System.Windows.Forms.DataGridViewTextBoxColumn colEndWords; - private System.Windows.Forms.DataGridViewTextBoxColumn colFissionWords; - private System.Windows.Forms.DataGridViewTextBoxColumn colGetType; - private System.Windows.Forms.DataGridViewTextBoxColumn colSpProdType; - private System.Windows.Forms.DataGridViewTextBoxColumn colWxSafe; - private System.Windows.Forms.DataGridViewTextBoxColumn colAddTime; - private System.Windows.Forms.DataGridViewTextBoxColumn colMdyTime; - private System.Windows.Forms.DataGridViewLinkColumn col_edit; - private System.Windows.Forms.DataGridViewLinkColumn col_delete; - private System.Windows.Forms.DataGridViewLinkColumn col_select_product; private DevComponents.DotNetBar.PanelEx btnBatchEditNotes; private DevComponents.DotNetBar.PanelEx btnEditNotes; public System.Windows.Forms.ComboBox cmbNotes; @@ -1852,6 +1847,45 @@ namespace TemuKJD.WXForms.ProductForms private System.Windows.Forms.DateTimePicker dtpEnd; private System.Windows.Forms.Label label10; private System.Windows.Forms.TextBox txt_listing_time; + private System.Windows.Forms.Label label11; + public System.Windows.Forms.ComboBox cb_safe; + private System.Windows.Forms.DataGridViewCheckBoxColumn colSelect; + private System.Windows.Forms.DataGridViewTextBoxColumn col_db_id; + private System.Windows.Forms.DataGridViewTextBoxColumn colFromShopId; + private System.Windows.Forms.DataGridViewTextBoxColumn colFromShopName; + private System.Windows.Forms.DataGridViewTextBoxColumn col_product_name; + private System.Windows.Forms.DataGridViewTextBoxColumn colImgUrl; + private System.Windows.Forms.DataGridViewImageColumn colMainImg; + private System.Windows.Forms.DataGridViewTextBoxColumn colSkuReplaceImgUrl; + private System.Windows.Forms.DataGridViewImageColumn colSkuReplaceImg; + private System.Windows.Forms.DataGridViewTextBoxColumn col_spu_id; + private System.Windows.Forms.DataGridViewTextBoxColumn colSpuIdShow; + private System.Windows.Forms.DataGridViewTextBoxColumn col_skc_id; + private System.Windows.Forms.DataGridViewTextBoxColumn colMainCate; + private System.Windows.Forms.DataGridViewTextBoxColumn col_cat_name; + private System.Windows.Forms.DataGridViewTextBoxColumn colCatBound; + private System.Windows.Forms.DataGridViewTextBoxColumn col_ext_code; + private System.Windows.Forms.DataGridViewTextBoxColumn colStyleCode; + private System.Windows.Forms.DataGridViewTextBoxColumn colGroup; + private System.Windows.Forms.DataGridViewTextBoxColumn colPriceText; + private System.Windows.Forms.DataGridViewTextBoxColumn colExtCodePrice; + private System.Windows.Forms.DataGridViewLinkColumn colEdit; + private System.Windows.Forms.DataGridViewTextBoxColumn colAddTimes; + private System.Windows.Forms.DataGridViewTextBoxColumn colSelectTotalCount; + private System.Windows.Forms.DataGridViewTextBoxColumn colSaleCount; + private System.Windows.Forms.DataGridViewTextBoxColumn col_notes; + private System.Windows.Forms.DataGridViewTextBoxColumn colHeadWords; + private System.Windows.Forms.DataGridViewTextBoxColumn colEndWords; + private System.Windows.Forms.DataGridViewTextBoxColumn colFissionWords; + private System.Windows.Forms.DataGridViewTextBoxColumn colGetType; + private System.Windows.Forms.DataGridViewTextBoxColumn colSpProdType; + private System.Windows.Forms.DataGridViewTextBoxColumn colWxSafe; + private System.Windows.Forms.DataGridViewTextBoxColumn colDetectionResult; + private System.Windows.Forms.DataGridViewTextBoxColumn colAddTime; + private System.Windows.Forms.DataGridViewTextBoxColumn colMdyTime; + private System.Windows.Forms.DataGridViewLinkColumn col_edit; + private System.Windows.Forms.DataGridViewLinkColumn col_delete; + private System.Windows.Forms.DataGridViewLinkColumn col_select_product; private DevComponents.DotNetBar.PanelEx btnSafeDetection; } } \ No newline at end of file diff --git a/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.cs b/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.cs index d8bb012..3e44e60 100644 --- a/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.cs +++ b/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.cs @@ -65,7 +65,7 @@ namespace TemuKJD.WXForms.ProductForms PDDDBProdListForm.GetPDDProductNotes(new List { cmbNotes }, addUserName); PDDDBProdListForm.GetPDDProductNotes(new List { cmbBatchEditNotes }, addUserName); }); - + cb_safe.SelectedIndex = 0; //chkCatBoundLessThan.Visible = false; //chkCatBoundMoreThan.Visible = false; //tableLayoutPanel1.SetColumn(lblSourceType, 2); @@ -275,6 +275,7 @@ namespace TemuKJD.WXForms.ProductForms .Replace("已采集数据", "1688商品").Replace("机会商品", "多多商品").Replace("多多外部数据", "多多商品").Replace("微信店采", "微信商品"), prod.special_prod_type, prod.wx_safe_status, + prod.is_safe == null ? "未检测" : prod.is_safe.Value ==true ? "安全" : "违规", prod.add_time.ToString("yy-MM-dd HH:mm"), prod.mdy_time.ToString("yy-MM-dd HH:mm"), "编辑", @@ -528,9 +529,9 @@ namespace TemuKJD.WXForms.ProductForms { data += "&no_fission_words=true"; } - + data += "&safe_status=" + cb_safe.SelectedIndex; if (isNotCount) - data += "&is_not_count=true"; + data += "&is_not_count=true"; })); var html = GetHtml.PostUtf8(url, data); if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) @@ -1287,7 +1288,6 @@ namespace TemuKJD.WXForms.ProductForms { detection.Cancel(); btnSafeDetection.Text = "正在停止"; - btnSafeDetection.Enabled = false; return; } if (pbLoading.Visible || !btnSelect.Enabled || !btn_import_wx.Enabled) @@ -1298,27 +1298,38 @@ namespace TemuKJD.WXForms.ProductForms var rows = dgvDbProduct.Rows.Cast() .Where(row => row.Cells["colSelect"].Value.ParseToBool()).ToList(); var ids = rows.Select(row => row.Cells["col_db_id"].Value.ParseToInt()).ToList(); - var selected = lstTemuDbProduct.Where(prod => ids.Contains(prod.id)) - .Select(prod => new ComplianceGoods - { - id = prod.id.ToString(), - name = prod.product_name, - image = prod.main_img, - type = ComplianceGoods.Library - }).ToList(); + var productDict = lstTemuDbProduct + .Where(p => ids.Contains(p.id)) + .ToDictionary(p => p.id); + + var selected = ids + .Where(id => productDict.ContainsKey(id)) + .Select(id => + { + var prod = productDict[id]; + return new ComplianceGoods + { + id = prod.id.ToString(), + name = prod.product_name, + image = prod.main_img, + type = ComplianceGoods.Library + }; + }) + .ToList(); + + if (selected.Count == 0) { BaseMsgForm.ShowInfo("请选择商品"); return; } BaseMsgForm.ShowInfo($"当前AI检测剩余:{BaseClass.ImageAnalysisQuota}张"); - var locked = splitContainer1.Panel1.Controls.Cast().Where(c => c != btnSafeDetection) - .Concat(new Control[] { splitContainer2 }).Where(c => c.Enabled).ToList(); + var originalText = btnSafeDetection.Text; detection = new CancellationTokenSource(); try { - foreach (var control in locked) control.Enabled = false; + btnSafeDetection.Text = "停止检测"; var result = await GoodsComplianceService.DetectSelectedAsync(selected, ComplianceProductReader.Read, (id, status) => { @@ -1339,9 +1350,8 @@ namespace TemuKJD.WXForms.ProductForms detection = null; if (!IsDisposed) { - foreach (var control in locked) control.Enabled = true; + btnSafeDetection.Text = originalText; - btnSafeDetection.Enabled = true; } } } From 3f11ae23bd37239e1b2fcab0f499a78d41e05f3a Mon Sep 17 00:00:00 2001 From: chfei Date: Mon, 9 Nov 2026 10:50:48 +0800 Subject: [PATCH 5/6] =?UTF-8?q?AI=E8=AF=86=E5=88=AB=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=A0=87=E9=A2=98=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Work/Services/GoodsComplianceService.cs | 9 ++++++++- test/GoodsComplianceSmoke.cs | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Work/Services/GoodsComplianceService.cs b/Work/Services/GoodsComplianceService.cs index 09dad1f..8f0b14b 100644 --- a/Work/Services/GoodsComplianceService.cs +++ b/Work/Services/GoodsComplianceService.cs @@ -37,7 +37,8 @@ public sealed class ComplianceResult public string Error { get; internal set; } public string Status => Error != null ? "失败,可重试" : Hits.Length == 0 ? "安全" : "有风险"; public string Summary => Error != null ? "失败:" + Error : - Hits.Length == 0 ? Status : $"有风险:第 {HitImageNumber} 张图片命中关键词:{string.Join(",", Hits)}"; + Hits.Length == 0 ? Status : $"有风险:{HitSource}命中关键词:{string.Join(",", Hits)}"; + private string HitSource => HitImageNumber == 0 ? "标题" : $"第 {HitImageNumber} 张图片"; } public sealed class ComplianceBatchResult @@ -217,6 +218,12 @@ public static class GoodsComplianceService { cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrWhiteSpace(product?.ProductId)) throw new InvalidDataException("缺少商品 ID。"); + result.Hits = Matches(product.Title ?? string.Empty, words); + if (result.Hits.Length > 0) + { + result.Text = product.Title; + return result; + } var urls = SelectImages(product.MainImages, options.MainCount) .Concat(SelectImages(product.DetailImages, options.DetailCount)); if (options.IncludeSku) urls = urls.Concat(SelectImages(product.Skus?.Select(s => s?.ImageUrl), null)); diff --git a/test/GoodsComplianceSmoke.cs b/test/GoodsComplianceSmoke.cs index f837b38..c36ef47 100644 --- a/test/GoodsComplianceSmoke.cs +++ b/test/GoodsComplianceSmoke.cs @@ -50,6 +50,15 @@ internal static class GoodsComplianceSmoke Check(words.Length == 5); Check(GoodsComplianceService.Matches("ABC广告", words).SequenceEqual(new[] { "广告", "abc" })); Check(GoodsComplianceService.Matches("普通商品", words).Length == 0); + // Exercise detection without calling the reporting API. + var detect = typeof(GoodsComplianceService).GetMethod("DetectProductAsync", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + var titleHit = ((System.Threading.Tasks.Task)detect.Invoke(null, new object[] + { + new Product { ProductId = "title", Title = "ABC广告" }, new ComplianceOptions(), words, null, + System.Threading.CancellationToken.None + })).GetAwaiter().GetResult(); + Check(titleHit.Status == "有风险" && titleHit.Summary == "有风险:标题命中关键词:广告,abc"); var invalidImage = new Product { ProductId = "bad-image", MainImages = new List { "file:///image.jpg" } }; var statuses = new List(); GoodsComplianceService.DetectSelectedAsync(new[] { cloud }, item => invalidImage, From 7c870e6c460675ae2a1444ed9700dadf3bccf192 Mon Sep 17 00:00:00 2001 From: chfei Date: Mon, 9 Nov 2026 14:23:57 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=E5=9B=BE=E7=89=87=E6=AC=A1=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WXUserCollectAuthProdListForm.cs | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.cs b/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.cs index 3e44e60..7e060d8 100644 --- a/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.cs +++ b/TEMUKJD/WXForms/ProductForms/WXUserCollectAuthProdListForm.cs @@ -1302,20 +1302,20 @@ namespace TemuKJD.WXForms.ProductForms .Where(p => ids.Contains(p.id)) .ToDictionary(p => p.id); - var selected = ids - .Where(id => productDict.ContainsKey(id)) - .Select(id => - { - var prod = productDict[id]; - return new ComplianceGoods - { - id = prod.id.ToString(), - name = prod.product_name, - image = prod.main_img, - type = ComplianceGoods.Library - }; - }) - .ToList(); + var selected = ids + .Where(id => productDict.ContainsKey(id)) + .Select(id => + { + var prod = productDict[id]; + return new ComplianceGoods + { + id = prod.id.ToString(), + name = prod.product_name, + image = prod.main_img, + type = ComplianceGoods.Library + }; + }) + .ToList(); if (selected.Count == 0) @@ -1323,7 +1323,15 @@ namespace TemuKJD.WXForms.ProductForms BaseMsgForm.ShowInfo("请选择商品"); return; } - BaseMsgForm.ShowInfo($"当前AI检测剩余:{BaseClass.ImageAnalysisQuota}张"); + if (BaseClass.ImageAnalysisQuota > 0) + { + BaseMsgForm.ShowInfo($"当前AI检测剩余:{BaseClass.ImageAnalysisQuota}张"); + } + else + { + BaseMsgForm.ShowInfo($"当前AI检测剩余:{BaseClass.ImageAnalysisQuota}张,请兑换"); + return; + } var originalText = btnSafeDetection.Text; detection = new CancellationTokenSource();