feat: 自助充值

This commit is contained in:
tk
2025-06-27 19:19:35 +08:00
committed by nsnail
parent 94d0b7028f
commit e62153289f
83 changed files with 2112 additions and 110 deletions

View File

@ -1,3 +1,4 @@
USDT
上次执行时间
上次执行状态
上次执行耗时
@ -19,6 +20,8 @@
作业状态
信息
倒序排序
充值成功
充值订单导出
全部数据
公告
共青团员
@ -26,6 +29,7 @@
创建时间
初中
删除
到账确认中
包含
博士
博士后
@ -53,10 +57,11 @@
已校验
已读
并且
微信支付
成功
或者
属角色
属部门
属角色
属部门
手机
手机号
执行耗时
@ -71,6 +76,8 @@
接口路径
插入种子数据
操作系统
支付宝
支付超时
支出
收入
数据范围
@ -114,6 +121,7 @@
站内信导出
等于
等待发送
等待支付
管理员充值
管理员扣费
管理模块

View File

@ -33,6 +33,7 @@ XML注释文件不存在
手机号码不能为空
接口编码不存在
支付宝账号
支付方式不正确
政治面貌不正确
数据库同步开始
数据库服务器时钟偏移

View File

@ -1,8 +1,11 @@
[
{
"CnyToPointRate": 100,
"Enabled": true,
"Trc20ReceiptAddress": "TMTByCrcZkY7o8YMax1pXiYV5SUQBZEnCu",
"UsdToPointRate": 700,
"UserRegisterConfirm": false,
"UserRegisterDeptId": 372119301627909,
"UserRegisterRoleId": 371729946431493
"UserRegisterRoleId": 371729946431493,
}
]

View File

@ -2,12 +2,21 @@
{
"Enabled": true,
"ExecutionCron": "0 * * * * ?",
"HttpMethod": 3,
"JobName": "HTTP 请求测试",
"NextExecTime": "2020-09-13 12:26:40",
"NextTimeId": 1600000000,
"RequestUrl": "https://httpbin.org/ip",
"Status": 1,
"UserId": 370942943322181,
}
"HttpMethod": 7,
"JobName": "用户收入支出统计",
"RequestBody": "{\"Sql\":\"UPDATE Sys_UserWallet SET TotalIncome = (SELECT COALESCE(SUM(Amount), 0) FROM Sys_WalletTrade WHERE Sys_WalletTrade.OwnerId = Sys_UserWallet.Id AND Sys_WalletTrade.TradeDirection = 1);UPDATE Sys_UserWallet SET TotalExpenditure = (SELECT COALESCE(SUM(Amount), 0) FROM Sys_WalletTrade WHERE Sys_WalletTrade.OwnerId = Sys_UserWallet.Id AND Sys_WalletTrade.TradeDirection = 2);\",\"TimeoutSecs\":60}",
"RequestHeader": "{\"Content-Type\":\"application/json\"}",
"RequestUrl": "https://na.tools92.top/api/sys/tools/execute.sql",
"UserId": 664362432344581,
},
{
"Enabled": true,
"ExecutionCron": "0 * * * * ?",
"HttpMethod": 7,
"JobName": "充值到账确认",
"RequestBody": "{\"readRecordCount\":100}",
"RequestHeader": "{\"Content-Type\":\"application/json\"}",
"RequestUrl": "https://na.tools92.top/api/sys/deposit.order/received.confirmation",
"UserId": 664362432344581,
},
]

View File

@ -96,6 +96,17 @@
"Title": "交易流水",
"Type": 1
},
{
"Component": "sys/order",
"Icon": "el-icon-shopping-cart",
"Id": 690907673255944,
"Name": "sys/order",
"ParentId": 690906994118665,
"Path": "/finance/order",
"Sort": 98,
"Title": "充值订单",
"Type": 1
},
// ------------------------------ 系统管理 ------------------------------
{
"Icon": "sc-icon-App",

View File

@ -78,5 +78,29 @@
{
"ApiId": "api/sys/wallet.trade/get",
"RoleId": 371729946431493,
},
{
"ApiId": "api/sys/deposit.order/paged.query",
"RoleId": 371729946431493,
},
{
"ApiId": "api/sys/deposit.order/count.by",
"RoleId": 371729946431493,
},
{
"ApiId": "api/sys/deposit.order/create",
"RoleId": 371729946431493,
},
{
"ApiId": "api/sys/deposit.order/get",
"RoleId": 371729946431493,
},
{
"ApiId": "api/sys/deposit.order/get.deposit.config",
"RoleId": 371729946431493,
},
{
"ApiId": "api/sys/deposit.order/pay.confirm",
"RoleId": 371729946431493,
}
]

View File

@ -26,5 +26,9 @@
{
"MenuId": 690907673255943,
"RoleId": 371729946431493
},
{
"MenuId": 690907673255944,
"RoleId": 371729946431493
}
]

View File

@ -14,5 +14,13 @@
"Password": "A8E87D23-49BC-25A1-1C7C-59186BEF5D15",
"Token": "4208EA97-B32F-4E39-A290-4C0D27B61EBF",
"UserName": "user"
},
{
"DeptId": 372119301627909,
"Enabled": true,
"Id": 664362432344581,
"Password": "A8E87D23-49BC-25A1-1C7C-59186BEF5D15",
"Token": "751D599B-2B8C-417C-9565-CCF2363F5F6F",
"UserName": "jobs"
}
]

View File

@ -3,6 +3,10 @@
"Id": 370942943322181,
"AppConfig": "[]"
},
{
"Id": 664362432344581,
"AppConfig": "[]"
},
{
"Id": 560217289236492,
"AppConfig": "[]"

View File

@ -3,6 +3,10 @@
"RoleId": 370943613149253,
"UserId": 370942943322181
},
{
"RoleId": 370943613149253,
"UserId": 664362432344581
},
{
"RoleId": 371729946431493,
"UserId": 560217289236492

View File

@ -4,6 +4,11 @@
"OwnerDeptId": 372119301627909,
"OwnerId": 370942943322181,
},
{
"Id": 664362432344581,
"OwnerDeptId": 372119301627909,
"OwnerId": 664362432344581,
},
{
"Id": 560217289236492,
"OwnerDeptId": 372119301627909,

View File

@ -1,17 +1,17 @@
namespace NetAdmin.Domain.DbMaps.Dependency.Fields;
/// <summary>
/// 所有者字段接口
/// 归属字段接口
/// </summary>
public interface IFieldOwner
{
/// <summary>
/// 所有者部门编号
/// 归属部门编号
/// </summary>
long? OwnerDeptId { get; init; }
/// <summary>
/// 所有者用户编号
/// 归属用户编号
/// </summary>
long? OwnerId { get; init; }
}

View File

@ -6,6 +6,14 @@ namespace NetAdmin.Domain.DbMaps.Sys;
[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_Config))]
public record Sys_Config : VersionEntity, IFieldEnabled
{
/// <summary>
/// 人民币兑点数比率
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual int CnyToPointRate { get; init; }
/// <summary>
/// 是否启用
/// </summary>
@ -14,6 +22,22 @@ public record Sys_Config : VersionEntity, IFieldEnabled
[JsonIgnore]
public virtual bool Enabled { get; init; }
/// <summary>
/// Trc20收款地址
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_CHAR_34)]
[CsvIgnore]
[JsonIgnore]
public virtual string Trc20ReceiptAddress { get; init; }
/// <summary>
/// 美元兑点数比率
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual int UsdToPointRate { get; init; }
/// <summary>
/// 用户注册是否需要人工确认
/// </summary>

View File

@ -0,0 +1,115 @@
namespace NetAdmin.Domain.DbMaps.Sys;
/// <summary>
/// 充值订单表
/// </summary>
[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_DepositOrder))]
[SqlIndex( //
$"{Chars.FLG_DB_INDEX_PREFIX}{nameof(ActualPayAmount)}_{nameof(FinishTimestamp)}", $"{nameof(ActualPayAmount)},{nameof(FinishTimestamp)}", true)]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(PaymentFinger), nameof(PaymentFinger), true, WhenNotNull = true)]
public record Sys_DepositOrder : LiteVersionEntity, IFieldOwner
{
/// <summary>
/// 实际支付金额
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long ActualPayAmount { get; init; }
/// <summary>
/// 订单状态
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual DepositOrderStatues DepositOrderStatus { get; init; }
/// <summary>
/// 充值点数
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long DepositPoint { get; init; }
/// <summary>
/// 完成时间戳
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long FinishTimestamp { get; init; }
/// <summary>
/// 归属用户
/// </summary>
[CsvIgnore]
[JsonIgnore]
[Navigate(nameof(OwnerId))]
public Sys_User Owner { get; init; }
/// <summary>
/// 归属部门编号
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long? OwnerDeptId { get; init; }
/// <summary>
/// 归属用户编号
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long? OwnerId { get; init; }
/// <summary>
/// 付款账号
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)]
[CsvIgnore]
[JsonIgnore]
public virtual string PaidAccount { get; init; }
/// <summary>
/// 付款时间
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual DateTime? PaidTime { get; init; }
/// <summary>
/// 付款指纹
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
[CsvIgnore]
[JsonIgnore]
public virtual string PaymentFinger { get; init; }
/// <summary>
/// 支付方式
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual PaymentModes PaymentMode { get; init; }
/// <summary>
/// 收款账号
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)]
[CsvIgnore]
[JsonIgnore]
public virtual string ReceiptAccount { get; init; }
/// <summary>
/// 兑点数比率
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual int ToPointRate { get; init; }
}

View File

@ -40,7 +40,7 @@ public record Sys_DocCatalog : VersionEntity, IFieldOwner
public virtual string Name { get; init; }
/// <summary>
/// 所有者
/// 归属用户
/// </summary>
[CsvIgnore]
[JsonIgnore]
@ -48,7 +48,7 @@ public record Sys_DocCatalog : VersionEntity, IFieldOwner
public Sys_User Owner { get; init; }
/// <summary>
/// 所有者部门编号
/// 归属部门编号
/// </summary>
[Column]
[CsvIgnore]
@ -56,7 +56,7 @@ public record Sys_DocCatalog : VersionEntity, IFieldOwner
public virtual long? OwnerDeptId { get; init; }
/// <summary>
/// 所有者用户编号
/// 归属用户编号
/// </summary>
[Column]
[CsvIgnore]

View File

@ -41,7 +41,7 @@ public record Sys_DocContent : VersionEntity, IFieldEnabled, IFieldOwner
public virtual bool Enabled { get; init; }
/// <summary>
/// 所有者
/// 归属用户
/// </summary>
[CsvIgnore]
[JsonIgnore]
@ -49,7 +49,7 @@ public record Sys_DocContent : VersionEntity, IFieldEnabled, IFieldOwner
public Sys_User Owner { get; init; }
/// <summary>
/// 所有者部门编号
/// 归属部门编号
/// </summary>
[Column]
[CsvIgnore]
@ -57,7 +57,7 @@ public record Sys_DocContent : VersionEntity, IFieldEnabled, IFieldOwner
public virtual long? OwnerDeptId { get; init; }
/// <summary>
/// 所有者用户编号
/// 归属用户编号
/// </summary>
[Column]
[CsvIgnore]

