diff --git a/Admin.NET/Admin.NET.Application/Configuration/Wechat.json b/Admin.NET/Admin.NET.Application/Configuration/Wechat.json
index f591e84d..dc59db4e 100644
--- a/Admin.NET/Admin.NET.Application/Configuration/Wechat.json
+++ b/Admin.NET/Admin.NET.Application/Configuration/Wechat.json
@@ -1,4 +1,4 @@
-{
+{
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
"Wechat": {
@@ -16,16 +16,16 @@
},
// 微信支付
"WechatPay": {
- "AppId": "", // 微信公众平台AppId、开放平台AppId、小程序AppId、企业微信CorpId
- "MerchantId": "", // 商户平台的商户号
- "MerchantV3Secret": "", // 商户平台的APIv3密钥
- "MerchantCertificateSerialNumber": "", // 商户平台的证书序列号
+ "AppId": "wxaaaaaaaaaaaaaa85", // 微信公众平台AppId、开放平台AppId、小程序AppId、企业微信CorpId
+ "MerchantId": "1500000001", // 商户平台的商户号
+ "MerchantV3Secret": "3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaoo", // 商户平台的APIv3密钥
+ "MerchantCertificateSerialNumber": "66aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0", // 商户平台的证书序列号
"MerchantCertificatePrivateKey": "\\WxPayCert\\apiclient_key.pem" // 商户平台的API证书私钥(apiclient_key.pem文件内容)
},
// 支付回调
"PayCallBack": {
- "WechatPayUrl": "https://xxx/api/sysWechatPay/payCallBack", // 微信支付回调
- "WechatRefundUrl": "", // 微信退款回调
+ "WechatPayUrl": "https://ip/sysWechatPay/payCallBack", // 微信支付回调: https://ip/sysWechatPay/payCallBack
+ "WechatRefundUrl": "https://ip/api/sysWechatPay/refundCallBack", // 微信退款回调:https://ip/api/sysWechatPay/refundCallBack
"AlipayUrl": "", // 支付宝支付回调
"AlipayRefundUrl": "" // 支付宝退款回调
}
diff --git a/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj b/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj
index c4570d6a..588bad29 100644
--- a/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj
+++ b/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj
@@ -20,9 +20,9 @@
-
-
-
+
+
+
@@ -48,7 +48,7 @@
-
+
diff --git a/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs b/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs
index 575cebfe..2e21069f 100644
--- a/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs
+++ b/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs
@@ -136,6 +136,12 @@ public partial class SysCodeGenConfig : EntityBase
[SugarColumn(ColumnDescription = "是否是统计字段", Length = 8)]
[MaxLength(8)]
public string? Statistical { get; set; }
+ ///
+ /// 是否是GroupBy字段
+ ///
+ [SugarColumn(ColumnDescription = "是否是GroupBy字段", Length = 8)]
+ [MaxLength(8)]
+ public string? IsGroupBy { get; set; }
///
/// 是否是查询条件
diff --git a/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs b/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs
index 19b96531..e81f07cc 100644
--- a/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs
+++ b/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs
@@ -18,104 +18,115 @@ public class SysWechatRefund : EntityBase
/// 微信支付订单号(原支付交易对应的微信订单号)
///
[SugarColumn(ColumnDescription = "微信支付订单号", Length = 32)]
- [Required]
+ [Required]
public string TransactionId { get; set; }
///
/// 商户订单号(原交易对应的商户付款单号)
///
[SugarColumn(ColumnDescription = "商户付款单号", Length = 32)]
- [Required]
+ [Required]
public string OutTradeNumber { get; set; }
///
/// 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。
///
[SugarColumn(ColumnDescription = "商户退款单号", Length = 64)]
- [Required]
- public string OutRefundNo { get; set; }
+ [Required]
+ public string OutRefundNumber { get; set; }
+
+ ///
+ /// 微信接口退款ID
+ ///
+ public string RefundId { get; set; }
///
/// 退款原因,示例:商品已售完
///
- [SugarColumn(ColumnDescription = "退款原因", Length = 80)]
+ [SugarColumn(ColumnDescription = "退款原因", Length = 80)]
public string Reason { get; set; }
///
/// 退款金额
///
- [SugarColumn(ColumnDescription = "退款金额")]
+ [SugarColumn(ColumnDescription = "退款金额")]
public int Refund { get; set; }
///
/// 原订单总金额
///
- [SugarColumn(ColumnDescription = "订单总金额")]
+ [SugarColumn(ColumnDescription = "订单总金额")]
public int Total { get; set; }
///
/// 退款结果回调url
///
- [SugarColumn(ColumnDescription = "退款结果回调url", Length = 256)]
+ [SugarColumn(ColumnDescription = "退款结果回调url", Length = 256)]
public string? NotifyUrl { get; set; }
///
/// 退款资金来源, 可不传,默认使用未结算资金退款(仅对老资金流商户适用)
///
- [SugarColumn(ColumnDescription = "退款资金来源", Length = 32)]
+ [SugarColumn(ColumnDescription = "退款资金来源", Length = 32)]
public string? FundsAccount { get; set; }
///
/// 关联的商户订单号
///
- [SugarColumn(ColumnDescription = "关联的用户订单号", Length = 256)]
+ [SugarColumn(ColumnDescription = "关联的用户订单号", Length = 256)]
public string? OrderId { get; set; }
///
/// 关联的商户订单状态(或者为第几次支付,有些订单涉及多次支付,比如先付预付款,后补尾款)
///
- [SugarColumn(ColumnDescription = "关联的商户订单状态", Length = 32)]
- public string? OrderStatus { get; set; }
+ [SugarColumn(ColumnDescription = "关联的商户订单状态", Length = 32)]
+ public string? RefundStatus { get; set; }
+
+ ///
+ /// 支完成时间
+ ///
+ [SugarColumn(ColumnDescription = "完成时间")]
+ public DateTime? SuccessTime { get; set; }
///
/// 关联的商户商品编码
///
- [SugarColumn(ColumnDescription = "关联的商户商品编码", Length = 32)]
- public string MerchantGoodsId { get; set; }
+ [SugarColumn(ColumnDescription = "关联的商户商品编码", Length = 32)]
+ public string? MerchantGoodsId { get; set; }
///
/// 关联的商户商品名称
///
- [SugarColumn(ColumnDescription = "关联的商户商品名称", Length = 256)]
- public string GoodsName { get; set; }
+ [SugarColumn(ColumnDescription = "关联的商户商品名称", Length = 256)]
+ public string? GoodsName { get; set; }
///
/// 关联的商户商品单价
///
- [SugarColumn(ColumnDescription = "关联的商户商品单价")]
+ [SugarColumn(ColumnDescription = "关联的商户商品单价")]
public int UnitPrice { get; set; }
///
/// 关联的商户商品退款金额
///
- [SugarColumn(ColumnDescription = "关联的商户商品退款金额")]
+ [SugarColumn(ColumnDescription = "关联的商户商品退款金额")]
public int RefundAmount { get; set; }
///
/// 关联的商户商品退货数量
///
- [SugarColumn(ColumnDescription = "关联的商户商品退货数量")]
+ [SugarColumn(ColumnDescription = "关联的商户商品退货数量")]
public int RefundQuantity { get; set; } = 1;
///
/// 附加数据
///
- [SugarColumn(ColumnDescription = "附加数据")]
+ [SugarColumn(ColumnDescription = "附加数据")]
public string? Attachment { get; set; }
///
/// 备注
///
- [SugarColumn(ColumnDescription = "备注", ColumnDataType = StaticConfig.CodeFirst_BigString)]
+ [SugarColumn(ColumnDescription = "备注", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? Remark { get; set; }
}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs
index fa13ecd0..c3bb3b94 100644
--- a/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs
+++ b/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs
@@ -222,6 +222,23 @@ public class SysCodeGenTemplateSeedData : ISqlSugarEntitySeedData
public string Statistical { get; set; }
+ ///
+ /// 是否是GroupBy字段
+ ///
+ public string? IsGroupBy { get; set; }
///
/// 是否是查询条件
diff --git a/Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs b/Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs
index c6441b0f..b997bef9 100644
--- a/Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs
+++ b/Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs
@@ -116,7 +116,7 @@ public class SysOnlineUserService : IDynamicApiController, ITransient
[DisplayName("清理在线用户")]
public async Task ClearOnline()
{
- if (await _sysConfigService.GetConfigValueByCode(ConfigConst.SysSingleLogin)) return;
+ if (!await _sysConfigService.GetConfigValueByCode(ConfigConst.SysSingleLogin)) return;
// 相同账号最后登录的用户Id集合
var onlineUsers = await _sysOnlineUerRep.AsQueryable().GroupBy(u => u.UserId)
diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs
index b71954d7..84907694 100644
--- a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs
+++ b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs
@@ -140,13 +140,13 @@ public class PageSysWechatPayInput : BasePageInput
/// order_id
///
///
- public string? OrderId { get; set; } = "-1";
+ public string? OrderId { get; set; }
///
/// order_status
///
///
- public string? OrderStatus { get; set; } = "-1";
+ public string? OrderStatus { get; set; }
///
/// out_trade_number
diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs
index 4fa2f986..095abdf7 100644
--- a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs
+++ b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs
@@ -15,6 +15,20 @@ namespace Admin.NET.Core.Service;
[ApiDescriptionSettings(Order = 210, Description = "微信支付")]
public class SysWechatPayService : IDynamicApiController, ITransient
{
+ private static readonly List wechatPayEventHandlers = [new WechatPayEventInterceptor() { Order = int.MaxValue }];
+
+ ///
+ /// 注册支付记录变化事件处理器
+ ///
+ /// 处理器
+ /// 排序,数据越大越先执行
+ public static void AddPayEventInterceptor(WechatPayEventInterceptor eh, int order)
+ {
+ eh.Order = order;
+ wechatPayEventHandlers.Add(eh);
+ wechatPayEventHandlers.Sort((a, b) => b.Order - a.Order);
+ }
+
private readonly SqlSugarRepository _sysWechatPayRep;
private readonly SqlSugarRepository _sysWechatRefundRep;
private readonly WechatPayOptions _wechatPayOptions;
@@ -28,7 +42,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient
IOptions payCallBackOptions)
{
_sysWechatPayRep = sysWechatPayRep;
- this._sysWechatRefundRep = sysWechatRefundRep;
+ _sysWechatRefundRep = sysWechatRefundRep;
_wechatPayOptions = wechatPayOptions.Value;
_payCallBackOptions = payCallBackOptions.Value;
@@ -187,7 +201,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient
{
string outTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000); // 微信需要的订单号(唯一)
- //检查订单信息是否已存在(使用“商户交易单号+状态”唯一性判断)
+ // 检查订单信息是否已存在(使用“商户交易单号+状态”唯一性判断)
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OrderId == input.OrderId && u.OrderStatus == input.OrderStatus);
if (wechatPay != null)
{
@@ -250,6 +264,82 @@ public class SysWechatPayService : IDynamicApiController, ITransient
return await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == tradeId);
}
+ ///
+ /// 获取支付订单详情(微信接口) 🔖
+ ///
+ ///
+ ///
+ [DisplayName("获取支付订单详情(微信接口)")]
+ public async Task GetPayInfoFromWechat(string tradeId)
+ {
+ var request = new GetPayTransactionByOutTradeNumberRequest();
+ request.OutTradeNumber = tradeId;
+ var response = await _wechatTenpayClient.ExecuteGetPayTransactionByOutTradeNumberAsync(request);
+ // 修改订单支付状态
+ var wechatPayOld = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == response.OutTradeNumber
+ && u.MerchantId == response.MerchantId);
+ SysWechatPay wechatPayNew = null;
+ if (wechatPayOld != null)
+ wechatPayNew = wechatPayOld.DeepCopy();
+ // 如果状态不一致就更新数据库中的记录
+ if (wechatPayNew != null && wechatPayNew.TradeState != response.TradeState)
+ {
+ wechatPayNew.OpenId = response.Payer?.OpenId;
+ wechatPayNew.TransactionId = response.TransactionId; // 支付订单号
+ wechatPayNew.TradeType = response.TradeType; // 交易类型
+ wechatPayNew.TradeState = response.TradeState; // 交易状态
+ wechatPayNew.TradeStateDescription = response.TradeStateDescription; // 交易状态描述
+ wechatPayNew.BankType = response.BankType; // 付款银行类型
+ if (response.Amount != null)
+ {
+ wechatPayNew.Total = response.Amount.Total; // 订单总金额
+ wechatPayNew.PayerTotal = response.Amount.PayerTotal; // 用户支付金额
+ }
+ if (response.SuccessTime.HasValue)
+ wechatPayNew.SuccessTime = response.SuccessTime.Value.DateTime; // 支付完成时间
+ else
+ wechatPayNew.SuccessTime = DateTime.Now;
+ await _sysWechatPayRep.AsUpdateable(wechatPayNew).IgnoreColumns(true).ExecuteCommandAsync();
+ }
+ if (wechatPayOld == null || wechatPayOld.TradeState != wechatPayNew.TradeState)
+ {
+ // 没必要等所有回调事件处理完再返回给微信,开一个Task执行
+ Task.Run(async () =>
+ {
+ foreach (var eh in wechatPayEventHandlers)
+ {
+ try
+ {
+ if (!await eh.PayInforChanged(wechatPayOld, wechatPayNew))
+ break;
+ }
+ catch (Exception ex)
+ {
+ $"GetPayInfoFromWechat 中执行微信支付回调{eh.GetType().Name}出错".LogError(ex);
+ }
+ }
+ }).Start();
+ }
+ // 下面这里创建一个新的对象,是因为不想把全部字段都返回
+ wechatPayNew = new SysWechatPay()
+ {
+ AppId = _wechatPayOptions.AppId,
+ MerchantId = _wechatPayOptions.MerchantId,
+ SubAppId = _wechatPayOptions.AppId,
+ SubMerchantId = _wechatPayOptions.MerchantId,
+ OutTradeNumber = request.OutTradeNumber,
+ Attachment = response.Attachment,
+ TransactionId = response.TransactionId,
+ TradeType = response.TradeType, // 交易类型
+ TradeState = response.TradeState, // 交易状态
+ TradeStateDescription = response.TradeStateDescription, // 交易状态描述
+ BankType = response.BankType, // 付款银行类型
+ SuccessTime = response.SuccessTime.HasValue ? response.SuccessTime.Value.DateTime : DateTime.Now // 支付完成时间
+ };
+
+ return wechatPayNew;
+ }
+
///
/// 微信支付成功回调(商户直连) 🔖
///
@@ -269,35 +359,97 @@ public class SysWechatPayService : IDynamicApiController, ITransient
var callbackResource = _wechatTenpayClient.DecryptEventResource(callbackModel);
// 修改订单支付状态
- var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber
+ var wechatPayOld = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber
&& u.MerchantId == callbackResource.MerchantId);
- if (wechatPay == null) return null;
- //wechatPay.OpenId = callbackResource.Payer.OpenId; // 支付者标识
- //wechatPay.MerchantId = callbackResource.MerchantId; // 微信商户号
- //wechatPay.OutTradeNumber = callbackResource.OutTradeNumber; // 商户订单号
- wechatPay.TransactionId = callbackResource.TransactionId; // 支付订单号
- wechatPay.TradeType = callbackResource.TradeType; // 交易类型
- wechatPay.TradeState = callbackResource.TradeState; // 交易状态
- wechatPay.TradeStateDescription = callbackResource.TradeStateDescription; // 交易状态描述
- wechatPay.BankType = callbackResource.BankType; // 付款银行类型
- wechatPay.Total = callbackResource.Amount.Total; // 订单总金额
- wechatPay.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额
- wechatPay.SuccessTime = callbackResource.SuccessTime; // 支付完成时间
-
- await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync();
+ if (wechatPayOld == null) return null;
+ var wechatPayNew = wechatPayOld.DeepCopy();
+ if (callbackResource.Payer != null)
+ wechatPayNew.OpenId = callbackResource.Payer.OpenId; // 支付者标识
+ wechatPayNew.TransactionId = callbackResource.TransactionId; // 支付订单号
+ wechatPayNew.TradeType = callbackResource.TradeType; // 交易类型
+ wechatPayNew.TradeState = callbackResource.TradeState; // 交易状态
+ wechatPayNew.TradeStateDescription = callbackResource.TradeStateDescription; // 交易状态描述
+ wechatPayNew.BankType = callbackResource.BankType; // 付款银行类型
+ wechatPayNew.Total = callbackResource.Amount.Total; // 订单总金额
+ wechatPayNew.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额
+ wechatPayNew.SuccessTime = callbackResource.SuccessTime; // 支付完成时间
+ await _sysWechatPayRep.AsUpdateable(wechatPayNew).IgnoreColumns(true).ExecuteCommandAsync();
+ // 因为这个是回调给微信的,所以这里没必要等所有回调事件处理完再返回给微信,开一个Task执行
+ if (wechatPayOld.TradeState != wechatPayNew.TradeState)
+ {
+ Task.Run(async () =>
+ {
+ foreach (var eh in wechatPayEventHandlers)
+ {
+ try
+ {
+ if (!await eh.PayInforChanged(wechatPayOld, wechatPayNew))
+ break;
+ }
+ catch (Exception ex)
+ {
+ $"PayCallBack 中执行微信支付回调{eh.GetType().Name}出错".LogError(ex);
+ }
+ }
+ }).Start();
+ }
return new WechatPayOutput()
{
- Total = wechatPay.Total,
- Attachment = wechatPay.Attachment,
- GoodsTag = wechatPay.GoodsTag,
- OrderId = long.Parse(wechatPay.OrderId)
+ Total = wechatPayNew.Total,
+ Attachment = wechatPayNew.Attachment,
+ GoodsTag = wechatPayNew.GoodsTag,
+ OrderId = long.Parse(wechatPayNew.OrderId)
};
}
return null;
}
+ ///
+ /// 微信退款回调(商户直连) 🔖
+ ///
+ ///
+ [AllowAnonymous]
+ [DisplayName("微信退款回调(商户直连)")]
+ public async Task RefundCallBack()
+ {
+ using var ms = new MemoryStream();
+ await App.HttpContext.Request.Body.CopyToAsync(ms);
+ var b = ms.ToArray();
+ var callbackJson = Encoding.UTF8.GetString(b);
+
+ var callbackModel = _wechatTenpayClient.DeserializeEvent(callbackJson);
+ if ("REFUND.SUCCESS".Equals(callbackModel.EventType))
+ {
+ // 参考:https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/refund-result-notice.html
+ try
+ {
+ var callbackRefundResource = _wechatTenpayClient.DecryptEventResource(callbackModel);
+ // 修改订单支付状态
+ var wechatRefund = await _sysWechatRefundRep.GetFirstAsync(u => u.OutRefundNumber == callbackRefundResource.OutRefundNumber);
+ if (wechatRefund == null) return null;
+ wechatRefund.RefundStatus = callbackRefundResource.RefundStatus; // 交易状态
+ wechatRefund.SuccessTime = callbackRefundResource.SuccessTime.Value.DateTime; // 支付完成时间
+
+ await _sysWechatRefundRep.AsUpdateable(wechatRefund).IgnoreColumns(true).ExecuteCommandAsync();
+ // 有退款,刷新一下订单状态(通过主动查询Wechat接口获取)
+ // 通过 GetPayInfoFromWechat 也会触发 WechatPayEventHandler,所以这个回调中不用主动调用
+ await GetPayInfoFromWechat(callbackRefundResource.OutTradeNumber);
+ }
+ catch (Exception ex)
+ {
+ "微信退款回调时出错:".LogError(ex);
+ }
+ }
+ else
+ {
+ callbackModel.EventType.LogInformation();
+ }
+
+ return null;
+ }
+
///
/// 微信支付成功回调(服务商模式) 🔖
///
@@ -317,22 +469,43 @@ public class SysWechatPayService : IDynamicApiController, ITransient
var callbackResource = _wechatTenpayClient.DecryptEventResource(callbackModel);
// 修改订单支付状态
- var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber
+ var wechatPayOld = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber
&& u.MerchantId == callbackResource.MerchantId);
- if (wechatPay == null) return;
+
+ if (wechatPayOld == null) return;
+ var wechatPayNew = wechatPayOld.DeepCopy();
//wechatPay.OpenId = callbackResource.Payer.OpenId; // 支付者标识
//wechatPay.MerchantId = callbackResource.MerchantId; // 微信商户号
//wechatPay.OutTradeNumber = callbackResource.OutTradeNumber; // 商户订单号
- wechatPay.TransactionId = callbackResource.TransactionId; // 支付订单号
- wechatPay.TradeType = callbackResource.TradeType; // 交易类型
- wechatPay.TradeState = callbackResource.TradeState; // 交易状态
- wechatPay.TradeStateDescription = callbackResource.TradeStateDescription; // 交易状态描述
- wechatPay.BankType = callbackResource.BankType; // 付款银行类型
- wechatPay.Total = callbackResource.Amount.Total; // 订单总金额
- wechatPay.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额
- wechatPay.SuccessTime = callbackResource.SuccessTime; // 支付完成时间
+ wechatPayNew.TransactionId = callbackResource.TransactionId; // 支付订单号
+ wechatPayNew.TradeType = callbackResource.TradeType; // 交易类型
+ wechatPayNew.TradeState = callbackResource.TradeState; // 交易状态
+ wechatPayNew.TradeStateDescription = callbackResource.TradeStateDescription; // 交易状态描述
+ wechatPayNew.BankType = callbackResource.BankType; // 付款银行类型
+ wechatPayNew.Total = callbackResource.Amount.Total; // 订单总金额
+ wechatPayNew.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额
+ wechatPayNew.SuccessTime = callbackResource.SuccessTime; // 支付完成时间
- await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync();
+ await _sysWechatPayRep.AsUpdateable(wechatPayNew).IgnoreColumns(true).ExecuteCommandAsync();
+ // 因为这个是回调给微信的,所以这里没必要等所有回调事件处理完再返回给微信,开一个Task执行
+ if (wechatPayOld.TradeState != wechatPayNew.TradeState)
+ {
+ Task.Run(async () =>
+ {
+ foreach (var eh in wechatPayEventHandlers)
+ {
+ try
+ {
+ if (!await eh.PayInforChanged(wechatPayOld, wechatPayNew))
+ break;
+ }
+ catch (Exception ex)
+ {
+ $"PayPartnerCallBack 中执行微信支付回调{eh.GetType().Name}出错".LogError(ex);
+ }
+ }
+ }).Start();
+ }
}
}
@@ -345,10 +518,16 @@ public class SysWechatPayService : IDynamicApiController, ITransient
[DisplayName("微信退款申请)")]
public async Task Refund(RefundRequestInput input)
{
+ var vechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == input.OutTradeNumber);
+ if (vechatPay == null)
+ {
+ throw Oops.Bah("没有相应支付记录");
+ }
var request = new CreateRefundDomesticRefundRequest()
{
OutTradeNumber = input.OutTradeNumber,
OutRefundNumber = "REFUND_" + DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff"),
+ NotifyUrl = _payCallBackOptions.WechatRefundUrl,
Amount = new CreateRefundDomesticRefundRequest.Types.Amount()
{
Total = input.Total,
@@ -359,7 +538,15 @@ public class SysWechatPayService : IDynamicApiController, ITransient
var response = await _wechatTenpayClient.ExecuteCreateRefundDomesticRefundAsync(request);
if (!response.IsSuccessful())
+ {
+ // 退款失败,该单可能已经退款了,所以主动查询微信接口更新状态
+ try
+ {
+ await this.GetPayInfoFromWechat(input.OutTradeNumber);
+ }
+ catch (Exception ex) { }
throw Oops.Oh($"JSAPI 退款申请失败(状态码:{response.GetRawStatus()},错误代码:{response.ErrorCode},错误描述:{response.ErrorMessage})");
+ }
var wechatRefund = await _sysWechatRefundRep.GetFirstAsync(u => u.OutTradeNumber == input.OutTradeNumber);
if (wechatRefund == null)
@@ -367,15 +554,16 @@ public class SysWechatPayService : IDynamicApiController, ITransient
// 保存退款申请信息
wechatRefund = new SysWechatRefund()
{
- TransactionId = input.OutTradeNumber,
- OutTradeNumber = request.OutTradeNumber,
- OutRefundNo = request.OutTradeNumber, // 每笔付款只退一次,所以这里直接用付款单号
+ TransactionId = vechatPay.TransactionId,
+ OutTradeNumber = input.OutTradeNumber,
+ OutRefundNumber = request.OutRefundNumber, // 每笔付款只退一次,所以这里直接用付款单号
Reason = request.Reason,
Refund = input.Refund,
+ RefundId = response.RefundId,
Total = input.Total,
- //NotifyUrl = "",
+ NotifyUrl = _payCallBackOptions.WechatRefundUrl,
OrderId = input.OrderId,
- OrderStatus = input.OrderStatus,
+ RefundStatus = input.OrderStatus,
MerchantGoodsId = input.MerchantGoodsId,
GoodsName = input.GoodsName,
UnitPrice = input.UnitPrice,
@@ -385,6 +573,8 @@ public class SysWechatPayService : IDynamicApiController, ITransient
Remark = input.Remark
};
await _sysWechatRefundRep.InsertAsync(wechatRefund);
+ // 发送了退款请求也要更新原定单的状态(从微信查询)
+ await this.GetPayInfoFromWechat(input.OutTradeNumber);
}
}
@@ -530,10 +720,14 @@ public class SysWechatPayService : IDynamicApiController, ITransient
/// 根据支付Id获取退款信息列表 🔖
///
///
+ /// <
///
[DisplayName("根据支付Id获取退款信息列表")]
- public async Task> GetRefundList([FromQuery] string transactionId)
+ public async Task> GetRefundList([FromQuery] string transactionId, [FromQuery] string outTradeNumber)
{
- return await _sysWechatRefundRep.AsQueryable().Where(u => u.TransactionId == transactionId).ToListAsync();
+ return await _sysWechatRefundRep.AsQueryable()
+ .WhereIF(!string.IsNullOrEmpty(transactionId), u => u.TransactionId == transactionId)
+ .WhereIF(!string.IsNullOrEmpty(outTradeNumber), u => u.OutTradeNumber == outTradeNumber)
+ .ToListAsync();
}
}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/WechatPayEventInterceptor.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/WechatPayEventInterceptor.cs
new file mode 100644
index 00000000..1647ceec
--- /dev/null
+++ b/Admin.NET/Admin.NET.Core/Service/Wechat/WechatPayEventInterceptor.cs
@@ -0,0 +1,30 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.Service;
+
+///
+/// 微信支传订单状态变化事件拦截器
+///
+public class WechatPayEventInterceptor
+{
+ ///
+ /// 数值越大越先执行
+ ///
+ public int Order = 0;
+
+ ///
+ /// 信息变化处理器
+ ///
+ /// 主要用来判断 TradeStatus 的状态,这个状态有"空",NOTPAY,SUCCESS, REFUND
+ /// 旧记录状态
+ /// 新记录状态
+ ///
+ public virtual async Task PayInforChanged(SysWechatPay oldInfo, SysWechatPay newInfo)
+ {
+ return await Task.FromResult(true);
+ }
+}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs b/Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs
new file mode 100644
index 00000000..235346ac
--- /dev/null
+++ b/Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs
@@ -0,0 +1,144 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Admin.NET.Core.Utils;
+// 聚合配置增强版(独立类)
+public class AggregationBuilder
+{
+ private readonly List _configs;
+ private readonly Type _entityType;
+ private readonly Type _outputType;
+
+ public List SelectParts { get; } = new();
+ public List HavingConditions { get; } = new();
+
+ public AggregationBuilder(
+ IEnumerable configs,
+ Type entityType,
+ Type outputType)
+ {
+ _configs = configs.ToList();
+ _entityType = entityType;
+ _outputType = outputType;
+ Build();
+ }
+
+ private void Build()
+ {
+ foreach (var config in _configs.Where(IsValidConfig))
+ {
+ // 处理SELECT部分
+ var expression = string.IsNullOrEmpty(config.CustomExpression)
+ ? $"{config.Function.ToString().ToUpper()}({config.Field})"
+ : config.CustomExpression;
+
+ SelectParts.Add($"{expression} AS {config.Alias}");
+
+ // 处理HAVING条件
+ if (!string.IsNullOrEmpty(config.HavingCondition))
+ {
+ HavingConditions.Add($"{expression} {config.HavingCondition}");
+ }
+ }
+ }
+
+ private bool IsValidConfig(AggregationConfig config)
+ {
+ // 字段基础验证
+ if (!string.IsNullOrEmpty(config.Field) &&
+ _entityType.GetProperty(config.Field) == null)
+ return false;
+
+ // 输出属性验证
+ return _outputType.GetProperty(config.Alias) != null;
+ }
+ ///
+ /// 验证聚合配置有效性
+ ///
+ private bool ValidateAggregation(AggregationConfig config, Type entityType, Type outputType)
+ {
+ // 验证实体字段存在性
+ var entityProp = entityType.GetProperty(config.Field);
+ if (entityProp == null) return false;
+
+ // 验证输出字段存在性
+ var outputProp = outputType.GetProperty(config.Alias);
+ if (outputProp == null) return false;
+
+ // 验证类型兼容性
+ return config.Function switch
+ {
+ AggregateFunction.Count => outputProp.PropertyType == typeof(int),
+ _ => outputProp.PropertyType == entityProp.PropertyType ||
+ IsNumericType(outputProp.PropertyType)
+ };
+ }
+
+ private static bool IsNumericType(Type type)
+ {
+ return Type.GetTypeCode(type) switch
+ {
+ TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16
+ or TypeCode.UInt32 or TypeCode.UInt64 or TypeCode.Int16
+ or TypeCode.Int32 or TypeCode.Int64 or TypeCode.Decimal
+ or TypeCode.Double or TypeCode.Single => true,
+ _ => false
+ };
+ }
+ ///
+ /// 验证字段有效性
+ ///
+ public static List ValidateFields(string[] fields, Type targetType)
+ {
+ if (fields == null || fields.Length == 0) return new List();
+
+ return fields
+ .Where(f => !string.IsNullOrWhiteSpace(f))
+ .Select(f => f.Trim())
+ .Where(f => targetType.GetProperty(
+ targetType == typeof(UsagetokenOutput) ?
+ $"{f}Sum" : f) != null)
+ .ToList();
+ }
+}
+
+// 增强版聚合配置类
+public class AggregationConfig
+{
+ ///
+ /// 数据库字段名(与CustomExpression二选一)
+ ///
+ public string Field { get; set; }
+
+ ///
+ /// 自定义聚合表达式(优先级高于Field+Function)
+ ///
+ public string CustomExpression { get; set; }
+
+ ///
+ /// 聚合函数类型(使用CustomExpression时可不填)
+ ///
+ public AggregateFunction Function { get; set; } = AggregateFunction.Sum;
+
+ ///
+ /// 输出字段别名(必须与DTO属性名一致)
+ ///
+ public required string Alias { get; set; }
+
+ ///
+ /// HAVING条件表达式(如"> 100")
+ ///
+ public string HavingCondition { get; set; }
+}
+public enum AggregateFunction
+{
+ Sum,
+ Avg,
+ Count,
+ Max,
+ Min,
+ // 可扩展其他函数
+}
diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm
index e50562a7..52565d29 100644
--- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm
+++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm
@@ -17,7 +17,7 @@ namespace @Model.NameSpace;
@if(@Model.ConfigId!="1300000000001"){
@:[Tenant("@(@Model.ConfigId)")]
}
-public partial class @(@Model.EntityName) @Model.BaseClassName
+public class @(@Model.EntityName) @Model.BaseClassName
{
@foreach (var column in Model.TableField){
if(@Model.BaseClassName=="" && @column.IsPrimarykey){
diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm
index 008afc22..c2f9bf9c 100644
--- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm
+++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm
@@ -1,28 +1,60 @@
-
-namespace @(@Model.NameSpace).Entity;
+@if(@Model.NameSpace != "Admin.NET.Core"){
+@:using Admin.NET.Core;
+}
+namespace @(@Model.NameSpace);
///
-/// 扩展@(@Model.BusName)实体
+/// 扩展@(@Model.BusName)输出实体
///
-public partial class @(@Model.ClassName)
+public partial class @(@Model.ClassName)Output
{
-
- @if(Model.TableField.FirstOrDefault(u => u.ColumnName.ToLower() == "name") == null){
- @:///
- @:/// 合约商品树形Name
- @:///
- @:[SugarColumn(IsIgnore = true)]
- @:public string Name { get; set; }
- }
- ///
- /// @(@Model.BusName)子项
- ///
- [SugarColumn(IsIgnore = true)]
- public List<@(@Model.ClassName)> Children { get; set; }
+
///
- /// 是否禁止选中
+ /// count
///
- [SugarColumn(IsIgnore = true)]
- public bool Disabled { get; set; }
+ public int count { get; set; }
+ @foreach (var par in Model.TableInoutpar.Where(m => m.inouttype == "Input").ToList()){
+ @:///
+ @:/// @(@par.Name)
+ @:///
+ @:public @(@par.DataType) @(@par.parameter) { get; set; }
+ }
+
+ @if(Model.TableField.FirstOrDefault(u => u.ColumnName.ToLower() == "name") == null){
+ @:///
+ @:/// Name
+ @:///
+ @:public string Name { get; set; }
+ }
+@if(Model.TabType=="Tree"){
+ @:///
+ @:/// @(@Model.BusName)子项
+ @:///
+ @:[SugarColumn(IsIgnore = true)]
+ @:public List<@(@Model.ClassName)Output> Children { get; set; }
+
+ @:///
+ @:/// 是否禁止选中
+ @:///
+ @:[SugarColumn(IsIgnore = true)]
+ @:public bool Disabled { get; set; }
+}
}
+
+///
+/// 扩展@(@Model.BusName)分页查询输入参数
+///
+public partial class Page@(@Model.ClassName)Input
+{
+ public string[] GroupBy { get; set; }
+ public string[] Sum { get; set; }
+ public IEnumerable Aggregations { get; set; }
+ @foreach (var par in Model.TableInoutpar.Where(m => m.inouttype == "Output").ToList()){
+ @:///
+ @:/// @(@par.Name)
+ @:///
+ @:public @(@par.DataType) @(@par.parameter) { get; set; }
+}
+
+}
diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm
index ab89091e..04e35e14 100644
--- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm
+++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm
@@ -1,4 +1,3 @@
-using Admin.NET.Core.Service;
using Microsoft.AspNetCore.Http;
using OfficeOpenXml.FormulaParsing.Excel.Functions.Text;
@{
@@ -19,7 +18,9 @@ using OfficeOpenXml.FormulaParsing.Excel.Functions.Text;
}
}
}
-using @(@Model.NameSpace).Entity;
+@if(@Model.NameSpace != "Admin.NET.Core"){
+@:using Admin.NET.Core;
+}
namespace @Model.NameSpace;
///
@@ -35,26 +36,35 @@ public partial class @(@Model.ClassName)Service
///
[ApiDescriptionSettings(Name = "GetTreeList", Description = "获取列表", Order = 1100), HttpPost]
[DisplayName("获取列表")]
- public async Task> GetTreeList([FromQuery] Tree input)
+ public async Task> GetTreeList(Page@(@Model.ClassName)Input input)
{
- // 带条件筛选时返回列表数据
- var list = await _@(@Model.LowerClassName)Rep.AsQueryable()
- .WhereIF(input.Id > 0, t => t.Id == input.Id)
- //名称
- //.WhereIF(!string.IsNullOrWhiteSpace(input.tacticsNme), u => u.tacticsNme.Contains(input.tacticsNme.Trim()))
- .OrderBy(t => new { t.Id })
- //.Select((u) => new Tree
- //{
- // Id = u.Id.ToString(),
- // Name = u.@(@Model.TreeName)
- //})
- .ToListAsync();
- @if(!string.IsNullOrEmpty(Model.TreeName)){
- @if(Model.TableField.FirstOrDefault(u => u.ColumnName.ToLower() == "name") == null){
- @:list.ForEach(t => t.Name = t.@(@Model.TreeName) );
- }
- }
- return list;
+ @if(Model.TabType=="Tree"){
+ @:var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToTreeAsync(u => u.Children, u => u.@(@Model.TreeKey), input.Id);
+ @://var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToListAsync();//非树形结构表
+
+ @:var md = await _@(@Model.LowerClassName)Rep.AsQueryable().Where(u => u.Id == input.Id).Select<@(@Model.ClassName)Output>().FirstAsync();
+ @:if (md == null) return list;
+ @:
+ @:md.Children = list;
+ @:list = [md];
+ @:return list;
+ }else{
+ @://return await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToTreeAsync(u => u.Children, u => u.@(@Model.TreeKey), input.Id));//树形结构表
+ @:return await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToListAsync();
+ }
}
}
+///
+/// 扩展@(@Model.BusName)中间件
+///
+public partial class @(@Model.ClassName)Mid
+{
+
+}
+@{
+string LowerFirstLetter(string text)
+{
+return text.ToString()[..1].ToLower() + text[1..]; // 首字母小写
+}
+}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm
index acd91601..a35291df 100644
--- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm
+++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm
@@ -41,7 +41,7 @@ if (@column.ColumnKey != "True"){
///
/// @(@Model.BusName)分页查询输入参数
///
- public class Page@(@Model.ClassName)Input : BasePageInput
+ public partial class Page@(@Model.ClassName)Input : BasePageInput
{
///
/// 关键字查询
diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Mid.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Mid.cs.vm
new file mode 100644
index 00000000..621a81b9
--- /dev/null
+++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Mid.cs.vm
@@ -0,0 +1,116 @@
+@{
+ string joinTableName = "u";
+ Dictionary definedObjects = new Dictionary();
+ bool haveLikeCdt = false;
+ string RemoteField="";
+ string PKName="";
+ foreach (var column in Model.TableField){
+ if (column.QueryWhether == "Y" && column.QueryType == "like"){
+ haveLikeCdt = true;
+ }
+ if(column.RemoteVerify){
+ RemoteField=@column.PropertyName;
+ }
+ if(column.ColumnKey == "True"){
+ PKName=column.PropertyName;
+ }
+ }
+}
+@if(@Model.NameSpace != "Admin.NET.Core"){
+@:using Admin.NET.Core;
+}
+
+namespace @(@Model.NameSpace);
+public partial class @(@Model.ClassName)Mid
+{
+ ///
+ /// 获取查询
+ ///
+ ///
+ ///
+ ///
+ public static ISugarQueryable<@(@Model.ClassName)Output> GetQuery(SqlSugarRepository<@(@Model.ClassName)> _@(@Model.LowerClassName)Rep,Page@(@Model.ClassName)Input input)
+ {
+ var sysCacheService = App.GetRequiredService();
+ var db = App.GetRequiredService();
+ @if (haveLikeCdt) {
+ @:input.SearchKey = input.SearchKey?.Trim();
+ }
+ var query = _@(@Model.LowerClassName)Rep.AsQueryable()
+ @{string conditionFlag = "";}
+ @if (haveLikeCdt) {
+ @:.WhereIF(!string.IsNullOrEmpty(input.SearchKey), u =>
+ @foreach (var column in Model.TableField){
+ if (@column.QueryWhether == "Y" && column.QueryType == "like"){
+ @:@(conditionFlag)u.@(@column.PropertyName).Contains(input.SearchKey)
+ conditionFlag="|| ";
+ }
+ }
+ @:)
+ }
+ @foreach (var column in Model.TableField){
+ if (@column.QueryWhether == "Y"){
+ if (@column.NetType?.TrimEnd('?') == "string"){
+ if(@column.QueryType == "like"){
+ @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName).Contains(input.@(@column.PropertyName).Trim()))
+ }else{
+ @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName))
+ }
+ }else if(@column.NetType?.TrimEnd('?') == "int" || @column.NetType?.TrimEnd('?') == "long"){
+ @:.WhereIF(input.@column.PropertyName>0, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName))
+ }else if(@column.NetType?.TrimEnd('?') == "DateTime" && @column.QueryType == "~"){
+ @:.WhereIF(input.@(@column.PropertyName)Range != null && input.@(@column.PropertyName)Range.Length == 2, u => u.@(@column.PropertyName) >= input.@(@column.PropertyName)Range[0] && u.@(@column.PropertyName) <= input.@(@column.PropertyName)Range[1])
+ }else if(@column.NetType?.TrimEnd('?').EndsWith("Enum") == true) {
+ @:.WhereIF(input.@(@column.PropertyName).HasValue, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName))
+ }
+ }
+ }
+
+ .Select<@(@Model.ClassName)Output>()
+ @if(!string.IsNullOrEmpty(Model.TreeName)){
+ @:.Mapper(c => c.Name= c.@(@Model.TreeName).ToString())
+ }
+ @foreach (var column in Model.TableField){
+ if(@column.EffectType == "Upload"){
+ @://.Mapper(c => c.@(@column.PropertyName)Attachment, c => c.@(@column.PropertyName))
+ }
+ else if(@column.EffectType == "ForeignKey"){
+ @:.Mapper(t =>
+ @:{
+ @: //使用缓存
+ @: var key = $"@(@column.FkEntityName)_{t.@(@column.PropertyName)}";
+ @: if (!sysCacheService.ExistKey(key))
+ @: {
+ @: var m = db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName));
+ @: if (m != null) sysCacheService.Set(key, m);
+ @: }
+ @: t.@(@column.PropertyName)@(@column.FkColumnName) = sysCacheService.Get<@(@column.FkEntityName)>(key)?.@(@column.FkColumnName);
+ @: //t.@(@column.PropertyName)@(@column.FkColumnName)=db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName))).@(@column.FkColumnName);//
+ @:})
+ }
+ else if(@column.EffectType == "ApiTreeSelector"){
+ @:.Mapper(t =>
+ @:{
+ @: //使用缓存
+ @: var key = $"@(@column.FkEntityName)_{t.@(@column.PropertyName)}";
+ @: if (!sysCacheService.ExistKey(key))
+ @: {
+ @: var m = db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.ValueColumn) == t.@(@column.PropertyName));
+ @: if (m != null) sysCacheService.Set(key, m);
+ @: }
+ @: t.@(@column.PropertyName)@(@column.DisplayColumn) = sysCacheService.Get<@(@column.FkEntityName)>(key)?.@(@column.DisplayColumn);
+ @: //t.@(@column.PropertyName)@(@column.FkColumnName)=db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName))).@(@column.FkColumnName);//
+ @:})
+ }
+ }
+ ;
+
+ return query;
+ }
+}
+@{
+string LowerFirstLetter(string text)
+{
+return text.ToString()[..1].ToLower() + text[1..]; // 首字母小写
+}
+}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm
index ec2e5cf1..846b0515 100644
--- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm
+++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm
@@ -9,7 +9,7 @@ namespace @Model.NameSpace;
///
/// @(@Model.BusName)输出参数
///
-public class @(@Model.ClassName)Output
+public partial class @(@Model.ClassName)Output
{
@foreach (var column in Model.TableField){
@:///
diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm
index a8183649..22a284d8 100644
--- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm
+++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm
@@ -71,65 +71,9 @@ public partial class @(@Model.ClassName)Service : IDynamicApiController, ITransi
[DisplayName("分页查询@(@Model.BusName)")]
public async Task> Page(Page@(@Model.ClassName)Input input)
{
-@if (haveLikeCdt) {
- @:input.SearchKey = input.SearchKey?.Trim();
-}
- var query = _@(@Model.LowerClassName)Rep.AsQueryable()
-@{string conditionFlag = "";}
-@if (haveLikeCdt) {
- @:.WhereIF(!string.IsNullOrEmpty(input.SearchKey), u =>
- @foreach (var column in Model.TableField){
- if (@column.QueryWhether == "Y" && column.QueryType == "like"){
- @:@(conditionFlag)u.@(@column.PropertyName).Contains(input.SearchKey)
- conditionFlag="|| ";
- }
- }
- @:)
-}
-@foreach (var column in Model.TableField){
-if (@column.QueryWhether == "Y"){
- if (@column.NetType?.TrimEnd('?') == "string"){
- if(@column.QueryType == "like"){
- @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName).Contains(input.@(@column.PropertyName).Trim()))
- }else{
- @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName))
- }
- }else if(@column.NetType?.TrimEnd('?') == "int" || @column.NetType?.TrimEnd('?') == "long"){
- @:.WhereIF(input.@column.PropertyName>0, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName))
- }else if(@column.NetType?.TrimEnd('?') == "DateTime" && @column.QueryType == "~"){
- @:.WhereIF(input.@(@column.PropertyName)Range != null && input.@(@column.PropertyName)Range.Length == 2, u => u.@(@column.PropertyName) >= input.@(@column.PropertyName)Range[0] && u.@(@column.PropertyName) <= input.@(@column.PropertyName)Range[1])
- }else if(@column.NetType?.TrimEnd('?').EndsWith("Enum") == true) {
- @:.WhereIF(input.@(@column.PropertyName).HasValue, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName))
- }
-}
-}
-@foreach (var column in Model.TableField){
- if(@column.EffectType == "Upload"){
- @://.Mapper(c => c.@(@column.PropertyName)Attachment, c => c.@(@column.PropertyName))
- }
-}
- .Select<@(@Model.ClassName)Output>();
-
-
-
- @if(@Model.TableField.Any(x=>x.EffectType == "ForeignKey"||x.EffectType == "ApiTreeSelector")){
- @:var list = await query.MergeTable().OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
- } else {
- @:var list = await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
- }
- @if(@Model.TableField.Any(x=>x.EffectType == "ForeignKey"||x.EffectType == "ApiTreeSelector")){
- @:list.Items.ForEach(t =>{
-
- @foreach (var column in Model.TableField){
- if(@column.EffectType == "ForeignKey"){
- @:t.@(@column.PropertyName)@(@column.FkColumnName) =_@(@column.LowerFkEntityName)Rep.GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName)).@(@column.FkColumnName);
- }
- else if(@column.EffectType == "ApiTreeSelector"){
- @:t.@(@column.PropertyName)@(@column.FkColumnName) =_@(@column.LowerFkEntityName)Rep.GetFirst(f => f.@(@column.DisplayColumn) == t.@(@column.PropertyName)).@(@column.FkColumnName);
- }
- }
- @:});
- }
+ //var query= @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input);
+ //var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).MergeTable().ToPagedListAsync(input.Page, input.PageSize);
+ var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
return list;
}
@@ -217,14 +161,125 @@ if (@column.ColumnKey == "True"){
///
///
///
- [ApiDescriptionSettings(Name = "list", Description = "获取@(@Model.BusName)列表", Order = 950), HttpGet]
+ [ApiDescriptionSettings(Name = "list", Description = "获取@(@Model.BusName)列表", Order = 950), HttpPost]
[DisplayName("获取@(@Model.BusName)列表")]
- public async Task> List([FromQuery] Page@(@Model.ClassName)Input input)
+ public async Task> List(Page@(@Model.ClassName)Input input)
{
- return await _@(@Model.LowerClassName)Rep.AsQueryable().Select<@(@Model.ClassName)Output>().ToListAsync();
+ return await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToListAsync();
}
+@if(@Model.TableField.Any(x=>x.Statistical == "Y")){
+ @:///
+ @:/// 获取@(@Model.BusName)
+ @:///
+ @:///
+ @:///
+ @:[ApiDescriptionSettings(Name = "GetTotalSum", Description = "获取@(@Model.BusName)统计", Order = 960), HttpPost]
+ @:[DisplayName("获取@(@Model.BusName)统计")]
+ @:public async Task> GetTotalSum(Page@(@Model.ClassName)Input input)
+ @:{
+ @:// 单次查询同时获取统计值
+ @:var querystats = @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input)
+ @foreach (var column in Model.TableField){
+ if (@column.IsGroupBy == "Y"){
+ @: .GroupByIF(input.GroupBy.Contains("@column.PropertyName"), u => u.@column.PropertyName)
+ }
+ }
+ @: //.Having(it => SqlFunc.AggregateCount(it.Id) > 0)//聚合函数过滤
+ @: .Select(it => new @(@Model.ClassName)Output
+ @: {
+ @: count = SqlFunc.AggregateCount(it.Id),
+ @foreach (var column in Model.TableField){
+ if (@column.IsGroupBy == "Y"){
+ @: @(@column.PropertyName) = it.@(@column.PropertyName),
+ }
+ if (@column.Statistical == "Y"){
+ @: @(@column.PropertyName) = SqlFunc.AggregateSum(it.@(@column.PropertyName)) ?? 0,
+ }
+ }
+ @: });
+ @:return await querystats.ToListAsync();
+ @:}
+}
+
+ ///
+ /// 根据输入参数获取@(@Model.BusName)统计
+ /// 支持双模式聚合配置:
+ /// 常规模式:Field + Function
+ /// 高级模式:CustomExpression(支持任意合法SQL表达式)
+ /// 智能条件组合:
+ /// 多个HAVING条件自动用AND连接
+ /// 条件表达式自动包裹聚合函数(如SUM(cost) > 10000)
+ /// 可扩展支持OR条件
+ ///
+ ///
+ ///
+ [ApiDescriptionSettings(Name = "GetAggregTotalSum", Order = 970), HttpPost]
+ [DisplayName("根据输入参数获取@(@Model.BusName)统计")]
+ public async Task> GetAggregTotalSum(Page@(@Model.ClassName)Input input)
+ {
+
+ @:var query = @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input)
+ // 输入参数示例
+ //input = new Page@(@Model.ClassName)Input
+ //{
+ // GroupBy = ["department", "project"],
+ // GroupBy = input.GroupBy,
+ // Aggregations =
+ // [
+ // new AggregationConfig
+ //{
+ // Field = "cost",
+ // Function = AggregateFunction.Sum,
+ // Alias = "totalCost",
+ // HavingCondition = "> 10000"
+ //},
+ //new AggregationConfig
+ //{
+ // CustomExpression = "AVG(CAST(response_time AS FLOAT))",
+ // Alias = "avgResponse",
+ // HavingCondition = "< 500"
+ //}
+ // ]
+ //};
+ // 生成SQL示例
+ // SELECT
+ // department, project,
+ // SUM(cost) AS totalCost,
+ // AVG(CAST(response_time AS FLOAT)) AS avgResponse
+ // FROM...
+ // GROUP BY department, project
+ // HAVING(SUM(cost) > 10000) AND(AVG(CAST(response_time AS FLOAT)) < 500)
+
+ // 处理分组字段
+ var groupFields = AggregationBuilder.ValidateFields(input.GroupBy, typeof(@(@Model.ClassName)Output));
+ if (groupFields.Count > 0)
+ {
+ query = query.GroupBy(string.Join(",", groupFields));
+ }
+ // 构建聚合配置
+ var aggregator = new AggregationBuilder(
+ configs: input.Aggregations,
+ entityType: typeof(@(@Model.ClassName)),
+ outputType: typeof(@(@Model.ClassName)Output)
+ );
+
+ // 组合SELECT语句
+ var selectParts = groupFields.Select(f => $"{f} AS {f}")
+ .Concat(aggregator.SelectParts)
+ .ToList();
+
+ // 应用HAVING条件
+ if (aggregator.HavingConditions.Count > 0)
+ {
+ query = query.Having(string.Join(" AND ", aggregator.HavingConditions));
+ }
+ // 执行查询
+ return await query.Select<@(@Model.ClassName)Output>(string.Join(", ", selectParts))
+ .ToListAsync();
+ }
+
@foreach (var column in Model.TableField){
if(@column.EffectType == "ForeignKey" && (@column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){
@:///
@@ -270,7 +325,7 @@ if(@column.EffectType == "ApiTreeSelector" && !definedObjects.ContainsKey("@(@co
@:[DisplayName("获取@(@column.FkEntityName)Tree")]
@:public async Task @(@column.FkEntityName)Tree()
@:{
- @:return await _@(@Model.LowerClassName)Rep.Context.Queryable<@(@column.FkEntityName)>().ToTreeAsync(u => u.Children, u => u.@(@column.PidColumn), 0);
+ @:return await _@(@Model.LowerClassName)Rep.Context.Queryable<@(@column.FkEntityName)>().Select<@(@column.FkEntityName)Output>().ToTreeAsync(u => u.Children, u => u.@(@column.PidColumn), 0);
@:}
}
}
diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm
index cf7594b8..eea5ff09 100644
--- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm
+++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm
@@ -18,6 +18,8 @@ enum Api {
Update@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/update',
Page@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/page',
Tree@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/GetTreeList',
+ Get@(@Model.ClassName)TotalSum = '/api/@(@Model.LowerClassName)/GetTotalSum',
+ Get@(@Model.ClassName)AggregTotalSum = '/api/@(@Model.LowerClassName)/GetAggregTotalSum',
Detail@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/detail',
@if(Model.RemoteVerify){
@:Exists@(RemoteField) = '/api/@(@Model.LowerClassName)/exists@(RemoteField)',
@@ -72,6 +74,20 @@ export const treelist@(@Model.ClassName) = (params?: any) =>
method: 'post',
data: params,
});
+// 获取统计,支持全表和分组
+export const get@(@Model.ClassName)TotalSum = (params?: any) =>
+ request({
+ url: Api.Get@(@Model.ClassName)TotalSum,
+ method: 'post',
+ data: params,
+ });
+// 输入参数获取统计
+export const get@(@Model.ClassName)AggregTotalSum = (params?: any) =>
+ request({
+ url: Api.Get@(@Model.ClassName)AggregTotalSum,
+ method: 'post',
+ data: params,
+ });
// 详情@(@Model.BusName)
export const detail@(@Model.ClassName) = (id: any) =>
request({
diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm
index 171b618e..db8effb6 100644
--- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm
+++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm
@@ -52,7 +52,7 @@
-
+
@@ -60,7 +60,7 @@
-
+
@@ -235,7 +235,7 @@ import ModifyRecord from '/@@/components/table/modifyRecord.vue';
@:import { @(@Model.ClassName), @(@Model.ClassName)Input, @(@Model.ClassName)Output } from '/@@/api-services/models';
} else {
-@:import { page@(@Model.ClassName), delete@(@Model.ClassName) } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)';
+@:import { page@(@Model.ClassName), delete@(@Model.ClassName), get@(@Model.ClassName)TotalSum } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)';
foreach (var column in Model.QueryWhetherList){
if(@column.EffectType == "ForeignKey"){
@:import { get@(@column.FkEntityName)@(@column.PropertyName)Dropdown } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)';
@@ -278,6 +278,7 @@ const state = reactive({
pageSize: 50 as number,
defaultSort: { field: 'Id', order: 'asc', descStr: 'desc' },
},
+ totalSum:[] as any,
visible: false,
title: '',
});
@@ -337,6 +338,26 @@ const checkTableColumnVisible = (tableColumnName: any) => {
}
{ title: '操作', fixed: 'right', width: 180, showOverflow: true, slots: { default: 'row_buttons' } },
],
+ @if(@Model.TableField.Any(x=>x.Statistical == "Y")){
+ @:footerMethod: ({ columns, data }) => {
+ @: const totalSum=state.totalSum[0];
+ @: return [
+ @: columns.map((column, colIndex) => {
+ @: if (colIndex === 0) {
+ @: return `合计:`
+ @: }
+ @foreach (var column in Model.TableField){
+ if (@column.Statistical == "Y"){
+ @: if (column.field === '@(@column.LowerPropertyName)') {
+ @: // 计算表格内总和
+ @: return `${data.reduce((sum, row) => sum + (row.@(@column.LowerPropertyName) || 0), 0)}/总数:${totalSum.@(@column.LowerPropertyName)}`
+ @: }
+ }
+ }
+ @: })
+ @: ]
+ @:},
+ }
},
// vxeGrid配置参数(此处可覆写任何参数),参考vxe-table官方文档
{
@@ -377,6 +398,14 @@ const handleQueryApi = async (page: VxeGridPropTypes.ProxyAjaxQueryPageParams, s
// 查询操作
const handleQuery = async (reset = false) => {
options.loading = true;
+ @if(@Model.TableField.Any(x=>x.Statistical == "Y")){
+ @if (@Model.IsApiService) {
+ @:state.totalSum =getAPI(@(@Model.ClassName)Api).api@(@Model.ClassName)GetTotalSumPost(params).data.result;
+ }
+ } else {
+ @:state.totalSum =(await get@(@Model.ClassName)TotalSum(state.queryParams)).data.result;
+ }
+ }
reset ? await xGrid.value?.commitProxy('reload') : await xGrid.value?.commitProxy('query');
options.loading = false;
};
diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm
index 52f88582..255cb9ec 100644
--- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm
+++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm
@@ -109,7 +109,11 @@ watch(filterText, (val) => {
// 获取树数据
const fetchTreeData = async (showLoading: boolean = true) => {
if (showLoading) state.loading = true;
- var res = await treelist@(@Model.ClassName)({Id:0});
+ @if (@Model.IsApiService) {
+ @:var res = await getAPI(@(@Model.ClassName)Api).api@(@Model.ClassName)GetTreeListPost({Id:0});
+ } else {
+ @:var res = await treelist@(@Model.ClassName)({Id:0});
+ }
state.@(@Model.LowerClassName)Data = res.data.result ?? [];
if (showLoading) state.loading = false;
return res.data.result ?? [];
diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.ReZero/Admin.NET.Plugin.ReZero.csproj b/Admin.NET/Plugins/Admin.NET.Plugin.ReZero/Admin.NET.Plugin.ReZero.csproj
index 8dd211d3..483e2d0f 100644
--- a/Admin.NET/Plugins/Admin.NET.Plugin.ReZero/Admin.NET.Plugin.ReZero.csproj
+++ b/Admin.NET/Plugins/Admin.NET.Plugin.ReZero/Admin.NET.Plugin.ReZero.csproj
@@ -25,7 +25,7 @@
-
+
diff --git a/Web/package.json b/Web/package.json
index 1953e0ec..f3dfe892 100644
--- a/Web/package.json
+++ b/Web/package.json
@@ -2,7 +2,7 @@
"name": "admin.net.pro",
"type": "module",
"version": "2.4.33",
- "lastBuildTime": "2025.02.23",
+ "lastBuildTime": "2025.02.25",
"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",
"author": "zuohuaijun",
"license": "MIT",
@@ -88,22 +88,22 @@
"@types/node": "^20.17.19",
"@types/nprogress": "^0.2.3",
"@types/sortablejs": "^1.15.8",
- "@typescript-eslint/eslint-plugin": "^8.24.1",
- "@typescript-eslint/parser": "^8.24.1",
+ "@typescript-eslint/eslint-plugin": "^8.25.0",
+ "@typescript-eslint/parser": "^8.25.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"@vue/compiler-sfc": "^3.5.13",
- "code-inspector-plugin": "^0.20.0",
+ "code-inspector-plugin": "^0.20.1",
"eslint": "^9.21.0",
"eslint-plugin-vue": "^9.32.0",
"globals": "^16.0.0",
"less": "^4.2.2",
"prettier": "^3.5.2",
"rollup-plugin-visualizer": "^5.14.0",
- "sass": "^1.85.0",
+ "sass": "^1.85.1",
"terser": "^5.39.0",
"typescript": "^5.7.3",
- "vite": "^6.1.1",
+ "vite": "^6.2.0",
"vite-plugin-cdn-import": "^1.0.1",
"vite-plugin-compression2": "^1.3.3",
"vite-plugin-vue-setup-extend": "^0.4.0",
diff --git a/Web/src/api-services/apis/sys-wechat-pay-api.ts b/Web/src/api-services/apis/sys-wechat-pay-api.ts
index 793674d2..650cc607 100644
--- a/Web/src/api-services/apis/sys-wechat-pay-api.ts
+++ b/Web/src/api-services/apis/sys-wechat-pay-api.ts
@@ -174,6 +174,55 @@ export const SysWechatPayApiAxiosParamCreator = function (configuration?: Config
options: localVarRequestOptions,
};
},
+ /**
+ *
+ * @summary 获取支付订单详情(微信接口) 🔖
+ * @param {string} tradeId
+ * @param {*} [options] Override http request option.
+ * @throws {RequiredError}
+ */
+ apiSysWechatPayPayInfoFromWechatTradeIdGet: async (tradeId: string, options: AxiosRequestConfig = {}): Promise => {
+ // verify required parameter 'tradeId' is not null or undefined
+ if (tradeId === null || tradeId === undefined) {
+ throw new RequiredError('tradeId','Required parameter tradeId was null or undefined when calling apiSysWechatPayPayInfoFromWechatTradeIdGet.');
+ }
+ const localVarPath = `/api/sysWechatPay/payInfoFromWechat/{tradeId}`
+ .replace(`{${"tradeId"}}`, encodeURIComponent(String(tradeId)));
+ // use dummy base URL string because the URL constructor only accepts absolute URLs.
+ const localVarUrlObj = new URL(localVarPath, 'https://example.com');
+ let baseOptions;
+ if (configuration) {
+ baseOptions = configuration.baseOptions;
+ }
+ const localVarRequestOptions :AxiosRequestConfig = { method: 'GET', ...baseOptions, ...options};
+ const localVarHeaderParameter = {} as any;
+ const localVarQueryParameter = {} as any;
+
+ // authentication Bearer required
+ // http bearer authentication required
+ if (configuration && configuration.accessToken) {
+ const accessToken = typeof configuration.accessToken === 'function'
+ ? await configuration.accessToken()
+ : await configuration.accessToken;
+ localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
+ }
+
+ const query = new URLSearchParams(localVarUrlObj.search);
+ for (const key in localVarQueryParameter) {
+ query.set(key, localVarQueryParameter[key]);
+ }
+ for (const key in options.params) {
+ query.set(key, options.params[key]);
+ }
+ localVarUrlObj.search = (new URLSearchParams(query)).toString();
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+ localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+ return {
+ url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
+ options: localVarRequestOptions,
+ };
+ },
/**
*
* @summary 获取支付订单详情 🔖
@@ -559,12 +608,56 @@ export const SysWechatPayApiAxiosParamCreator = function (configuration?: Config
},
/**
*
- * @summary 根据支付Id获取退款信息列表 🔖
- * @param {string} [transactionId]
+ * @summary 微信退款回调(商户直连) 🔖
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
- apiSysWechatPayRefundListGet: async (transactionId?: string, options: AxiosRequestConfig = {}): Promise => {
+ apiSysWechatPayRefundCallBackPost: async (options: AxiosRequestConfig = {}): Promise => {
+ const localVarPath = `/api/sysWechatPay/refundCallBack`;
+ // use dummy base URL string because the URL constructor only accepts absolute URLs.
+ const localVarUrlObj = new URL(localVarPath, 'https://example.com');
+ let baseOptions;
+ if (configuration) {
+ baseOptions = configuration.baseOptions;
+ }
+ const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
+ const localVarHeaderParameter = {} as any;
+ const localVarQueryParameter = {} as any;
+
+ // authentication Bearer required
+ // http bearer authentication required
+ if (configuration && configuration.accessToken) {
+ const accessToken = typeof configuration.accessToken === 'function'
+ ? await configuration.accessToken()
+ : await configuration.accessToken;
+ localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
+ }
+
+ const query = new URLSearchParams(localVarUrlObj.search);
+ for (const key in localVarQueryParameter) {
+ query.set(key, localVarQueryParameter[key]);
+ }
+ for (const key in options.params) {
+ query.set(key, options.params[key]);
+ }
+ localVarUrlObj.search = (new URLSearchParams(query)).toString();
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+ localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+ return {
+ url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
+ options: localVarRequestOptions,
+ };
+ },
+ /**
+ *
+ * @summary 根据支付Id获取退款信息列表
+ * @param {string} [transactionId]
+ * @param {string} [outTradeNumber]
+ * @param {*} [options] Override http request option.
+ * @throws {RequiredError}
+ */
+ apiSysWechatPayRefundListGet: async (transactionId?: string, outTradeNumber?: string, options: AxiosRequestConfig = {}): Promise => {
const localVarPath = `/api/sysWechatPay/refundList`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, 'https://example.com');
@@ -589,6 +682,10 @@ export const SysWechatPayApiAxiosParamCreator = function (configuration?: Config
localVarQueryParameter['transactionId'] = transactionId;
}
+ if (outTradeNumber !== undefined) {
+ localVarQueryParameter['outTradeNumber'] = outTradeNumber;
+ }
+
const query = new URLSearchParams(localVarUrlObj.search);
for (const key in localVarQueryParameter) {
query.set(key, localVarQueryParameter[key]);
@@ -703,6 +800,20 @@ export const SysWechatPayApiFp = function(configuration?: Configuration) {
return axios.request(axiosRequestArgs);
};
},
+ /**
+ *
+ * @summary 获取支付订单详情(微信接口) 🔖
+ * @param {string} tradeId
+ * @param {*} [options] Override http request option.
+ * @throws {RequiredError}
+ */
+ async apiSysWechatPayPayInfoFromWechatTradeIdGet(tradeId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> {
+ const localVarAxiosArgs = await SysWechatPayApiAxiosParamCreator(configuration).apiSysWechatPayPayInfoFromWechatTradeIdGet(tradeId, options);
+ return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+ const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
+ return axios.request(axiosRequestArgs);
+ };
+ },
/**
*
* @summary 获取支付订单详情 🔖
@@ -816,13 +927,27 @@ export const SysWechatPayApiFp = function(configuration?: Configuration) {
},
/**
*
- * @summary 根据支付Id获取退款信息列表 🔖
- * @param {string} [transactionId]
+ * @summary 微信退款回调(商户直连) 🔖
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
- async apiSysWechatPayRefundListGet(transactionId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> {
- const localVarAxiosArgs = await SysWechatPayApiAxiosParamCreator(configuration).apiSysWechatPayRefundListGet(transactionId, options);
+ async apiSysWechatPayRefundCallBackPost(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> {
+ const localVarAxiosArgs = await SysWechatPayApiAxiosParamCreator(configuration).apiSysWechatPayRefundCallBackPost(options);
+ return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+ const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
+ return axios.request(axiosRequestArgs);
+ };
+ },
+ /**
+ *
+ * @summary 根据支付Id获取退款信息列表
+ * @param {string} [transactionId]
+ * @param {string} [outTradeNumber]
+ * @param {*} [options] Override http request option.
+ * @throws {RequiredError}
+ */
+ async apiSysWechatPayRefundListGet(transactionId?: string, outTradeNumber?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> {
+ const localVarAxiosArgs = await SysWechatPayApiAxiosParamCreator(configuration).apiSysWechatPayRefundListGet(transactionId, outTradeNumber, options);
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
return axios.request(axiosRequestArgs);
@@ -880,6 +1005,16 @@ export const SysWechatPayApiFactory = function (configuration?: Configuration, b
async apiSysWechatPayPayCallBackPost(options?: AxiosRequestConfig): Promise> {
return SysWechatPayApiFp(configuration).apiSysWechatPayPayCallBackPost(options).then((request) => request(axios, basePath));
},
+ /**
+ *
+ * @summary 获取支付订单详情(微信接口) 🔖
+ * @param {string} tradeId
+ * @param {*} [options] Override http request option.
+ * @throws {RequiredError}
+ */
+ async apiSysWechatPayPayInfoFromWechatTradeIdGet(tradeId: string, options?: AxiosRequestConfig): Promise> {
+ return SysWechatPayApiFp(configuration).apiSysWechatPayPayInfoFromWechatTradeIdGet(tradeId, options).then((request) => request(axios, basePath));
+ },
/**
*
* @summary 获取支付订单详情 🔖
@@ -961,13 +1096,23 @@ export const SysWechatPayApiFactory = function (configuration?: Configuration, b
},
/**
*
- * @summary 根据支付Id获取退款信息列表 🔖
- * @param {string} [transactionId]
+ * @summary 微信退款回调(商户直连) 🔖
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
- async apiSysWechatPayRefundListGet(transactionId?: string, options?: AxiosRequestConfig): Promise> {
- return SysWechatPayApiFp(configuration).apiSysWechatPayRefundListGet(transactionId, options).then((request) => request(axios, basePath));
+ async apiSysWechatPayRefundCallBackPost(options?: AxiosRequestConfig): Promise> {
+ return SysWechatPayApiFp(configuration).apiSysWechatPayRefundCallBackPost(options).then((request) => request(axios, basePath));
+ },
+ /**
+ *
+ * @summary 根据支付Id获取退款信息列表
+ * @param {string} [transactionId]
+ * @param {string} [outTradeNumber]
+ * @param {*} [options] Override http request option.
+ * @throws {RequiredError}
+ */
+ async apiSysWechatPayRefundListGet(transactionId?: string, outTradeNumber?: string, options?: AxiosRequestConfig): Promise> {
+ return SysWechatPayApiFp(configuration).apiSysWechatPayRefundListGet(transactionId, outTradeNumber, options).then((request) => request(axios, basePath));
},
/**
*
@@ -1021,6 +1166,17 @@ export class SysWechatPayApi extends BaseAPI {
public async apiSysWechatPayPayCallBackPost(options?: AxiosRequestConfig) : Promise> {
return SysWechatPayApiFp(this.configuration).apiSysWechatPayPayCallBackPost(options).then((request) => request(this.axios, this.basePath));
}
+ /**
+ *
+ * @summary 获取支付订单详情(微信接口) 🔖
+ * @param {string} tradeId
+ * @param {*} [options] Override http request option.
+ * @throws {RequiredError}
+ * @memberof SysWechatPayApi
+ */
+ public async apiSysWechatPayPayInfoFromWechatTradeIdGet(tradeId: string, options?: AxiosRequestConfig) : Promise> {
+ return SysWechatPayApiFp(this.configuration).apiSysWechatPayPayInfoFromWechatTradeIdGet(tradeId, options).then((request) => request(this.axios, this.basePath));
+ }
/**
*
* @summary 获取支付订单详情 🔖
@@ -1110,14 +1266,25 @@ export class SysWechatPayApi extends BaseAPI {
}
/**
*
- * @summary 根据支付Id获取退款信息列表 🔖
- * @param {string} [transactionId]
+ * @summary 微信退款回调(商户直连) 🔖
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof SysWechatPayApi
*/
- public async apiSysWechatPayRefundListGet(transactionId?: string, options?: AxiosRequestConfig) : Promise> {
- return SysWechatPayApiFp(this.configuration).apiSysWechatPayRefundListGet(transactionId, options).then((request) => request(this.axios, this.basePath));
+ public async apiSysWechatPayRefundCallBackPost(options?: AxiosRequestConfig) : Promise> {
+ return SysWechatPayApiFp(this.configuration).apiSysWechatPayRefundCallBackPost(options).then((request) => request(this.axios, this.basePath));
+ }
+ /**
+ *
+ * @summary 根据支付Id获取退款信息列表
+ * @param {string} [transactionId]
+ * @param {string} [outTradeNumber]
+ * @param {*} [options] Override http request option.
+ * @throws {RequiredError}
+ * @memberof SysWechatPayApi
+ */
+ public async apiSysWechatPayRefundListGet(transactionId?: string, outTradeNumber?: string, options?: AxiosRequestConfig) : Promise> {
+ return SysWechatPayApiFp(this.configuration).apiSysWechatPayRefundListGet(transactionId, outTradeNumber, options).then((request) => request(this.axios, this.basePath));
}
/**
*
diff --git a/Web/src/api-services/models/sys-wechat-refund.ts b/Web/src/api-services/models/sys-wechat-refund.ts
index 628fc180..f6e8563a 100644
--- a/Web/src/api-services/models/sys-wechat-refund.ts
+++ b/Web/src/api-services/models/sys-wechat-refund.ts
@@ -106,7 +106,15 @@ export interface SysWechatRefund {
* @type {string}
* @memberof SysWechatRefund
*/
- outRefundNo: string;
+ outRefundNumber: string;
+
+ /**
+ * 微信接口退款ID
+ *
+ * @type {string}
+ * @memberof SysWechatRefund
+ */
+ refundId?: string | null;
/**
* 退款原因,示例:商品已售完
@@ -162,7 +170,15 @@ export interface SysWechatRefund {
* @type {string}
* @memberof SysWechatRefund
*/
- orderStatus?: string | null;
+ refundStatus?: string | null;
+
+ /**
+ * 支完成时间
+ *
+ * @type {Date}
+ * @memberof SysWechatRefund
+ */
+ successTime?: Date | null;
/**
* 关联的商户商品编码
diff --git a/Web/src/components/sysDict/sysDict.vue b/Web/src/components/sysDict/sysDict.vue
index d51b5ee7..6c7b8838 100644
--- a/Web/src/components/sysDict/sysDict.vue
+++ b/Web/src/components/sysDict/sysDict.vue
@@ -95,7 +95,7 @@ watch(
- emit('update:modelValue', newValue)">
+ emit('update:modelValue', newValue)" clearable>
diff --git a/Web/src/views/system/codeGen/component/genConfigDialog.vue b/Web/src/views/system/codeGen/component/genConfigDialog.vue
index db307ae2..581b30a2 100644
--- a/Web/src/views/system/codeGen/component/genConfigDialog.vue
+++ b/Web/src/views/system/codeGen/component/genConfigDialog.vue
@@ -38,6 +38,9 @@
+
+
+
@@ -205,6 +208,15 @@ const options = reactive({
default: 'statistical',
},
},
+ {
+ field: 'isGroupBy',
+ title: 'GroupBy',
+ minWidth: 70,
+ slots: {
+ edit: 'isGroupBy',
+ default: 'isGroupBy',
+ },
+ },
{
field: 'queryWhether',
title: '是否是查询',
diff --git a/Web/src/views/system/weChatPay/index.vue b/Web/src/views/system/weChatPay/index.vue
index a1ea84af..3427e330 100644
--- a/Web/src/views/system/weChatPay/index.vue
+++ b/Web/src/views/system/weChatPay/index.vue
@@ -10,7 +10,7 @@
- 查询
+ 查询
重置
@@ -31,10 +31,12 @@
完成
退款
+ 未支付
未完成
+
@@ -45,13 +47,14 @@
size="small"
text
type="primary"
- v-if="scope.row.qrcodeContent != null && scope.row.qrcodeContent != '' && (scope.row.tradeState === '' || !scope.row.tradeState)"
+ v-if="scope.row.qrcodeContent != null && scope.row.qrcodeContent != '' && (scope.row.tradeState === '' || !scope.row.tradeState || scope.row.tradeState == 'NOTPAY')"
@click="openQrDialog(scope.row.qrcodeContent)"
>
付款二维码
查看退款
全额退款
+ 刷新
@@ -75,16 +78,19 @@
新增模拟数据
-
+
-
+
+
+
+
-
+
-
+
- 完成
- 退款
+ 完成
+ 退款
未完成
@@ -134,7 +140,7 @@