View File

@ -35,7 +35,7 @@ public record Sys_JobRecord : LiteImmutableEntity
public int HttpStatusCode { get; init; }
/// <summary>
/// 所有者信息
/// 归属信息
/// </summary>
[CsvIgnore]
[JsonIgnore]

View File

@ -66,7 +66,7 @@ public record Sys_LoginLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFiel
public virtual string LoginUserName { get; protected init; }
/// <summary>
/// 所有者
/// 归属用户
/// </summary>
[CsvIgnore]
[JsonIgnore]
@ -74,7 +74,7 @@ public record Sys_LoginLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFiel
public Sys_User Owner { get; init; }
/// <summary>
/// 所有者部门编号
/// 归属部门编号
/// </summary>
[Column]
[CsvIgnore]
@ -82,7 +82,7 @@ public record Sys_LoginLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFiel
public virtual long? OwnerDeptId { get; init; }
/// <summary>
/// 所有者用户编号
/// 归属用户编号
/// </summary>
[Column]
[CsvIgnore]

View File

@ -76,7 +76,7 @@ public record Sys_RequestLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFi
public virtual int HttpStatusCode { get; init; }
/// <summary>
/// 所有者
/// 归属用户
/// </summary>
[CsvIgnore]
[JsonIgnore]
@ -84,7 +84,7 @@ public record Sys_RequestLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFi
public Sys_User Owner { get; init; }
/// <summary>
/// 所有者部门编号
/// 归属部门编号
/// </summary>
[Column]
[CsvIgnore]
@ -92,7 +92,7 @@ public record Sys_RequestLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFi
public virtual long? OwnerDeptId { get; init; }
/// <summary>
/// 所有者用户编号
/// 归属用户编号
/// </summary>
[Column]
[CsvIgnore]

View File

@ -20,7 +20,7 @@ public record Sys_User : VersionEntity, IFieldSummary, IFieldEnabled, IRegister
public virtual string Avatar { get; init; }
/// <summary>
/// 属部门
/// 属部门
/// </summary>
[CsvIgnore]
[JsonIgnore]
@ -84,7 +84,7 @@ public record Sys_User : VersionEntity, IFieldSummary, IFieldEnabled, IRegister
public Sys_UserProfile Profile { get; init; }
/// <summary>
/// 属角色
/// 属角色
/// </summary>
[CsvIgnore]
[JsonIgnore]

View File

@ -23,7 +23,7 @@ public record Sys_UserWallet : LiteVersionEntity, IFieldOwner
public virtual long FrozenBalance { get; init; }
/// <summary>
/// 所有者
/// 归属用户
/// </summary>
[CsvIgnore]
[JsonIgnore]
@ -31,7 +31,7 @@ public record Sys_UserWallet : LiteVersionEntity, IFieldOwner
public Sys_User Owner { get; init; }
/// <summary>
/// 所有者部门编号
/// 归属部门编号
/// </summary>
[Column]
[CsvIgnore]
@ -39,7 +39,7 @@ public record Sys_UserWallet : LiteVersionEntity, IFieldOwner
public virtual long? OwnerDeptId { get; init; }
/// <summary>
/// 所有者用户编号
/// 归属用户编号
/// </summary>
[Column]
[CsvIgnore]

View File

@ -31,7 +31,7 @@ public record Sys_WalletTrade : ImmutableEntity, IFieldOwner, IFieldSummary
public virtual long? BusinessOrderNumber { get; init; }
/// <summary>
/// 所有者
/// 归属用户
/// </summary>
[CsvIgnore]
[JsonIgnore]
@ -39,7 +39,7 @@ public record Sys_WalletTrade : ImmutableEntity, IFieldOwner, IFieldSummary
public Sys_User Owner { get; init; }
/// <summary>
/// 所有者部门编号
/// 归属部门编号
/// </summary>
[Column]
[CsvIgnore]
@ -47,7 +47,7 @@ public record Sys_WalletTrade : ImmutableEntity, IFieldOwner, IFieldSummary
public virtual long? OwnerDeptId { get; init; }
/// <summary>
/// 所有者用户编号
/// 归属用户编号
/// </summary>
[Column]
[CsvIgnore]

View File

@ -5,10 +5,25 @@ namespace NetAdmin.Domain.Dto.Sys.Config;
/// </summary>
public record CreateConfigReq : Sys_Config
{
/// <inheritdoc cref="Sys_Config.CnyToPointRate" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[Range(1, int.MaxValue)]
public override int CnyToPointRate { get; init; }
/// <inheritdoc cref="IFieldEnabled.Enabled" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override bool Enabled { get; init; }
/// <inheritdoc cref="Sys_Config.Trc20ReceiptAddress" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[Length(34, 34)]
public override string Trc20ReceiptAddress { get; init; }
/// <inheritdoc cref="Sys_Config.UsdToPointRate" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[Range(1, int.MaxValue)]
public override int UsdToPointRate { get; init; }
/// <inheritdoc cref="Sys_Config.UserRegisterConfirm" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override bool UserRegisterConfirm { get; init; }

View File

@ -8,6 +8,10 @@ namespace NetAdmin.Domain.Dto.Sys.Config;
/// </summary>
public record QueryConfigRsp : Sys_Config
{
/// <inheritdoc cref="Sys_Config.CnyToPointRate" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override int CnyToPointRate { get; init; }
/// <inheritdoc cref="IFieldCreatedTime.CreatedTime" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override DateTime CreatedTime { get; init; }
@ -20,6 +24,14 @@ public record QueryConfigRsp : Sys_Config
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
/// <inheritdoc cref="Sys_Config.Trc20ReceiptAddress" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string Trc20ReceiptAddress { get; init; }
/// <inheritdoc cref="Sys_Config.UsdToPointRate" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override int UsdToPointRate { get; init; }
/// <inheritdoc cref="Sys_Config.UserRegisterConfirm" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override bool UserRegisterConfirm { get; init; }

View File

@ -0,0 +1,36 @@
namespace NetAdmin.Domain.Dto.Sys.DepositOrder;
/// <summary>
/// 请求:创建充值订单
/// </summary>
public record CreateDepositOrderReq : Sys_DepositOrder, IValidatableObject
{
/// <inheritdoc cref="Sys_DepositOrder.ActualPayAmount" />
public override long ActualPayAmount { get; init; }
/// <inheritdoc cref="Sys_DepositOrder.DepositOrderStatus" />
public override DepositOrderStatues DepositOrderStatus { get; init; } = DepositOrderStatues.WaitingForPayment;
/// <inheritdoc cref="Sys_DepositOrder.DepositPoint" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[Range(100, long.MaxValue)]
public override long DepositPoint { get; init; }
/// <inheritdoc cref="Sys_DepositOrder.PaymentMode" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[EnumDataType(typeof(PaymentModes))]
public override PaymentModes PaymentMode { get; init; }
/// <inheritdoc cref="Sys_DepositOrder.ToPointRate" />
public override int ToPointRate { get; init; }
/// <inheritdoc />
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (PaymentMode != PaymentModes.USDT) {
yield return new ValidationResult(Ln., [nameof(PaymentMode)]);
}
yield return ValidationResult.Success;
}
}

View File

@ -0,0 +1,15 @@
namespace NetAdmin.Domain.Dto.Sys.DepositOrder;
/// <summary>
/// 请求:编辑充值订单
/// </summary>
public record EditDepositOrderReq : CreateDepositOrderReq
{
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
/// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; }
}

View File

@ -0,0 +1,19 @@
namespace NetAdmin.Domain.Dto.Sys.DepositOrder;
/// <summary>
/// 响应:查询充值订单
/// </summary>
public sealed record GetDepositConfigRsp : Sys_Config
{
/// <inheritdoc cref="Sys_Config.CnyToPointRate" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override int CnyToPointRate { get; init; }
/// <inheritdoc cref="Sys_Config.Trc20ReceiptAddress" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string Trc20ReceiptAddress { get; init; }
/// <inheritdoc cref="Sys_Config.UsdToPointRate" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override int UsdToPointRate { get; init; }
}

View File

@ -0,0 +1,11 @@
namespace NetAdmin.Domain.Dto.Sys.DepositOrder;
/// <summary>
/// 请求:支付确认
/// </summary>
public record PayConfirmReq : Sys_DepositOrder
{
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
}

View File

@ -0,0 +1,11 @@
namespace NetAdmin.Domain.Dto.Sys.DepositOrder;
/// <summary>
/// 请求:查询充值订单
/// </summary>
public sealed record QueryDepositOrderReq : Sys_DepositOrder
{
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
}

View File

@ -0,0 +1,73 @@
using NetAdmin.Domain.Dto.Sys.User;
namespace NetAdmin.Domain.Dto.Sys.DepositOrder;
/// <summary>
/// 响应:查询充值订单
/// </summary>
public record QueryDepositOrderRsp : Sys_DepositOrder
{
/// <inheritdoc cref="Sys_DepositOrder.ActualPayAmount" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long ActualPayAmount { get; init; }
/// <inheritdoc cref="IFieldCreatedTime.CreatedTime" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override DateTime CreatedTime { get; init; }
/// <inheritdoc cref="Sys_DepositOrder.DepositOrderStatus" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override DepositOrderStatues DepositOrderStatus { get; init; }
/// <inheritdoc cref="Sys_DepositOrder.DepositPoint" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long DepositPoint { get; init; }
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
/// <inheritdoc cref="IFieldModifiedTime.ModifiedTime" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override DateTime? ModifiedTime { get; init; }
/// <inheritdoc cref="Sys_DepositOrder.Owner" />
[CsvIgnore]
public new virtual QueryUserRsp Owner { get; init; }
/// <inheritdoc cref="IFieldOwner.OwnerDeptId" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override long? OwnerDeptId { get; init; }
/// <inheritdoc cref="IFieldOwner.OwnerId" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override long? OwnerId { get; init; }
/// <inheritdoc cref="Sys_DepositOrder.PaidAccount" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string PaidAccount { get; init; }
/// <inheritdoc cref="Sys_DepositOrder.PaidTime" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override DateTime? PaidTime { get; init; }
/// <inheritdoc cref="Sys_DepositOrder.PaymentFinger" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string PaymentFinger { get; init; }
/// <inheritdoc cref="Sys_DepositOrder.PaymentMode" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override PaymentModes PaymentMode { get; init; }
/// <inheritdoc cref="Sys_DepositOrder.ReceiptAccount" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string ReceiptAccount { get; init; }
/// <inheritdoc cref="Sys_DepositOrder.ToPointRate" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override int ToPointRate { get; init; }
/// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; }
}

View File

@ -0,0 +1,12 @@
namespace NetAdmin.Domain.Dto.Sys.DepositOrder;
/// <summary>
/// 请求:到账确认
/// </summary>
public record ReceivedConfirmationReq : DataAbstraction
{
/// <summary>
/// 读取前n条记录
/// </summary>
public int ReadRecordCount { get; init; }
}

View File

@ -19,10 +19,10 @@ public sealed record ExportUserRsp : QueryUserRsp
public override QueryDeptRsp Dept { get; init; }
/// <summary>
/// 属部门
/// 属部门
/// </summary>
[CsvIndex(5)]
[CsvName(nameof(Ln.属部门))]
[CsvName(nameof(Ln.属部门))]
public string DeptName { get; init; }
/// <inheritdoc />
@ -56,10 +56,10 @@ public sealed record ExportUserRsp : QueryUserRsp
public override string Mobile { get; init; }
/// <summary>
/// 属角色
/// 属角色
/// </summary>
[CsvIndex(4)]
[CsvName(nameof(Ln.属角色))]
[CsvName(nameof(Ln.属角色))]
public string RoleNames { get; init; }
/// <inheritdoc />

View File

@ -10,6 +10,11 @@ public abstract record ApiClientOptions : OptionAbstraction
/// </summary>
public string Gateway { get; set; }
/// <summary>
/// 超时时间
/// </summary>
public int TimeoutSecs { get; set; }
/// <summary>
/// 密钥
/// </summary>

View File

@ -0,0 +1,8 @@
using NetAdmin.Infrastructure.Configuration.Dependency;
namespace NetAdmin.Infrastructure.Configuration.Options;
/// <summary>
/// TronScan 配置
/// </summary>
public sealed record TronScanOptions : ApiClientOptions;

View File

@ -18,6 +18,7 @@ public static class Chars
public const string FLG_DB_EXCEPTION_IDX = "idx_";
public const string FLG_DB_EXCEPTION_PRIMARY_KEY_CONFLICT = "PRIMARY KEY";
public const string FLG_DB_EXCEPTION_UNIQUE_CONSTRAINT_CONFLICT = "UNIQUE constraint";
public const string FLG_DB_FIELD_TYPE_CHAR_34 = "char(34)";
public const string FLG_DB_FIELD_TYPE_NVARCHAR = "nvarchar";
public const string FLG_DB_FIELD_TYPE_NVARCHAR_1022 = "nvarchar(1022)";
public const string FLG_DB_FIELD_TYPE_NVARCHAR_127 = "nvarchar(127)";
@ -53,6 +54,7 @@ public static class Chars
public const string FLG_HTTP_HEADER_KEY_ACCESS_TOKEN = "ACCESS-TOKEN";
public const string FLG_HTTP_HEADER_KEY_AUTHORIZATION = "Authorization";
public const string FLG_HTTP_HEADER_KEY_REFERER = "Referer";
public const string FLG_HTTP_HEADER_KEY_TRON_PRO_API_KEY = "TRON-PRO-API-KEY";
public const string FLG_HTTP_HEADER_KEY_USER_AGENT = "User-Agent";
public const string FLG_HTTP_HEADER_KEY_X_ACCESS_TOKEN = "X-ACCESS-TOKEN";
public const string FLG_HTTP_HEADER_KEY_X_ACCESS_TOKEN_HEADER_KEY = "X-Authorization";

View File

@ -28,5 +28,6 @@ public static class Numbers
public const int SECS_CACHE_LOGIN_BY_USER_ID = 3600 * 24 * 30; // 缓存时间(秒):通过用户编号登录的用户信息
public const int SECS_REDIS_LOCK_EXPIRY = 60; // 秒Redis锁过期时间
public const int SECS_REDIS_LOCK_RETRY_DELAY = 1; // 秒Redis锁重试间隔
public const int SECS_TIMEOUT_HTTP_CLIENT = 15; // 超时时间HTTP 客户端
public const int SECS_TIMEOUT_JOB = 180; // 超时时间(秒):作业
}

View File

@ -0,0 +1,38 @@
namespace NetAdmin.Infrastructure.Enums;
/// <summary>
/// 充值订单状态
/// </summary>
[Export]
public enum DepositOrderStatues
{
/// <summary>
/// 等待支付
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.等待支付))]
WaitingForPayment = 1
,
/// <summary>
/// 到账确认中
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.到账确认中))]
PaymentConfirming = 2
,
/// <summary>
/// 充值成功
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.充值成功))]
Succeeded = 3
,
/// <summary>
/// 支付超时
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.支付超时))]
PaymentTimeout = 4
}

View File

@ -0,0 +1,30 @@
namespace NetAdmin.Infrastructure.Enums;
/// <summary>
/// 支付方式
/// </summary>
[Export]
public enum PaymentModes
{
/// <summary>
/// USDT
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.USDT))]
USDT = 1
,
/// <summary>
/// 支付宝
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.支付宝))]
Alipay = 2
,
/// <summary>
/// 微信支付
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.微信支付))]
WeChat = 3
}

View File

@ -10,7 +10,7 @@ public enum TradeDirections
/// 收入
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.收入))]
Income = 4
Income = 1
,
@ -18,5 +18,5 @@ public enum TradeDirections
/// 支出
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.支出))]
Expense = 5
Expense = 2
}

View File

@ -0,0 +1,43 @@
namespace NetAdmin.Infrastructure.Rpc.TronScan;
/// <summary>
/// 云码平台 客户端
/// </summary>
public interface ITronScanClient : IHttpDispatchProxy
{
private static readonly ILogger<ITronScanClient> _logger = App.GetService<ILogger<ITronScanClient>>();
/// <summary>
/// 异常拦截
/// </summary>
[Interceptor(InterceptorTypes.Exception)]
static Task OnExceptionAsync(HttpClient _, HttpResponseMessage rsp, string errors)
{
return rsp.LogExceptionAsync(errors, _logger);
}
/// <summary>
/// 请求拦截
/// </summary>
[Interceptor(InterceptorTypes.Request)]
static Task OnRequestAsyncAsync(HttpClient _, HttpRequestMessage req)
{
return req.LogAsync(_logger);
}
/// <summary>
/// 响应拦截
/// </summary>
[Interceptor(InterceptorTypes.Response)]
static Task OnResponsingAsync(HttpClient _, HttpResponseMessage rsp)
{
return rsp.LogAsync(_logger);
}
/// <summary>
/// 获取交易记录
/// </summary>
[Client(nameof(TronScanOptions))]
[Get("api/filter/trc20/transfers?limit={limit}&toAddress={toAddress}")]
Task<TransfersRsp> TransfersAsync([Headers(Chars.FLG_HTTP_HEADER_KEY_TRON_PRO_API_KEY)] string token, int limit, string toAddress);
}

View File

@ -0,0 +1,15 @@
namespace NetAdmin.Infrastructure.Rpc.TronScan;
/// <summary>
/// 表示一个普通的地址信息记录。
/// </summary>
/// <remarks>
/// 该记录包含一个布尔值属性,用于指示地址是否存在风险。
/// </remarks>
public record NormalAddressInfo
{
/// <summary>
/// 获取或设置地址是否存在风险的标识。
/// </summary>
public bool Risk { get; set; }
}

View File

@ -0,0 +1,57 @@
namespace NetAdmin.Infrastructure.Rpc.TronScan;
/// <summary>
/// 表示一个令牌的信息,包括发行者地址、令牌缩写、显示属性等详细信息。
/// </summary>
public record TokenInfo
{
/// <summary>
/// 获取或设置发行者的地址。
/// </summary>
public string IssuerAddr { get; set; }
/// <summary>
/// 获取或设置令牌的缩写。
/// </summary>
public string TokenAbbr { get; set; }
/// <summary>
/// 获取或设置令牌可显示的数量。
/// </summary>
public int TokenCanShow { get; set; }
/// <summary>
/// 获取或设置令牌的小数位数。
/// </summary>
public int TokenDecimal { get; set; }
/// <summary>
/// 获取或设置令牌的唯一标识符。
/// </summary>
public string TokenId { get; set; }
/// <summary>
/// 获取或设置令牌的级别。
/// </summary>
public string TokenLevel { get; set; }
/// <summary>
/// 获取或设置令牌的Logo URL或路径。
/// </summary>
public string TokenLogo { get; set; }
/// <summary>
/// 获取或设置令牌的名称。
/// </summary>
public string TokenName { get; set; }
/// <summary>
/// 获取或设置令牌的类型。
/// </summary>
public string TokenType { get; set; }
/// <summary>
/// 获取或设置是否为VIP令牌。
/// </summary>
public bool Vip { get; set; }
}

View File

@ -0,0 +1,116 @@
namespace NetAdmin.Infrastructure.Rpc.TronScan;
/// <summary>
/// 表示一个代币转账事件。
/// </summary>
public record TokenTransferInfo
{
/// <summary>
/// 获取或设置批准的转账金额。
/// </summary>
[JsonPropertyName("approval_amount")]
public string ApprovalAmount { get; set; }
/// <summary>
/// 获取或设置发生转账事件的区块编号。
/// </summary>
public int Block { get; set; }
/// <summary>
/// 获取或设置发生转账事件的区块的时间戳。
/// </summary>
[JsonPropertyName("block_ts")]
public long BlockTs { get; set; }
/// <summary>
/// 获取或设置一个值,指示转账是否已确认。
/// </summary>
public bool Confirmed { get; set; }
/// <summary>
/// 获取或设置代币的合约地址。
/// </summary>
[JsonPropertyName("contract_address")]
public string ContractAddress { get; set; }
/// <summary>
/// 获取或设置合约执行的返回结果。
/// </summary>
public string ContractRet { get; set; }
/// <summary>
/// 获取或设置合约类型。
/// </summary>
[JsonPropertyName("contract_type")]
public string ContractType { get; set; }
/// <summary>
/// 获取或设置事件类型。
/// </summary>
[JsonPropertyName("event_type")]
public string EventType { get; set; }
/// <summary>
/// 获取或设置转账的最终结果。
/// </summary>
public string FinalResult { get; set; }
/// <summary>
/// 获取或设置发送方地址。
/// </summary>
[JsonPropertyName("from_address")]
public string FromAddress { get; set; }
/// <summary>
/// 获取或设置一个值,指示发送方地址是否为合约地址。
/// </summary>
public bool FromAddressIsContract { get; set; }
/// <summary>
/// 获取或设置转账数量。
/// </summary>
public string Quant { get; set; }
/// <summary>
/// 获取或设置一个值,指示转账是否已回滚。
/// </summary>
public bool Revert { get; set; }
/// <summary>
/// 获取或设置一个值,指示转账是否为风险交易。
/// </summary>
public bool RiskTransaction { get; set; }
/// <summary>
/// 获取或设置转账状态。
/// </summary>
public int Status { get; set; }
/// <summary>
/// 获取或设置接收方地址。
/// </summary>
[JsonPropertyName("to_address")]
public string ToAddress { get; set; }
/// <summary>
/// 获取或设置一个值,指示接收方地址是否为合约地址。
/// </summary>
public bool ToAddressIsContract { get; set; }
/// <summary>
/// 获取或设置代币信息。
/// </summary>
public TokenInfo TokenInfo { get; set; }
/// <summary>
/// 获取或设置交易ID。
/// </summary>
[JsonPropertyName("transaction_id")]
public string TransactionId { get; set; }
/// <summary>
/// 获取或设置合约触发信息。
/// </summary>
[JsonPropertyName("trigger_info")]
public TriggerInfo TriggerInfo { get; set; }
}

View File

@ -0,0 +1,28 @@
namespace NetAdmin.Infrastructure.Rpc.TronScan;
/// <summary>
/// 表示包含合约信息及相关数据的根响应对象。
/// </summary>
public record TransfersRsp
{
/// <summary>
/// 获取或设置按地址键入的正常地址信息字典。
/// </summary>
public Dictionary<string, NormalAddressInfo> NormalAddressInfo { get; set; }
/// <summary>
/// 获取或设置范围内的项目总数。
/// </summary>
public int RangeTotal { get; set; }
/// <summary>
/// 获取或设置令牌转账列表。
/// </summary>
[JsonPropertyName("token_transfers")]
public IReadOnlyCollection<TokenTransferInfo> TokenTransfers { get; set; }
/// <summary>
/// 获取或设置项目的总数。
/// </summary>
public int Total { get; set; }
}

View File

@ -0,0 +1,34 @@
namespace NetAdmin.Infrastructure.Rpc.TronScan;
/// <summary>
/// 表示触发智能合约的信息。
/// </summary>
public record TriggerInfo
{
/// <summary>
/// 获取或设置调用合约时传递的值。
/// </summary>
[JsonPropertyName("call_value")]
public int CallValue { get; set; }
/// <summary>
/// 获取或设置合约的地址。
/// </summary>
[JsonPropertyName("contract_address")]
public string ContractAddress { get; set; }
/// <summary>
/// 获取或设置调用合约方法的数据。
/// </summary>
public string Data { get; set; }
/// <summary>
/// 获取或设置合约方法的标识符。
/// </summary>
public string Method { get; set; }
/// <summary>
/// 获取或设置合约方法的名称。
/// </summary>
public string MethodName { get; set; }
}

View File

@ -0,0 +1,28 @@
using NetAdmin.Domain.Dto.Sys.DepositOrder;
namespace NetAdmin.SysComponent.Application.Modules.Sys;
/// <summary>
/// 充值订单模块
/// </summary>
public interface IDepositOrderModule : ICrudModule<CreateDepositOrderReq, QueryDepositOrderRsp // 创建类型
, EditDepositOrderReq // 编辑类型
, QueryDepositOrderReq, QueryDepositOrderRsp // 查询类型
, DelReq // 删除类型
>
{
/// <summary>
/// 获取充值配置
/// </summary>
Task<GetDepositConfigRsp> GetDepositConfigAsync();
/// <summary>
/// 支付确认
/// </summary>
Task<int> PayConfirmAsync(PayConfirmReq req);
/// <summary>
/// 到账确认
/// </summary>
Task<int> ReceivedConfirmationAsync(ReceivedConfirmationReq req);
}

View File

@ -0,0 +1,6 @@
namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency;
/// <summary>
/// 充值订单服务
/// </summary>
public interface IDepositOrderService : IService, IDepositOrderModule;

View File

@ -0,0 +1,235 @@
using NetAdmin.Application.Extensions;
using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Sys.DepositOrder;
using NetAdmin.Domain.Dto.Sys.WalletTrade;
using NetAdmin.Domain.Extensions;
using NetAdmin.Infrastructure.Rpc.TronScan;
namespace NetAdmin.SysComponent.Application.Services.Sys;
/// <inheritdoc cref="IDepositOrderService" />
public sealed class DepositOrderService(BasicRepository<Sys_DepositOrder, long> rpo) //
: RepositoryService<Sys_DepositOrder, long, IDepositOrderService>(rpo), IDepositOrderService
{
/// <inheritdoc />
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
req.ThrowIfInvalid();
var ret = 0;
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var item in req.Items) {
ret += await DeleteAsync(item).ConfigureAwait(false);
}
return ret;
}
/// <inheritdoc />
public Task<long> CountAsync(QueryReq<QueryDepositOrderReq> req)
{
req.ThrowIfInvalid();
return QueryInternal(req).WithNoLockNoWait().CountAsync();
}
/// <inheritdoc />
public async Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryDepositOrderReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req with { Order = Orders.None })
.WithNoLockNoWait()
.GroupBy(req.GetToListExp<Sys_DepositOrder>())
.ToDictionaryAsync(a => a.Count())
.ConfigureAwait(false);
return ret.Select(x => new KeyValuePair<IImmutableDictionary<string, string>, int>(
req.RequiredFields.ToImmutableDictionary(
y => y, y => typeof(Sys_DepositOrder).GetProperty(y)!.GetValue(x.Key)?.ToString()), x.Value))
.Where(x => x.Key.Any(y => !y.Value.NullOrEmpty()))
.OrderByDescending(x => x.Value);
}
/// <exception cref="ArgumentOutOfRangeException">req</exception>
/// <inheritdoc />
public async Task<QueryDepositOrderRsp> CreateAsync(CreateDepositOrderReq req)
{
req.ThrowIfInvalid();
var config = await GetDepositConfigAsync().ConfigureAwait(false);
var toPointRate = req.PaymentMode switch {
PaymentModes.USDT => config.UsdToPointRate
, PaymentModes.Alipay => config.CnyToPointRate
, PaymentModes.WeChat => config.CnyToPointRate
, _ => throw new ArgumentOutOfRangeException(nameof(req))
};
var ret = await Rpo.InsertAsync(req with {
ActualPayAmount = (long)(((decimal)req.DepositPoint / toPointRate).Round(3) * 1000)
, ToPointRate = toPointRate
, ReceiptAccount = config.Trc20ReceiptAddress
})
.ConfigureAwait(false);
return ret.Adapt<QueryDepositOrderRsp>();
}
/// <inheritdoc />
public Task<int> DeleteAsync(DelReq req)
{
req.ThrowIfInvalid();
return Rpo.DeleteAsync(a => a.Id == req.Id);
}
/// <inheritdoc />
public async Task<QueryDepositOrderRsp> EditAsync(EditDepositOrderReq req)
{
req.ThrowIfInvalid();
#if DBTYPE_SQLSERVER
return (await UpdateReturnListAsync(req).ConfigureAwait(false)).FirstOrDefault()?.Adapt<QueryDepositOrderRsp>();
#else
return await UpdateAsync(req).ConfigureAwait(false) > 0
? await GetAsync(new QueryDepositOrderReq { Id = req.Id }).ConfigureAwait(false)
: null;
#endif
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryDepositOrderReq> req)
{
req.ThrowIfInvalid();
return ExportAsync<QueryDepositOrderReq, QueryDepositOrderRsp>(QueryInternal, req, Ln.);
}
/// <inheritdoc />
public async Task<QueryDepositOrderRsp> GetAsync(QueryDepositOrderReq req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(new QueryReq<QueryDepositOrderReq> { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false);
return ret.Adapt<QueryDepositOrderRsp>();
}
/// <inheritdoc />
public async Task<GetDepositConfigRsp> GetDepositConfigAsync()
{
var ret = await S<IConfigService>().GetLatestConfigAsync().ConfigureAwait(false);
return ret.Adapt<GetDepositConfigRsp>();
}
/// <inheritdoc />
public async Task<PagedQueryRsp<QueryDepositOrderRsp>> PagedQueryAsync(PagedQueryReq<QueryDepositOrderReq> req)
{
req.ThrowIfInvalid();
var list = await QueryInternal(req)
.Include(a => a.Owner)
.Page(req.Page, req.PageSize)
.WithNoLockNoWait()
.Count(out var total)
.ToListAsync(req)
.ConfigureAwait(false);
return new PagedQueryRsp<QueryDepositOrderRsp>(req.Page, req.PageSize, total, list.Adapt<IEnumerable<QueryDepositOrderRsp>>());
}
/// <inheritdoc />
public Task<int> PayConfirmAsync(PayConfirmReq req)
{
req.ThrowIfInvalid();
return UpdateAsync(req with { DepositOrderStatus = DepositOrderStatues.PaymentConfirming }, [nameof(req.DepositOrderStatus)], null
, a => a.DepositOrderStatus == DepositOrderStatues.WaitingForPayment && a.Id == req.Id, null, true);
}
/// <inheritdoc />
public async Task<IEnumerable<QueryDepositOrderRsp>> QueryAsync(QueryReq<QueryDepositOrderReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req).WithNoLockNoWait().Take(req.Count).ToListAsync(req).ConfigureAwait(false);
return ret.Adapt<IEnumerable<QueryDepositOrderRsp>>();
}
/// <inheritdoc />
public async Task<int> ReceivedConfirmationAsync(ReceivedConfirmationReq req)
{
req.ThrowIfInvalid();
var ret = 0;
var config = await S<IConfigService>().GetLatestConfigAsync().ConfigureAwait(false);
var waitConfirmList = (await QueryAsync(new QueryReq<QueryDepositOrderReq> {
Count = Numbers.MAX_LIMIT_QUERY_PAGE_SIZE
, DynamicFilter
= new DynamicFilterInfo {
Field = nameof(
QueryDepositOrderReq.DepositOrderStatus)
, Operator = DynamicFilterOperators.Eq
, Value = DepositOrderStatues.PaymentConfirming
}
, Order = Orders.Ascending
, Prop = nameof(QueryDepositOrderReq.Id)
})
.ConfigureAwait(false)).ToList();
var apiResult = await S<ITronScanClient>()
.TransfersAsync(S<IOptions<TronScanOptions>>().Value.Token, req.ReadRecordCount, config.Trc20ReceiptAddress)
.ConfigureAwait(false);
foreach (var apiItem in apiResult.TokenTransfers.Where(x => x.TokenInfo.TokenAbbr == "USDT" && x.Confirmed && x.ContractRet == "SUCCESS" &&
x.FinalResult == "SUCCESS")) {
var order = waitConfirmList.SingleOrDefault(x => x.ActualPayAmount == apiItem.Quant.Int64() / 1000);
if (order == null || order.CreatedTime > apiItem.BlockTs.Time()) {
continue;
}
try {
await S<UnitOfWorkManager>()
.AtomicOperateAsync(async () => {
var updated = await UpdateAsync( //
new Sys_DepositOrder {
DepositOrderStatus = DepositOrderStatues.Succeeded
, Version = order.Version
, FinishTimestamp = DateTime.Now.TimeUnixUtcMs()
, PaidAccount = apiItem.FromAddress
, PaidTime = apiItem.BlockTs.Time()
, PaymentFinger = apiItem.TransactionId
}, [nameof(Sys_DepositOrder.DepositOrderStatus), nameof(Sys_DepositOrder.FinishTimestamp), nameof(Sys_DepositOrder.PaidAccount), nameof(Sys_DepositOrder.PaidTime), nameof(Sys_DepositOrder.PaymentFinger)]
, null, a => a.Id == order.Id && a.DepositOrderStatus == DepositOrderStatues.PaymentConfirming)
.ConfigureAwait(false);
if (updated != 1) {
throw new NetAdminUnexpectedException(Ln.);
}
_ = await S<IWalletTradeService>()
.CreateAsync(new CreateWalletTradeReq {
Amount = order.DepositPoint
, BusinessOrderNumber = order.Id
, TradeDirection = TradeDirections.Income
, TradeType = TradeTypes.SelfDeposit
, OwnerId = order.OwnerId
})
.ConfigureAwait(false) ?? throw new NetAdminUnexpectedException(Ln.);
})
.ConfigureAwait(false);
ret++;
}
catch {
// ignore
}
}
return ret;
}
private ISelect<Sys_DepositOrder> QueryInternal(QueryReq<QueryDepositOrderReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);
if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) {
ret = ret.OrderByDescending(a => a.Id);
}
return ret;
}
}

View File

@ -0,0 +1,6 @@
namespace NetAdmin.SysComponent.Cache.Sys.Dependency;
/// <summary>
/// 充值订单缓存
/// </summary>
public interface IDepositOrderCache : ICache<IDistributedCache, IDepositOrderService>, IDepositOrderModule;

View File

@ -0,0 +1,86 @@
using NetAdmin.Domain.Dto.Sys.DepositOrder;
namespace NetAdmin.SysComponent.Cache.Sys;
/// <inheritdoc cref="IDepositOrderCache" />
public sealed class DepositOrderCache(IDistributedCache cache, IDepositOrderService service)
: DistributedCache<IDepositOrderService>(cache, service), IScoped, IDepositOrderCache
{
/// <inheritdoc />
public Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
return Service.BulkDeleteAsync(req);
}
/// <inheritdoc />
public Task<long> CountAsync(QueryReq<QueryDepositOrderReq> req)
{
return Service.CountAsync(req);
}
/// <inheritdoc />
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryDepositOrderReq> req)
{
return Service.CountByAsync(req);
}
/// <inheritdoc />
public Task<QueryDepositOrderRsp> CreateAsync(CreateDepositOrderReq req)
{
return Service.CreateAsync(req);
}
/// <inheritdoc />
public Task<int> DeleteAsync(DelReq req)
{
return Service.DeleteAsync(req);
}
/// <inheritdoc />
public Task<QueryDepositOrderRsp> EditAsync(EditDepositOrderReq req)
{
return Service.EditAsync(req);
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryDepositOrderReq> req)
{
return Service.ExportAsync(req);
}
/// <inheritdoc />
public Task<QueryDepositOrderRsp> GetAsync(QueryDepositOrderReq req)
{
return Service.GetAsync(req);
}
/// <inheritdoc />
public Task<GetDepositConfigRsp> GetDepositConfigAsync()
{
return Service.GetDepositConfigAsync();
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryDepositOrderRsp>> PagedQueryAsync(PagedQueryReq<QueryDepositOrderReq> req)
{
return Service.PagedQueryAsync(req);
}
/// <inheritdoc />
public Task<int> PayConfirmAsync(PayConfirmReq req)
{
return Service.PayConfirmAsync(req);
}
/// <inheritdoc />
public Task<IEnumerable<QueryDepositOrderRsp>> QueryAsync(QueryReq<QueryDepositOrderReq> req)
{
return Service.QueryAsync(req);
}
/// <inheritdoc />
public Task<int> ReceivedConfirmationAsync(ReceivedConfirmationReq req)
{
return Service.ReceivedConfirmationAsync(req);
}
}

View File

@ -0,0 +1,122 @@
using NetAdmin.Domain.Dto.Sys.DepositOrder;
namespace NetAdmin.SysComponent.Host.Controllers.Sys;
/// <summary>
/// 充值订单服务
/// </summary>
[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))]
[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)]
public sealed class DepositOrderController(IDepositOrderCache cache)
: ControllerBase<IDepositOrderCache, IDepositOrderService>(cache), IDepositOrderModule
{
/// <summary>
/// 批量删除充值订单
/// </summary>
[Transaction]
public Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
return Cache.BulkDeleteAsync(req);
}
/// <summary>
/// 充值订单计数
/// </summary>
public Task<long> CountAsync(QueryReq<QueryDepositOrderReq> req)
{
return Cache.CountAsync(req);
}
/// <summary>
/// 充值订单分组计数
/// </summary>
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryDepositOrderReq> req)
{
return Cache.CountByAsync(req);
}
/// <summary>
/// 创建充值订单
/// </summary>
[Transaction]
public Task<QueryDepositOrderRsp> CreateAsync(CreateDepositOrderReq req)
{
return Cache.CreateAsync(req);
}
/// <summary>
/// 删除充值订单
/// </summary>
[Transaction]
public Task<int> DeleteAsync(DelReq req)
{
return Cache.DeleteAsync(req);
}
/// <summary>
/// 编辑充值订单
/// </summary>
[Transaction]
public Task<QueryDepositOrderRsp> EditAsync(EditDepositOrderReq req)
{
return Cache.EditAsync(req);
}
/// <summary>
/// 导出充值订单
/// </summary>
[NonAction]
public Task<IActionResult> ExportAsync(QueryReq<QueryDepositOrderReq> req)
{
return Cache.ExportAsync(req);
}
/// <summary>
/// 获取单个充值订单
/// </summary>
public Task<QueryDepositOrderRsp> GetAsync(QueryDepositOrderReq req)
{
return Cache.GetAsync(req);
}
/// <summary>
/// 获取充值配置
/// </summary>
public Task<GetDepositConfigRsp> GetDepositConfigAsync()
{
return Cache.GetDepositConfigAsync();
}
/// <summary>
/// 分页查询充值订单
/// </summary>
public Task<PagedQueryRsp<QueryDepositOrderRsp>> PagedQueryAsync(PagedQueryReq<QueryDepositOrderReq> req)
{
return Cache.PagedQueryAsync(req);
}
/// <summary>
/// 支付确认
/// </summary>
public Task<int> PayConfirmAsync(PayConfirmReq req)
{
return Cache.PayConfirmAsync(req);
}
/// <summary>
/// 查询充值订单
/// </summary>
[NonAction]
public Task<IEnumerable<QueryDepositOrderRsp>> QueryAsync(QueryReq<QueryDepositOrderReq> req)
{
return Cache.QueryAsync(req);
}
/// <summary>
/// 到账确认
/// </summary>
public Task<int> ReceivedConfirmationAsync(ReceivedConfirmationReq req)
{
return Cache.ReceivedConfirmationAsync(req);
}
}

View File

@ -3,6 +3,7 @@ using NetAdmin.Domain.Contexts;
using NetAdmin.Domain.Events;
using NetAdmin.Host.Filters;
using NetAdmin.Host.Middlewares;
using NetAdmin.Infrastructure.Configuration.Dependency;
using NetAdmin.Infrastructure.Schedule;
using NetAdmin.SysComponent.Host.Utils;
using FreeSqlBuilder = NetAdmin.Infrastructure.Utils.FreeSqlBuilder;
@ -104,6 +105,31 @@ public static class ServiceCollectionExtensions
}
}
/// <summary>
/// 添加 TronScan 客户端
/// </summary>
public static IServiceCollection AddTronScanClient(this IServiceCollection me)
{
_ = me.AddHttpClient(nameof(TronScanOptions), ConfigClient<TronScanOptions>);
return me;
}
private static void ConfigClient<T>(HttpClient client)
where T : ApiClientOptions, new()
{
ConfigClient<T>(client, Numbers.SECS_TIMEOUT_HTTP_CLIENT);
}
private static void ConfigClient<T>(HttpClient client, int timeoutSecs)
where T : ApiClientOptions, new()
{
var options = App.GetOptions<T>();
client.Timeout = TimeSpan.FromSeconds(options.TimeoutSecs > 0 ? options.TimeoutSecs : timeoutSecs);
client.DefaultRequestHeaders.Add("User-Agent", nameof(NetAdmin));
client.BaseAddress = new Uri($"{options.Gateway}/");
}
private static void RunJob(IEnumerable<KeyValuePair<Type, JobConfigAttribute>> jobTypes)
{
foreach (var job in jobTypes) {

View File

@ -149,7 +149,7 @@ public sealed class SqlAuditor : ISingleton
}
/// <summary>
/// 设置所有者
/// 设置归属
/// </summary>
private static void SetOwner(AuditValueEventArgs e, ContextUserInfo userInfo)
{

View File

@ -37,7 +37,7 @@ public sealed class JwtHandler : AppAuthorizeHandler
return true;
}
// 获取属角色接口权限 进行核对
// 获取属角色接口权限 进行核对
if (role.ApiIds?.Contains(path) ?? false) {
return true;
}

View File

@ -95,6 +95,7 @@ namespace YourSolution.AdmServer.Host
.AddContextUserInfo() // 添加上下文用户信息
.AddRedisCache() // 添加 Redis 缓存
.AddSchedules() // 添加计划任务
.AddTronScanClient() // 添加 TronScan 客户端
// IMvcBuilder
.AddControllers() // 添加控制器

View File

@ -94,4 +94,9 @@
"Secure": false
}
},
// TronScan 客户端配置
"TronScan": {
"Gateway": "https://apilist.tronscanapi.com",
"Token": "fc6629dc-0139-4238-bead-8db7c45ec1e2"
},
}

View File

@ -21,6 +21,7 @@
"markdown-it": "14.1.0",
"markdown-it-emoji": "3.0.0",
"nprogress": "0.2.0",
"qrcode-svg": "1.1.0",
"sortablejs": "1.15.6",
"vkbeautify": "0.99.3",
"vue": "3.5.17",

View File

@ -0,0 +1,117 @@
/**
* 充值订单服务
* @module @/api/sys/deposit.order
*/
import config from '@/config'
import http from '@/utils/request'
export default {
/**
* 批量删除充值订单
*/
bulkDelete: {
url: `${config.API_URL}/api/sys/deposit.order/bulk.delete`,
name: `批量删除充值订单`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 充值订单计数
*/
count: {
url: `${config.API_URL}/api/sys/deposit.order/count`,
name: `充值订单计数`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 充值订单分组计数
*/
countBy: {
url: `${config.API_URL}/api/sys/deposit.order/count.by`,
name: `充值订单分组计数`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 创建充值订单
*/
create: {
url: `${config.API_URL}/api/sys/deposit.order/create`,
name: `创建充值订单`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 删除充值订单
*/
delete: {
url: `${config.API_URL}/api/sys/deposit.order/delete`,
name: `删除充值订单`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 编辑充值订单
*/
edit: {
url: `${config.API_URL}/api/sys/deposit.order/edit`,
name: `编辑充值订单`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 获取单个充值订单
*/
get: {
url: `${config.API_URL}/api/sys/deposit.order/get`,
name: `获取单个充值订单`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 获取充值配置
*/
getDepositConfig: {
url: `${config.API_URL}/api/sys/deposit.order/get.deposit.config`,
name: `获取充值配置`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 分页查询充值订单
*/
pagedQuery: {
url: `${config.API_URL}/api/sys/deposit.order/paged.query`,
name: `分页查询充值订单`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 支付确认
*/
payConfirm: {
url: `${config.API_URL}/api/sys/deposit.order/pay.confirm`,
name: `支付确认`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
}

View File

@ -188,7 +188,7 @@
<scContextmenuItem :title="$t('复制')" command="copy" divided icon="el-icon-copy-document" suffix="C"></scContextmenuItem>
<scContextmenuItem
v-if="contextOpers.includes('add')"
:title="$t('新')"
:title="$t('新')"
command="add"
divided
icon="el-icon-plus"

View File

@ -90,6 +90,8 @@ export default {
// Object.keys(item).forEach((x) => delete item[x])
// Object.assign(item, data)
// })
} else {
table.refresh()
}
},
}

View File

@ -99,9 +99,9 @@ export default {
性别: 'Gender',
成功: 'Successful',
我的消息: 'My messages',
属字典: 'Dictionary',
属角色: 'Role',
属部门: 'Department',
属字典: 'Dictionary',
属角色: 'Role',
属部门: 'Department',
手机号: 'Mobile',
手机号码: 'Mobile phone number',
手风琴菜单: 'Accordion menu',
@ -131,13 +131,13 @@ export default {
整页路由: 'Full page routing',
文化程度: 'Educational level',
斑马纹: 'Zebra pattern',
作业: 'Add job',
字典项: 'Add Dictionary Item',
消息: 'Add Message',
用户: 'Add User',
角色: 'Add Role',
部门: 'Add Department',
配置: 'Add Configuration',
作业: 'Add job',
字典项: 'Add Dictionary Item',
消息: 'Add Message',
用户: 'Add User',
角色: 'Add Role',
部门: 'Add Department',
配置: 'Add Configuration',
新密码: 'New password',
新手机号码: 'New mobile phone number',
新手机验证码: 'New mobile verification code',
@ -379,7 +379,7 @@ export default {
响应体: 'Response body',
响应头: 'Response header',
执行时间编号: 'Execution time ID',
字典目录: 'Add dictionary catalog',
字典目录: 'Add dictionary catalog',
字典名称: 'Dictionary name',
字典编码: 'Dictionary code',
父路径: 'Parent path',
@ -579,7 +579,7 @@ export default {
'结束值(毫秒)': 'End Value (ms)',
'起始值(毫秒)': 'Starting Value (ms)',
复制: 'Copy',
: 'New',
: 'New',
'数据已导出(上限 {n} 条)': 'Data exported (Maximum {n} entries)',
同步成功: 'Synchronization succeeded',
缓存详情: 'Cache details',
@ -587,11 +587,11 @@ export default {
数据权限: 'Data Permissions',
首页视图: 'Home view',
删除配置: 'Delete the configuration',
属字典目录: 'Dictionary catalog',
属字典目录: 'Dictionary catalog',
删除字典项: 'Delete dictionary entries',
接口总数: 'Total number of apis',
属文档分类: 'Document catalog',
请选择属文档分类: 'Please select the document category',
属文档分类: 'Document catalog',
请选择属文档分类: 'Please select the document category',
请输入文档标题: 'Please enter a title for the document',
请输入文档内容: 'Please enter the content of the document',
请选择档案可见性: 'Please select profile visibility',

View File

@ -99,9 +99,9 @@ export default {
性别: '性别',
成功: '成功',
我的消息: '我的消息',
属字典: '属字典',
属角色: '属角色',
属部门: '属部门',
属字典: '属字典',
属角色: '属角色',
属部门: '属部门',
手机号: '手机号',
手机号码: '手机号码',
手风琴菜单: '手风琴菜单',
@ -131,13 +131,13 @@ export default {
整页路由: '整页路由',
文化程度: '文化程度',
斑马纹: '斑马纹',
作业: '新作业',
字典项: '新字典项',
消息: '新消息',
用户: '新用户',
角色: '新角色',
部门: '新部门',
配置: '新配置',
作业: '新作业',
字典项: '新字典项',
消息: '新消息',
用户: '新用户',
角色: '新角色',
部门: '新部门',
配置: '新配置',
新密码: '新密码',
新手机号码: '新手机号码',
新手机验证码: '新手机验证码',
@ -378,7 +378,7 @@ export default {
响应体: '响应体',
响应头: '响应头',
执行时间编号: '执行时间编号',
字典目录: '新字典目录',
字典目录: '新字典目录',
字典名称: '字典名称',
字典编码: '字典编码',
父路径: '父路径',
@ -577,7 +577,7 @@ export default {
'结束值(毫秒)': '结束值(毫秒)',
'起始值(毫秒)': '起始值(毫秒)',
复制: '复制',
: '新',
: '新',
'数据已导出(上限 {n} 条)': '数据已导出(上限 {n} 条)',
同步成功: '同步成功',
缓存详情: '缓存详情',
@ -585,11 +585,11 @@ export default {
数据权限: '数据权限',
首页视图: '首页视图',
删除配置: '删除配置',
属字典目录: '属字典目录',
属字典目录: '属字典目录',
删除字典项: '删除字典项',
接口总数: '接口总数',
属文档分类: '属文档分类',
请选择属文档分类: '请选择属文档分类',
属文档分类: '属文档分类',
请选择属文档分类: '请选择属文档分类',
请输入文档标题: '请输入文档标题',
请输入文档内容: '请输入文档内容',
请选择档案可见性: '请选择档案可见性',

View File

@ -85,6 +85,11 @@
</template>
</el-table-column>
</el-table-column>
<el-table-column :label="$t('财务配置')" align="center">
<el-table-column :label="$t('人民币兑点数比率')" align="center" prop="cnyToPointRate" width="150" />
<el-table-column :label="$t('美元兑点数比率')" align="center" prop="usdToPointRate" width="150" />
<el-table-column :label="$t('USDT 收款地址')" align="center" prop="trc20ReceiptAddress" width="300" />
</el-table-column>
<el-table-column :label="$t('启用')" align="center" prop="enabled" sortable="custom" width="100">
<template #default="{ row }">
<el-switch v-model="row.enabled" @change="changeSwitch($event, row)"></el-switch>
@ -93,7 +98,7 @@
<naColOperation
:buttons="naColOperation.buttons.concat(naColOperation.delButton(this.$t('删除配置'), $API.sys_config.delete))"
:vue="this"
width="150" />
width="120" />
</scTable>
</el-main>
</el-container>

View File

@ -25,6 +25,19 @@
</el-form-item>
</div>
</el-collapse-item>
<el-collapse-item :title="$t('财务配置')" name="2">
<div style="margin: 1rem">
<el-form-item :label="$t('人民币兑点数比率')" prop="cnyToPointRate">
<el-input-number v-model="form.cnyToPointRate" :max="999999999" :min="1"></el-input-number>
</el-form-item>
<el-form-item :label="$t('美元兑点数比率')" prop="usdToPointRate">
<el-input-number v-model="form.usdToPointRate" :max="999999999" :min="1"></el-input-number>
</el-form-item>
<el-form-item :label="$t('USDT 收款地址')" prop="trc20ReceiptAddress">
<el-input v-model="form.trc20ReceiptAddress" maxlength="34" placeholder="placeholder"></el-input>
</el-form-item>
</div>
</el-collapse-item>
</el-collapse>
<el-form-item :label="$t('启用')" prop="enabled">
@ -70,7 +83,7 @@ export default {
userRegisterRoleId: [{ required: true, message: '请选择默认角色' }],
},
titleMap: {
add: this.$t('新配置'),
add: this.$t('新配置'),
edit: this.$t('编辑配置'),
view: this.$t('查看配置'),
},

View File

@ -81,7 +81,7 @@ export default {
},
tabId: '0',
titleMap: {
add: this.$t('新部门'),
add: this.$t('新部门'),
edit: this.$t('编辑部门'),
view: this.$t('查看部门'),
},

View File

@ -4,7 +4,7 @@
<el-tabs tab-position="top">
<el-tab-pane :label="$t('基本信息')">
<el-form :disabled="mode === 'view'" :model="form" :rules="rules" label-width="10rem" ref="dialogForm">
<el-form-item :label="$t('属字典目录')" prop="catalogId">
<el-form-item :label="$t('属字典目录')" prop="catalogId">
<catalog-select v-model="form.catalogId" class="w100p" />
</el-form-item>
<el-form-item :label="$t('项名')" prop="key">
@ -49,12 +49,12 @@ export default {
mode: 'add',
//验证规则
rules: {
catalogId: [{ required: true, message: '请选择属字典目录' }],
catalogId: [{ required: true, message: '请选择属字典目录' }],
key: [{ required: true, message: '请输入项名' }],
value: [{ required: true, message: '请输入项值' }],
},
titleMap: {
add: this.$t('新字典项'),
add: this.$t('新字典项'),
edit: this.$t('编辑字典项'),
view: this.$t('查看字典项'),
},

View File

@ -35,7 +35,7 @@ export default {
name: [{ required: true, message: '请输入字典目录名称' }],
},
titleMap: {
add: this.$t('新字典目录'),
add: this.$t('新字典目录'),
edit: this.$t('编辑字典目录'),
},
visible: false,

View File

@ -4,7 +4,7 @@
<el-tabs tab-position="top">
<el-tab-pane :label="$t('基本信息')">
<el-form :disabled="mode === 'view'" :model="form" :rules="rules" label-width="10rem" ref="dialogForm">
<el-form-item :label="$t('属文档分类')" prop="catalogId">
<el-form-item :label="$t('属文档分类')" prop="catalogId">
<catalog-select v-model="form.catalogId" class="w100p" />
</el-form-item>
<el-form-item :label="$t('文档标题')" prop="title">
@ -60,13 +60,13 @@ export default {
mode: 'add',
//验证规则
rules: {
catalogId: [{ required: true, message: this.$t('请选择属文档分类') }],
catalogId: [{ required: true, message: this.$t('请选择属文档分类') }],
title: [{ required: true, message: this.$t('请输入文档标题') }],
body: [{ required: true, message: this.$t('请输入文档内容') }],
visibility: [{ required: true, message: this.$t('请选择档案可见性') }],
},
titleMap: {
add: this.$t('新文档'),
add: this.$t('新文档'),
edit: this.$t('编辑文档'),
view: this.$t('查看文档'),
},

View File

@ -35,7 +35,7 @@ export default {
name: [{ required: true, message: '请输入文档分类名称' }],
},
titleMap: {
add: this.$t('新文档分类'),
add: this.$t('新文档分类'),
edit: this.$t('编辑文档分类'),
},
visible: false,

View File

@ -204,7 +204,7 @@ export default {
},
tabId: '0',
titleMap: {
add: this.$t('新作业'),
add: this.$t('新作业'),
edit: this.$t('编辑作业'),
view: this.$t('查看作业'),
},

View File

@ -133,7 +133,7 @@ export default {
//验证规则
rules: {},
titleMap: {
add: this.$t('新作业记录'),
add: this.$t('新作业记录'),
edit: this.$t('编辑作业记录'),
view: this.$t('查看作业记录'),
},

View File

@ -5,7 +5,7 @@
</el-col>
<template v-else>
<el-col>
<h2>{{ form.meta.title || '新菜单' }}</h2>
<h2>{{ form.meta.title || '新菜单' }}</h2>
<el-form :model="form" :rules="rules" label-width="15rem" ref="dialogForm">
<el-form-item :label="$t('显示名称')" prop="meta.title">
<el-input v-model="form.meta.title" :placeholder="$t('菜单显示名字')" clearable></el-input>

View File

@ -117,7 +117,7 @@ export default {
],
},
titleMap: {
add: this.$t('新消息'),
add: this.$t('新消息'),
edit: this.$t('编辑消息'),
view: this.$t('查看消息'),
},

View File

@ -0,0 +1,351 @@
<template>
<el-container>
<el-header v-loading="statistics.total === '...'" class="el-header-statistics">
<el-row :gutter="15">
<el-col :lg="24">
<el-card shadow="never">
<scStatistic :title="$t('总数')" :value="statistics.total" group-separator></scStatistic>
</el-card>
</el-col>
</el-row>
</el-header>
<el-header class="el-header-select-filter">
<scSelectFilter
:data="[
{
title: $t('订单状态'),
key: 'depositOrderStatus',
options: [
{ label: '全部', value: '' },
...Object.entries(this.$GLOBAL.enums.depositOrderStatues).map((x) => {
return {
value: x[0],
label: x[1][1],
badge: this.statistics.depositOrderStatus?.find(
(y) => y.key.depositOrderStatus.toLowerCase() === x[0].toLowerCase(),
)?.value,
}
}),
],
},
{
title: $t('支付方式'),
key: 'paymentMode',
options: [
{ label: '全部', value: '' },
...Object.entries(this.$GLOBAL.enums.paymentModes).map((x) => {
return {
value: x[0],
label: x[1][1],
badge: this.statistics.paymentMode?.find((y) => y.key.paymentMode.toLowerCase() === x[0].toLowerCase())?.value,
}
}),
],
},
]"
:label-width="15"
@on-change="filterChange"
ref="selectFilter"></scSelectFilter>
</el-header>
<el-header>
<div class="left-panel">
<na-search
:controls="[
{
type: 'select-input',
field: [
'dy',
[
{ label: $t('订单编号'), key: 'id' },
{ label: $t('用户名'), key: 'owner.userName' },
{ label: $t('用户编号'), key: 'ownerId' },
{ label: $t('充值点数'), key: 'depositPoint' },
],
],
placeholder: $t('匹配内容'),
style: 'width:25rem',
selectStyle: 'width:8rem',
},
]"
:vue="this"
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
<div class="right-panel">
<el-button @click="this.dialog.save = { mode: 'add' }" icon="el-icon-plus" type="primary"></el-button>
</div>
</el-header>
<el-main class="nopadding">
<scTable
:context-menus="[
'id',
'ownerId',
'createdTime',
'paymentMode',
'amount',
'depositPoint',
'toPointRate',
'owner.userName',
'depositOrderStatus',
'actualPayAmount',
]"
:context-multi="{ id: ['createdTime'], ownerId: ['owner.userName'] }"
:context-opers="['view']"
:default-sort="{ prop: 'id', order: 'descending' }"
:export-api="$API.sys_depositorder.export"
:params="query"
:query-api="$API.sys_depositorder.pagedQuery"
:vue="this"
@data-change="getStatistics"
@selection-change="
(items) => {
selection = items
}
"
ref="table"
remote-filter
remote-sort
row-key="id"
stripe>
<naColId :label="$t('订单编号')" prop="id" sortable="custom" width="170" />
<naColUser
:clickOpenDialog="$GLOBAL.hasApiPermission('api/sys/user/get')"
:label="$t('归属用户')"
header-align="center"
nestProp="owner.userName"
nestProp2="ownerId"
prop="ownerId"
sortable="custom"
width="170"></naColUser>
<naColIndicator
:label="$t('订单状态')"
:options="
Object.entries(this.$GLOBAL.enums.depositOrderStatues).map((x) => {
return { value: x[0], text: `${x[1][1]}`, type: x[1][2], pulse: x[1][3] === 'true' }
})
"
align="center"
prop="depositOrderStatus"
sortable="custom" />
<el-table-column
:formatter="(row) => $TOOL.groupSeparator(row.depositPoint)"
:label="$t('充值点数')"
align="right"
prop="depositPoint"
sortable="custom" />
<el-table-column
:formatter="(row) => $TOOL.groupSeparator(row.toPointRate)"
:label="$t('兑换比率')"
align="right"
prop="toPointRate"
sortable="custom" />
<el-table-column
:formatter="(row) => $TOOL.groupSeparator(row.actualPayAmount / 1000)"
:label="$t('支付金额')"
align="right"
prop="actualPayAmount"
sortable="custom" />
<naColIndicator
:label="$t('支付方式')"
:options="
Object.entries(this.$GLOBAL.enums.paymentModes).map((x) => {
return { value: x[0], text: `${x[1][1]}`, type: x[1][2], pulse: x[1][3] === 'true' }
})
"
align="center"
prop="paymentMode"
sortable="custom" />
<naColOperation
:buttons="[
naColOperation.buttons[0],
{
icon: 'el-icon-credit-card',
title: '支付',
click: async (row, vue) => {
vue.dialog.save = { row, mode: 'pay' }
},
condition: (row) => {
return row.depositOrderStatus === 'waitingForPayment'
},
},
]"
:vue="this"
width="120" />
</scTable>
</el-main>
</el-container>
<save-dialog
v-if="dialog.save"
@closed="dialog.save = null"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>
</template>
<script>
import { defineAsyncComponent } from 'vue'
import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
const naColUser = defineAsyncComponent(() => import('@/components/naColUser'))
const saveDialog = defineAsyncComponent(() => import('./save.vue'))
export default {
components: {
naColUser,
saveDialog,
},
computed: {
naColOperation() {
return naColOperation
},
table() {
return table
},
},
async created() {
if (this.ownerId) {
this.query.dynamicFilter.filters.push({ field: 'ownerId', operator: 'eq', value: this.ownerId })
}
},
data() {
return {
statistics: {
total: '...',
},
dialog: {},
loading: false,
query: {
dynamicFilter: {
filters: [],
},
filter: {},
keywords: this.keywords,
},
selection: [],
}
},
inject: ['reload'],
methods: {
filterChange(data) {
Object.entries(data).forEach(([key, value]) => {
this.$refs.search.form.dy[key] = value === 'true' ? true : value === 'false' ? false : value
})
this.$refs.search.search()
},
async getStatistics() {
this.statistics.total = this.$refs.table?.total
const res = await Promise.all([
this.$API.sys_depositorder.countBy.post({
dynamicFilter: {
filters: this.query.dynamicFilter.filters,
},
requiredFields: ['PaymentMode'],
}),
this.$API.sys_depositorder.countBy.post({
dynamicFilter: {
filters: this.query.dynamicFilter.filters,
},
requiredFields: ['DepositOrderStatus'],
}),
])
this.statistics.paymentMode = res[0].data
this.statistics.depositOrderStatus = res[1].data
},
//重置
onReset() {
Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))
if (this.ownerId) {
this.$refs.search.selectInputKey = 'ownerId'
}
},
//搜索
async onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
field: 'createdTime',
operator: 'dateRange',
value: form.dy.createdTime.map((x) => x.replace(/ 00:00:00$/, '')),
})
}
if (typeof form.dy['owner.userName'] === 'string' && form.dy['owner.userName'].trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'owner.userName',
operator: 'eq',
value: form.dy['owner.userName'],
})
}
if (typeof form.dy['ownerId'] === 'string' && form.dy['ownerId'].trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'ownerId',
operator: 'eq',
value: form.dy['ownerId'],
})
}
if (typeof form.dy['id'] === 'string' && form.dy['id'].trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'id',
operator: 'eq',
value: form.dy['id'],
})
}
if (typeof form.dy['paymentMode'] === 'string' && form.dy['paymentMode'].trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'paymentMode',
operator: 'eq',
value: form.dy['paymentMode'],
})
}
if (typeof form.dy['depositOrderStatus'] === 'string' && form.dy['depositOrderStatus'].trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'depositOrderStatus',
operator: 'eq',
value: form.dy['depositOrderStatus'],
})
}
if (typeof form.dy['depositPoint'] === 'string' && form.dy['depositPoint'].trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'depositPoint',
operator: 'eq',
value: form.dy['depositPoint'],
})
}
await this.$refs.table.upData()
},
},
async mounted() {
if (this.ownerId) {
this.$refs.search.selectInputKey = 'ownerId'
this.$refs.search.form.dy.ownerId = this.ownerId
this.$refs.search.keeps.push({
field: 'ownerId',
value: this.ownerId,
type: 'dy',
})
}
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keeps.push({
field: 'keywords',
value: this.keywords,
type: 'root',
})
}
this.onReset()
},
props: ['keywords', 'ownerId'],
watch: {},
}
</script>
<style scoped></style>

View File

@ -0,0 +1,197 @@
<template>
<scDialog v-model="visible" :title="`${titleMap[mode]}${form?.id ?? '...'}`" @closed="$emit('closed')" destroy-on-close>
<div v-loading="loading">
<el-tabs v-model="tabId" @tab-change="tabChange" tab-position="top">
<el-tab-pane :label="$t('基本信息')" name="basic">
<el-form :disabled="mode === 'view' || mode === 'pay'" :model="form" :rules="rules" label-width="15rem" ref="dialogForm">
<el-form-item v-if="mode !== 'add'" :label="$t('订单编号')" prop="id">
<el-input v-model="form.id" clearable />
</el-form-item>
<el-form-item v-if="mode !== 'add'" :label="$t('订单状态')" prop="depositOrderStatus">
<el-select v-model="form.depositOrderStatus" filterable>
<el-option v-for="(item, i) in $GLOBAL.enums.depositOrderStatues" :key="i" :label="item[1]" :value="i" />
</el-select>
</el-form-item>
<el-form-item :label="$t('充值点数')" prop="depositPoint">
<el-input-number
:max="999999999"
:min="100"
:model-value="form.depositPoint"
:step="100"
@input="
(e) => {
if (e < 0) e = 0
if (e % 10 === 0) {
e += Math.floor(Math.random() * 9) + 1
}
if (e < 100) {
e += 100
}
this.form.depositPoint = e
}
"
clearable
style="width: 15rem" />
</el-form-item>
<el-form-item :label="$t('支付方式')" prop="paymentMode">
<el-select v-model="form.paymentMode" filterable>
<el-option v-for="(item, i) in $GLOBAL.enums.paymentModes" :key="i" :label="item[1]" :value="i" />
</el-select>
</el-form-item>
<el-form-item :label="$t('货币兑点数比率')" prop="toPointRate">
<el-input :model-value="form.toPointRate ? `1:${form.toPointRate}` : ''" disabled />
</el-form-item>
<el-form-item :label="$t('支付货币金额')" prop="actualPayAmount">
<el-input :model-value="form.actualPayAmount ? form.actualPayAmount / 1000 : ''" disabled />
</el-form-item>
<el-form-item v-if="mode === 'pay'" :label="$t('收款账号')" prop="receiptAccount">
<el-input v-model="form.receiptAccount" clearable />
</el-form-item>
<el-form-item v-if="mode === 'pay'" :label="$t('收款二维码')">
<div v-html="form.receiptAccountQrCode"></div>
</el-form-item>
<el-form-item v-if="mode !== 'add'" :label="$t('创建时间')" prop="createdTime">
<el-input v-model="form.createdTime" clearable />
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')">
<JsonViewer
:expand-depth="5"
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'dark' : 'light'"
:value="form"
copyable
expanded
sort></JsonViewer>
</el-tab-pane>
</el-tabs>
</div>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button v-if="mode === 'add'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('提交订单') }}</el-button>
<el-button
v-if="mode === 'pay'"
:disabled="loading"
:loading="loading"
@click="
async () => {
this.loading = true
try {
const res = await this.$API.sys_depositorder.payConfirm.post(this.form)
this.$emit('success', res.data, this.mode)
this.visible = false
this.$message.success(this.$t('操作成功'))
} catch {}
this.loading = false
}
"
type="danger"
>{{ $t('确认已支付') }}</el-button
>
</template>
</scDialog>
</template>
<script>
import QRCode from 'qrcode-svg'
export default {
computed: {},
components: {},
watch: {
'form.paymentMode': {
immediate: true,
handler(n, o) {
if (n === 'alipay' || n === 'weChat') {
this.form.toPointRate = this.config?.cnyToPointRate
} else if (n === 'usdt') {
this.form.toPointRate = this.config?.usdToPointRate
} else {
delete this.form.toPointRate
}
if (this.form.toPointRate && this.form.depositPoint) {
this.form.actualPayAmount = (this.form.depositPoint / this.form.toPointRate).toFixed(3) * 1000
} else {
delete this.form.actualPayAmount
}
},
},
'form.depositPoint': {
immediate: true,
handler(n, o) {
if (this.form.toPointRate) {
this.form.actualPayAmount = (n / this.form.toPointRate).toFixed(3) * 1000
}
},
},
},
data() {
return {
config: null,
//表单数据
form: {},
loading: true,
mode: 'add',
//验证规则
rules: {
depositPoint: [{ required: true, message: this.$t('请输入充值点数') }],
paymentMode: [{ required: true, message: this.$t('请选择支付方式') }],
},
tabId: 'basic',
titleMap: {
add: this.$t('新建充值订单'),
view: this.$t('查看充值订单'),
pay: this.$t('支付充值订单'),
},
visible: false,
}
},
emits: ['success', 'closed', 'mounted'],
methods: {
//显示
async open(data) {
this.visible = true
this.config = (await this.$API.sys_depositorder.getDepositConfig.post({})).data
if (data.mode === 'add') {
this.loading = false
return this
}
this.loading = true
this.mode = data.mode
if (data.row?.id) {
const res = await this.$API.sys_depositorder.get.post({ id: data.row.id })
if (res.data) {
Object.assign(this.form, res.data)
this.form.receiptAccountQrCode = new QRCode(this.form.receiptAccount).svg()
this.loading = false
return this
}
}
this.$message.error(`未找到该数据`)
return this
},
//表单提交方法
async submit() {
const valid = await this.$refs.dialogForm.validate().catch(() => {})
if (!valid) {
return false
}
this.loading = true
try {
const res = await this.$API.sys_depositorder.create.post(this.form)
this.$emit('success', res.data, this.mode)
this.visible = false
this.$message.success(this.$t('操作成功'))
} catch {}
this.loading = false
},
},
mounted() {
this.$emit('mounted')
},
}
</script>
<style scoped></style>

View File

@ -134,7 +134,7 @@ export default {
},
tabId: '0',
titleMap: {
add: this.$t('新角色'),
add: this.$t('新角色'),
edit: this.$t('编辑角色'),
view: this.$t('查看角色'),
},

View File

@ -109,7 +109,7 @@
<naColId :label="$t('交易编号')" prop="id" sortable="custom" width="170" />
<naColUser
:clickOpenDialog="$GLOBAL.hasApiPermission('api/sys/user/get')"
:label="$t('属用户')"
:label="$t('属用户')"
header-align="center"
nestProp="owner.userName"
nestProp2="ownerId"

View File

@ -22,16 +22,16 @@
<el-form-item :label="$t('交易后余额')">
<el-input :value="form.balanceBefore + form.amount" clearable />
</el-form-item>
<el-form-item :label="$t('所有者部门编号')" prop="ownerDeptId">
<el-form-item :label="$t('归属部门编号')" prop="ownerDeptId">
<el-input v-model="form.ownerDeptId" clearable />
</el-form-item>
<el-form-item :label="$t('所有者用户编号')" prop="ownerId">
<el-form-item :label="$t('归属用户编号')" prop="ownerId">
<el-input v-model="form.ownerId" clearable />
</el-form-item>
<el-form-item :label="$t('创建者用户编号')" prop="createdUserId">
<el-input v-model="form.createdUserId" clearable />
</el-form-item>
<el-form-item :label="$t('所有者用户名')" prop="createdUserName">
<el-form-item :label="$t('归属用户名')" prop="createdUserName">
<el-input v-model="form.createdUserName" clearable />
</el-form-item>
<el-form-item :label="$t('创建时间')" prop="createdTime">
@ -70,7 +70,7 @@ export default {
rules: {},
tabId: 'basic',
titleMap: {
add: this.$t('新交易'),
add: this.$t('新交易'),
edit: this.$t('编辑交易'),
view: this.$t('查看交易'),
},

View File

@ -41,7 +41,7 @@
field: ['filter', 'roleId'],
api: $API.sys_role.query,
config: { props: { label: 'name', value: 'id' } },
placeholder: $t('属角色'),
placeholder: $t('属角色'),
style: 'width:15rem',
},
]"
@ -121,13 +121,13 @@
</template>
</el-table-column>
<naColTags
:label="$t('属部门')"
:label="$t('属部门')"
@click="(item) => (this.dialog.deptSave = { row: item, mode: 'view' })"
field="name"
prop="dept"
width="120" />
<naColTags
:label="$t('属角色')"
:label="$t('属角色')"
@click="(item) => (this.dialog.roleSave = { row: item, mode: 'view' })"
field="name"
min-width="200"

View File

@ -50,7 +50,7 @@
</div>
</el-form-item>
<el-form-item :label="$t('属角色')" prop="roleIds">
<el-form-item :label="$t('属角色')" prop="roleIds">
<scSelect
v-if="!this.loading"
v-model="form.roleIds"
@ -61,7 +61,7 @@
filterable
multiple />
</el-form-item>
<el-form-item :label="$t('属部门')" prop="deptId">
<el-form-item :label="$t('属部门')" prop="deptId">
<naDept v-model="form.deptId" class="w100p"></naDept>
</el-form-item>
@ -315,18 +315,18 @@ export default {
pattern: this.$GLOBAL.chars.RGX_PASSWORD,
},
],
deptId: [{ required: true, message: '请选择属部门' }],
deptId: [{ required: true, message: '请选择属部门' }],
roleIds: [
{
required: true,
message: '请选择属角色',
message: '请选择属角色',
trigger: 'change',
},
],
},
tabId: '0',
titleMap: {
add: this.$t('新用户'),
add: this.$t('新用户'),
edit: this.$t('编辑用户'),
view: this.$t('查看用户'),
},

View File

@ -33,7 +33,7 @@
field: ['filter', 'deptId'],
api: $API.sys_dept.query,
config: { props: { label: 'name', value: 'id' } },
placeholder: $t('属部门'),
placeholder: $t('属部门'),
style: 'width:15rem',
condition: () => $GLOBAL.hasApiPermission('api/sys/dept/query'),
},
@ -42,7 +42,7 @@
field: ['filter', 'roleId'],
api: $API.sys_role.query,
config: { props: { label: 'name', value: 'id' } },
placeholder: $t('属角色'),
placeholder: $t('属角色'),
style: 'width:15rem',
condition: () => $GLOBAL.hasApiPermission('api/sys/dept/query'),
},
@ -92,7 +92,7 @@
<naColId :label="$t('钱包编号')" prop="id" sortable="custom" width="170" />
<naColUser
:clickOpenDialog="$GLOBAL.hasApiPermission('api/sys/user/get')"
:label="$t('属用户')"
:label="$t('属用户')"
nestProp="owner.userName"
nestProp2="ownerId"
prop="ownerId"

View File

@ -22,10 +22,10 @@
<el-form-item :label="$t('总支出')" prop="totalExpenditure">
<el-input v-model="form.totalExpenditure" clearable />
</el-form-item>
<el-form-item :label="$t('所有者部门编号')" prop="ownerDeptId">
<el-form-item :label="$t('归属部门编号')" prop="ownerDeptId">
<el-input v-model="form.ownerDeptId" clearable />
</el-form-item>
<el-form-item :label="$t('所有者用户编号')" prop="ownerId">
<el-form-item :label="$t('归属用户编号')" prop="ownerId">
<el-input v-model="form.ownerId" clearable />
</el-form-item>
<el-form-item :label="$t('创建时间')" prop="createdTime">
@ -75,7 +75,7 @@ export default {
rules: {},
tabId: 'basic',
titleMap: {
add: this.$t('新钱包'),
add: this.$t('新钱包'),
edit: this.$t('编辑钱包'),
view: this.$t('查看钱包'),
},

View File

@ -48,7 +48,7 @@ export default {
},
tabId: '0',
titleMap: {
add: this.$t('新用户'),
add: this.$t('新用户'),
edit: this.$t('编辑用户'),
view: this.$t('查看用户'),
},