feat: 财务管理

This commit is contained in:
tk
2025-06-26 17:35:19 +08:00
committed by nsnail
parent a202595687
commit 94d0b7028f
63 changed files with 2544 additions and 72 deletions

View File

@ -17,7 +17,6 @@
以什么结束 以什么结束
作业名称 作业名称
作业状态 作业状态
信息 信息
倒序排序 倒序排序
全部数据 全部数据
@ -45,6 +44,7 @@
字典内容导出 字典内容导出
宕机 宕机
客户端IP 客户端IP
小于 小于
小于等于 小于等于
小学 小学
@ -71,6 +71,8 @@
接口路径 接口路径
插入种子数据 插入种子数据
操作系统 操作系统
支出
收入
数据范围 数据范围
文档内容 文档内容
文档内容导出 文档内容导出
@ -99,6 +101,7 @@
用户代理 用户代理
用户名 用户名
用户导出 用户导出
用户钱包导出
电子邮箱 电子邮箱
登录 登录
@ -111,11 +114,14 @@
站内信导出 站内信导出
等于 等于
等待发送 等待发送
管理员充值
管理员扣费
管理模块 管理模块
系统模块 系统模块
绑定手机号码 绑定手机号码
结果非预期 结果非预期
群众 群众
自助充值
自定义 自定义
范围 范围
菜单 菜单
@ -128,16 +134,17 @@
请求方式 请求方式
请求日志导出 请求日志导出
调试 调试
追踪
追踪标识
身份证 身份证
运行 运行
追踪
追踪标识
通知 通知
邮箱号 邮箱号
部门名称 部门名称
部门导出 部门导出
配置导出 配置导出
重设密码 重设密码
钱包交易导出
链接 链接
错误 错误
随机排序 随机排序

View File

@ -5,6 +5,8 @@ XML注释文件不存在
中文姓名 中文姓名
事务已回滚 事务已回滚
事务已提交 事务已提交
交易失败
交易金额不正确
人机校验请求不能为空 人机校验请求不能为空
人机验证未通过 人机验证未通过
作业名称不能为空 作业名称不能为空
@ -108,6 +110,7 @@ XML注释文件不存在
部门可见 部门可见
部门名称不能为空 部门名称不能为空
配置文件初始化完毕 配置文件初始化完毕
钱包余额不足
键值不能为空 键值不能为空
键名称不能为空 键名称不能为空
随机延时结束时间不正确 随机延时结束时间不正确

View File

@ -26,7 +26,7 @@
"Id": 373837957840901, "Id": 373837957840901,
"Name": "sys/user", "Name": "sys/user",
"ParentId": 373837917724677, "ParentId": 373837917724677,
"Path": "/sys/user", "Path": "/power/user",
"Sort": 100, "Sort": 100,
"Title": "用户管理", "Title": "用户管理",
"Type": 1 "Type": 1
@ -37,7 +37,7 @@
"Id": 373838018527237, "Id": 373838018527237,
"Name": "sys/role", "Name": "sys/role",
"ParentId": 373837917724677, "ParentId": 373837917724677,
"Path": "/sys/role", "Path": "/power/role",
"Sort": 99, "Sort": 99,
"Title": "角色管理", "Title": "角色管理",
"Type": 1 "Type": 1
@ -48,7 +48,7 @@
"Id": 373838045605893, "Id": 373838045605893,
"Name": "sys/dept", "Name": "sys/dept",
"ParentId": 373837917724677, "ParentId": 373837917724677,
"Path": "/sys/dept", "Path": "/power/dept",
"Sort": 98, "Sort": 98,
"Title": "部门管理", "Title": "部门管理",
"Type": 1 "Type": 1
@ -59,18 +59,50 @@
"Id": 373838070898693, "Id": 373838070898693,
"Name": "sys/menu", "Name": "sys/menu",
"ParentId": 373837917724677, "ParentId": 373837917724677,
"Path": "/sys/menu", "Path": "/power/menu",
"Sort": 97, "Sort": 97,
"Title": "菜单管理", "Title": "菜单管理",
"Type": 1 "Type": 1
}, },
// ------------------------------ 财务管理 ------------------------------
{
"Icon": "el-icon-money",
"Id": 690906994118665,
"Name": "finance",
"Path": "/finance",
"Sort": 99,
"Title": "财务管理",
"Type": 1
},
{
"Component": "sys/wallet",
"Icon": "el-icon-wallet",
"Id": 690907673255942,
"Name": "sys/wallet",
"ParentId": 690906994118665,
"Path": "/finance/wallet",
"Sort": 100,
"Title": "钱包管理",
"Type": 1
},
{
"Component": "sys/trade",
"Icon": "el-icon-calendar",
"Id": 690907673255943,
"Name": "sys/trade",
"ParentId": 690906994118665,
"Path": "/finance/trade",
"Sort": 99,
"Title": "交易流水",
"Type": 1
},
// ------------------------------ 系统管理 ------------------------------ // ------------------------------ 系统管理 ------------------------------
{ {
"Icon": "sc-icon-App", "Icon": "sc-icon-App",
"Id": 485278637670422, "Id": 485278637670422,
"Name": "sys", "Name": "sys",
"Path": "/sys", "Path": "/sys",
"Sort": 99, "Sort": 98,
"Title": "系统管理", "Title": "系统管理",
"Type": 1 "Type": 1
}, },
@ -80,7 +112,7 @@
"Id": 380415005847557, "Id": 380415005847557,
"Name": "sys/config", "Name": "sys/config",
"ParentId": 485278637670422, "ParentId": 485278637670422,
"Path": "/sys/config", "Path": "/system/config",
"Sort": 100, "Sort": 100,
"Title": "系统设置", "Title": "系统设置",
"Type": 1 "Type": 1
@ -91,7 +123,7 @@
"Id": 510067557638158, "Id": 510067557638158,
"Name": "sys/job", "Name": "sys/job",
"ParentId": 485278637670422, "ParentId": 485278637670422,
"Path": "/sys/job", "Path": "/system/job",
"Sort": 99, "Sort": 99,
"Title": "计划作业", "Title": "计划作业",
"Type": 1 "Type": 1
@ -102,7 +134,7 @@
"Id": 375315654221829, "Id": 375315654221829,
"Name": "sys/dic", "Name": "sys/dic",
"ParentId": 485278637670422, "ParentId": 485278637670422,
"Path": "/sys/dic", "Path": "/system/dic",
"Sort": 98, "Sort": 98,
"Title": "字典管理", "Title": "字典管理",
"Type": 1 "Type": 1
@ -113,7 +145,7 @@
"Id": 482779610341392, "Id": 482779610341392,
"Name": "sys/msg", "Name": "sys/msg",
"ParentId": 485278637670422, "ParentId": 485278637670422,
"Path": "/sys/msg", "Path": "/system/msg",
"Sort": 97, "Sort": 97,
"Title": "消息管理", "Title": "消息管理",
"Type": 1, "Type": 1,
@ -124,7 +156,7 @@
"Id": 397880678895621, "Id": 397880678895621,
"Name": "sys/api", "Name": "sys/api",
"ParentId": 485278637670422, "ParentId": 485278637670422,
"Path": "/sys/api", "Path": "/system/api",
"Sort": 96, "Sort": 96,
"Title": "接口管理", "Title": "接口管理",
"Type": 1 "Type": 1
@ -135,7 +167,7 @@
"Id": 374911555702789, "Id": 374911555702789,
"Name": "sys/cache", "Name": "sys/cache",
"ParentId": 485278637670422, "ParentId": 485278637670422,
"Path": "/sys/cache", "Path": "/system/cache",
"Sort": 95, "Sort": 95,
"Title": "缓存管理", "Title": "缓存管理",
"Type": 1 "Type": 1
@ -146,7 +178,7 @@
"Id": 616214756757512, "Id": 616214756757512,
"Name": "archive", "Name": "archive",
"Path": "/archive", "Path": "/archive",
"Sort": 98, "Sort": 97,
"Title": "档案管理", "Title": "档案管理",
"Type": 1 "Type": 1
}, },
@ -167,7 +199,7 @@
"Id": 374792687640581, "Id": 374792687640581,
"Name": "log", "Name": "log",
"Path": "/log", "Path": "/log",
"Sort": 97, "Sort": 96,
"Title": "日志管理", "Title": "日志管理",
"Type": 1 "Type": 1
}, },
@ -177,7 +209,7 @@
"Id": 485285246504976, "Id": 485285246504976,
"Name": "sys/log/operation", "Name": "sys/log/operation",
"ParentId": 374792687640581, "ParentId": 374792687640581,
"Path": "/sys/log/operation", "Path": "/log/operation",
"Sort": 100, "Sort": 100,
"Title": "操作日志", "Title": "操作日志",
"Type": 1, "Type": 1,
@ -188,7 +220,7 @@
"Id": 485285246504970, "Id": 485285246504970,
"Name": "sys/log/login", "Name": "sys/log/login",
"ParentId": 374792687640581, "ParentId": 374792687640581,
"Path": "/sys/log/login", "Path": "/log/login",
"Sort": 99, "Sort": 99,
"Title": "登录日志", "Title": "登录日志",
"Type": 1, "Type": 1,
@ -199,7 +231,7 @@
"Id": 373838105399301, "Id": 373838105399301,
"Name": "dev", "Name": "dev",
"Path": "/dev", "Path": "/dev",
"Sort": 96, "Sort": 95,
"Title": "开发管理", "Title": "开发管理",
"Type": 1 "Type": 1
}, },

View File

@ -58,5 +58,25 @@
{ {
"ApiId": "api/sys/login.log/export", "ApiId": "api/sys/login.log/export",
"RoleId": 371729946431493, "RoleId": 371729946431493,
},
{
"ApiId": "api/sys/user.wallet/paged.query",
"RoleId": 371729946431493,
},
{
"ApiId": "api/sys/user.wallet/get",
"RoleId": 371729946431493,
},
{
"ApiId": "api/sys/wallet.trade/paged.query",
"RoleId": 371729946431493,
},
{
"ApiId": "api/sys/wallet.trade/count.by",
"RoleId": 371729946431493,
},
{
"ApiId": "api/sys/wallet.trade/get",
"RoleId": 371729946431493,
} }
] ]

View File

@ -6,5 +6,25 @@
{ {
"MenuId": 374967228141573, "MenuId": 374967228141573,
"RoleId": 371729946431493 "RoleId": 371729946431493
},
{
"MenuId": 690906994118665,
"RoleId": 371729946431493
},
{
"MenuId": 690907673255942,
"RoleId": 371729946431493
},
{
"MenuId": 374792687640581,
"RoleId": 371729946431493
},
{
"MenuId": 485285246504970,
"RoleId": 371729946431493
},
{
"MenuId": 690907673255943,
"RoleId": 371729946431493
} }
] ]

View File

@ -0,0 +1,12 @@
[
{
"Id": 370942943322181,
"OwnerDeptId": 372119301627909,
"OwnerId": 370942943322181,
},
{
"Id": 560217289236492,
"OwnerDeptId": 372119301627909,
"OwnerId": 560217289236492,
}
]

View File

@ -3,7 +3,7 @@
"devDependencies": { "devDependencies": {
"cz-git": "^1.11.2", "cz-git": "^1.11.2",
"commitizen": "^4.3.1", "commitizen": "^4.3.1",
"prettier": "^3.5.3", "prettier": "^3.6.1",
"standard-version": "^9.5.0" "standard-version": "^9.5.0"
}, },
"config": { "config": {

View File

@ -10,7 +10,7 @@ public abstract class ServiceBase<TLogger> : ServiceBase
/// </summary> /// </summary>
protected ServiceBase() // protected ServiceBase() //
{ {
Logger = App.GetService<ILogger<TLogger>>(); Logger = S<ILogger<TLogger>>();
} }
/// <summary> /// <summary>
@ -29,7 +29,7 @@ public abstract class ServiceBase : IScoped, IService
/// </summary> /// </summary>
protected ServiceBase() protected ServiceBase()
{ {
UserToken = App.GetService<ContextUserToken>(); UserToken = S<ContextUserToken>();
ServiceId = Guid.NewGuid(); ServiceId = Guid.NewGuid();
} }
@ -38,4 +38,19 @@ public abstract class ServiceBase : IScoped, IService
/// <inheritdoc /> /// <inheritdoc />
public ContextUserToken UserToken { get; set; } public ContextUserToken UserToken { get; set; }
/// <summary>
/// 获取服务
/// </summary>
#pragma warning disable RCS1036
#pragma warning restore RCS1036
// ReSharper disable once MemberCanBeMadeStatic.Global
#pragma warning disable CA1822, S2325
protected T S<T>()
#pragma warning restore S2325, CA1822
where T : class
{
return App.GetService<T>();
}
} }

View File

@ -42,8 +42,9 @@ public sealed class ExampleService(BasicRepository<Tpl_Example, long> rpo) //
.ToDictionaryAsync(a => a.Count()) .ToDictionaryAsync(a => a.Count())
.ConfigureAwait(false); .ConfigureAwait(false);
return ret.Select(x => new KeyValuePair<IImmutableDictionary<string, string>, int>( return ret.Select(x => new KeyValuePair<IImmutableDictionary<string, string>, int>(
req.RequiredFields.ToImmutableDictionary(y => y, y => typeof(Tpl_Example).GetProperty(y)!.GetValue(x.Key)!.ToString()) req.RequiredFields.ToImmutableDictionary(y => y, y => typeof(Tpl_Example).GetProperty(y)!.GetValue(x.Key)?.ToString())
, x.Value)) , x.Value))
.Where(x => x.Key.Any(y => !y.Value.NullOrEmpty()))
.OrderByDescending(x => x.Value); .OrderByDescending(x => x.Value);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,72 @@
namespace NetAdmin.Domain.DbMaps.Sys;
/// <summary>
/// 用户钱包表
/// </summary>
[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_UserWallet))]
public record Sys_UserWallet : LiteVersionEntity, IFieldOwner
{
/// <summary>
/// 可用余额
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long AvailableBalance { get; init; }
/// <summary>
/// 冻结余额
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long FrozenBalance { 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]
[CsvIgnore]
[JsonIgnore]
public virtual long TotalBalance { get; init; }
/// <summary>
/// 总支出
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long TotalExpenditure { get; init; }
/// <summary>
/// 总收入
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long TotalIncome { get; init; }
}

View File

@ -0,0 +1,80 @@
namespace NetAdmin.Domain.DbMaps.Sys;
/// <summary>
/// 钱包交易表
/// </summary>
[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_WalletTrade))]
public record Sys_WalletTrade : ImmutableEntity, IFieldOwner, IFieldSummary
{
/// <summary>
/// 交易金额
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long Amount { get; init; }
/// <summary>
/// 交易前余额
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long BalanceBefore { get; init; }
/// <summary>
/// 业务订单号
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long? BusinessOrderNumber { 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_255)]
[CsvIgnore]
[JsonIgnore]
public virtual string Summary { get; init; }
/// <summary>
/// 交易方向
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual TradeDirections TradeDirection { get; init; }
/// <summary>
/// 交易类型
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual TradeTypes TradeType { get; init; }
}

View File

@ -0,0 +1,8 @@
using NetAdmin.Domain.DbMaps.Sys;
namespace NetAdmin.Domain.Dto.Sys.UserWallet;
/// <summary>
/// 请求:创建用户钱包
/// </summary>
public record CreateUserWalletReq : Sys_UserWallet;

View File

@ -0,0 +1,15 @@
namespace NetAdmin.Domain.Dto.Sys.UserWallet;
/// <summary>
/// 请求:编辑用户钱包
/// </summary>
public record EditUserWalletReq : CreateUserWalletReq
{
/// <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,21 @@
namespace NetAdmin.Domain.Dto.Sys.UserWallet;
/// <summary>
/// 请求:查询用户钱包
/// </summary>
public sealed record QueryUserWalletReq : Sys_UserWallet
{
/// <summary>
/// 部门编号
/// </summary>
public long DeptId { get; init; }
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
/// <summary>
/// 角色编号
/// </summary>
public long RoleId { get; init; }
}

View File

@ -0,0 +1,56 @@
using NetAdmin.Domain.Dto.Sys.User;
namespace NetAdmin.Domain.Dto.Sys.UserWallet;
/// <summary>
/// 响应:查询用户钱包
/// </summary>
public record QueryUserWalletRsp : Sys_UserWallet
{
/// <inheritdoc cref="Sys_UserWallet.AvailableBalance" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long AvailableBalance { get; init; }
/// <inheritdoc cref="IFieldCreatedTime.CreatedTime" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override DateTime CreatedTime { get; init; }
/// <inheritdoc cref="Sys_UserWallet.FrozenBalance" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long FrozenBalance { 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_UserWallet.Owner" />
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_UserWallet.TotalBalance" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long TotalBalance { get; init; }
/// <inheritdoc cref="Sys_UserWallet.TotalExpenditure" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long TotalExpenditure { get; init; }
/// <inheritdoc cref="Sys_UserWallet.TotalIncome" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long TotalIncome { get; init; }
/// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; }
}

View File

@ -0,0 +1,53 @@
namespace NetAdmin.Domain.Dto.Sys.WalletTrade;
/// <summary>
/// 请求:创建钱包交易
/// </summary>
public record CreateWalletTradeReq : Sys_WalletTrade, IValidatableObject
{
private readonly TradeTypes _tradeType;
/// <inheritdoc cref="Sys_WalletTrade.Amount" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Amount { get; init; }
/// <inheritdoc cref="Sys_WalletTrade.BusinessOrderNumber" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override long? BusinessOrderNumber { get; init; }
/// <inheritdoc cref="IFieldOwner.OwnerId" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[Required]
[UserId]
[Range(1, long.MaxValue)]
public override long? OwnerId { get; init; }
/// <inheritdoc cref="IFieldSummary.Summary" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string Summary { get; init; }
/// <inheritdoc cref="Sys_WalletTrade.TradeDirection" />
public override TradeDirections TradeDirection { get; init; }
/// <inheritdoc cref="Sys_WalletTrade.TradeType" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[EnumDataType(typeof(TradeTypes))]
public override TradeTypes TradeType {
get => _tradeType;
init {
_tradeType = value;
TradeDirection = value.Attr<TradeAttribute>().Direction;
}
}
/// <inheritdoc />
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var tradeDirection = TradeType.Attr<TradeAttribute>().Direction;
if (Amount == 0 || (tradeDirection == TradeDirections.Income && Amount < 0) || (tradeDirection == TradeDirections.Expense && Amount > 0)) {
yield return new ValidationResult(Ln., [nameof(Amount)]);
}
yield return ValidationResult.Success;
}
}

View File

@ -0,0 +1,11 @@
namespace NetAdmin.Domain.Dto.Sys.WalletTrade;
/// <summary>
/// 请求:编辑钱包交易
/// </summary>
public record EditWalletTradeReq : CreateWalletTradeReq
{
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
}

View File

@ -0,0 +1,13 @@
using NetAdmin.Domain.DbMaps.Sys;
namespace NetAdmin.Domain.Dto.Sys.WalletTrade;
/// <summary>
/// 请求:查询钱包交易
/// </summary>
public sealed record QueryWalletTradeReq : Sys_WalletTrade
{
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
}

View File

@ -0,0 +1,61 @@
using NetAdmin.Domain.Dto.Sys.User;
namespace NetAdmin.Domain.Dto.Sys.WalletTrade;
/// <summary>
/// 响应:查询钱包交易
/// </summary>
public record QueryWalletTradeRsp : Sys_WalletTrade
{
/// <inheritdoc cref="Sys_WalletTrade.Amount" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Amount { get; init; }
/// <inheritdoc cref="Sys_WalletTrade.BalanceBefore" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long BalanceBefore { get; init; }
/// <inheritdoc cref="Sys_WalletTrade.BusinessOrderNumber" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override long? BusinessOrderNumber { get; init; }
/// <inheritdoc cref="IFieldCreatedTime.CreatedTime" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override DateTime CreatedTime { get; init; }
/// <inheritdoc cref="IFieldCreatedUser.CreatedUserId" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override long? CreatedUserId { get; init; }
/// <inheritdoc cref="IFieldCreatedUser.CreatedUserName" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string CreatedUserName { get; init; }
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
/// <inheritdoc cref="Sys_WalletTrade.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="IFieldSummary.Summary" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string Summary { get; init; }
/// <inheritdoc cref="Sys_WalletTrade.TradeDirection" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override TradeDirections TradeDirection { get; init; }
/// <inheritdoc cref="Sys_WalletTrade.TradeType" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override TradeTypes TradeType { get; init; }
}

View File

@ -0,0 +1,13 @@
namespace NetAdmin.Infrastructure.Attributes;
/// <summary>
/// 交易方向特性
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Enum)]
public sealed class TradeAttribute : Attribute
{
/// <summary>
/// 交易方向
/// </summary>
public TradeDirections Direction { get; init; }
}

View File

@ -0,0 +1,22 @@
namespace NetAdmin.Infrastructure.Enums;
/// <summary>
/// 交易方向
/// </summary>
[Export]
public enum TradeDirections
{
/// <summary>
/// 收入
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.收入))]
Income = 4
,
/// <summary>
/// 支出
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.支出))]
Expense = 5
}

View File

@ -0,0 +1,33 @@
namespace NetAdmin.Infrastructure.Enums;
/// <summary>
/// 交易类型
/// </summary>
[Export]
public enum TradeTypes
{
/// <summary>
/// 管理员充值
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.管理员充值))]
[Trade(Direction = TradeDirections.Income)]
AdminDeposit = 1
,
/// <summary>
/// 管理员扣费
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.管理员扣费))]
[Trade(Direction = TradeDirections.Expense)]
AdminDeduct = 2
,
/// <summary>
/// 自助充值
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.自助充值))]
[Trade(Direction = TradeDirections.Income)]
SelfDeposit = 3
}

View File

@ -0,0 +1,12 @@
using NetAdmin.Domain.Dto.Sys.UserWallet;
namespace NetAdmin.SysComponent.Application.Modules.Sys;
/// <summary>
/// 用户钱包模块
/// </summary>
public interface IUserWalletModule : ICrudModule<CreateUserWalletReq, QueryUserWalletRsp // 创建类型
, EditUserWalletReq // 编辑类型
, QueryUserWalletReq, QueryUserWalletRsp // 查询类型
, DelReq // 删除类型
>;

View File

@ -0,0 +1,12 @@
using NetAdmin.Domain.Dto.Sys.WalletTrade;
namespace NetAdmin.SysComponent.Application.Modules.Sys;
/// <summary>
/// 钱包交易模块
/// </summary>
public interface IWalletTradeModule : ICrudModule<CreateWalletTradeReq, QueryWalletTradeRsp // 创建类型
, EditWalletTradeReq // 编辑类型
, QueryWalletTradeReq, QueryWalletTradeRsp // 查询类型
, DelReq // 删除类型
>;

View File

@ -0,0 +1,6 @@
namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency;
/// <summary>
/// 用户钱包服务
/// </summary>
public interface IUserWalletService : IService, IUserWalletModule;

View File

@ -0,0 +1,6 @@
namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency;
/// <summary>
/// 钱包交易服务
/// </summary>
public interface IWalletTradeService : IService, IWalletTradeModule;

View File

@ -16,7 +16,7 @@ public sealed class ToolsService : ServiceBase<IToolsService>, IToolsService
public async Task<object[][]> ExecuteSqlAsync(ExecuteSqlReq req) public async Task<object[][]> ExecuteSqlAsync(ExecuteSqlReq req)
{ {
req.ThrowIfInvalid(); req.ThrowIfInvalid();
var cmd = App.GetService<IFreeSql>().Ado.CommandFluent(req.Sql).CommandTimeout(req.TimeoutSecs).ExecuteArrayAsync(); var cmd = S<IFreeSql>().Ado.CommandFluent(req.Sql).CommandTimeout(req.TimeoutSecs).ExecuteArrayAsync();
return req.WaitResult ? await cmd.ConfigureAwait(false) : null; return req.WaitResult ? await cmd.ConfigureAwait(false) : null;
} }

View File

@ -175,7 +175,7 @@ public sealed class UserProfileService(BasicRepository<Sys_UserProfile, long> rp
// 默认仪表版 // 默认仪表版
if (req.AppConfig == "[]") { if (req.AppConfig == "[]") {
req.AppConfig = BuildAppConfig(App.GetService<ContextUserInfo>().Roles.ToDictionary(x => x.Id, x => x.DashboardLayout)); req.AppConfig = BuildAppConfig(S<ContextUserInfo>().Roles.ToDictionary(x => x.Id, x => x.DashboardLayout));
} }
return UpdateAsync(req, [nameof(req.AppConfig)], null, a => a.Id == UserToken.Id, null, true); return UpdateAsync(req, [nameof(req.AppConfig)], null, a => a.Id == UserToken.Id, null, true);

View File

@ -3,6 +3,7 @@ using NetAdmin.Domain.Contexts;
using NetAdmin.Domain.DbMaps.Sys; using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Sys.User; using NetAdmin.Domain.Dto.Sys.User;
using NetAdmin.Domain.Dto.Sys.UserProfile; using NetAdmin.Domain.Dto.Sys.UserProfile;
using NetAdmin.Domain.Dto.Sys.UserWallet;
using NetAdmin.Domain.Dto.Sys.VerifyCode; using NetAdmin.Domain.Dto.Sys.VerifyCode;
using NetAdmin.Domain.Events.Sys; using NetAdmin.Domain.Events.Sys;
using NetAdmin.Domain.Extensions; using NetAdmin.Domain.Extensions;
@ -13,6 +14,7 @@ namespace NetAdmin.SysComponent.Application.Services.Sys;
public sealed class UserService( public sealed class UserService(
BasicRepository<Sys_User, long> rpo // BasicRepository<Sys_User, long> rpo //
, IUserProfileService userProfileService // , IUserProfileService userProfileService //
, IUserWalletService userWalletService //
, IVerifyCodeService verifyCodeService // , IVerifyCodeService verifyCodeService //
, IEventPublisher eventPublisher) // , IEventPublisher eventPublisher) //
: RepositoryService<Sys_User, long, IUserService>(rpo), IUserService : RepositoryService<Sys_User, long, IUserService>(rpo), IUserService
@ -112,6 +114,14 @@ public sealed class UserService(
, AppConfig = appConfig , AppConfig = appConfig
}) })
.ConfigureAwait(false); .ConfigureAwait(false);
// 钱包表
_ = await userWalletService.CreateAsync(new CreateUserWalletReq() with //
{
Id = dbUser.Id, OwnerId = dbUser.Id, OwnerDeptId = dbUser.DeptId
})
.ConfigureAwait(false);
var userList = await QueryAsync(new QueryReq<QueryUserReq> { Filter = new QueryUserReq { Id = dbUser.Id } }).ConfigureAwait(false); var userList = await QueryAsync(new QueryReq<QueryUserReq> { Filter = new QueryUserReq { Id = dbUser.Id } }).ConfigureAwait(false);
// 发布用户创建事件 // 发布用户创建事件

View File

@ -0,0 +1,145 @@
using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Sys.UserWallet;
using NetAdmin.Domain.Extensions;
namespace NetAdmin.SysComponent.Application.Services.Sys;
/// <inheritdoc cref="IUserWalletService" />
public sealed class UserWalletService(BasicRepository<Sys_UserWallet, long> rpo) //
: RepositoryService<Sys_UserWallet, long, IUserWalletService>(rpo), IUserWalletService
{
/// <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<QueryUserWalletReq> req)
{
req.ThrowIfInvalid();
return QueryInternal(req).WithNoLockNoWait().CountAsync();
}
/// <inheritdoc />
public async Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryUserWalletReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req with { Order = Orders.None })
.WithNoLockNoWait()
.GroupBy(req.GetToListExp<Sys_UserWallet>())
.ToDictionaryAsync(a => a.Count())
.ConfigureAwait(false);
return ret.Select(x => new KeyValuePair<IImmutableDictionary<string, string>, int>(
req.RequiredFields.ToImmutableDictionary(
y => y, y => typeof(Sys_UserWallet).GetProperty(y)!.GetValue(x.Key)?.ToString()), x.Value))
.Where(x => x.Key.Any(y => !y.Value.NullOrEmpty()))
.OrderByDescending(x => x.Value);
}
/// <inheritdoc />
public async Task<QueryUserWalletRsp> CreateAsync(CreateUserWalletReq req)
{
req.ThrowIfInvalid();
var ret = await Rpo.InsertAsync(req).ConfigureAwait(false);
return ret.Adapt<QueryUserWalletRsp>();
}
/// <inheritdoc />
public Task<int> DeleteAsync(DelReq req)
{
req.ThrowIfInvalid();
return Rpo.DeleteAsync(a => a.Id == req.Id);
}
/// <inheritdoc />
public async Task<QueryUserWalletRsp> EditAsync(EditUserWalletReq req)
{
req.ThrowIfInvalid();
#if DBTYPE_SQLSERVER
return (await UpdateReturnListAsync(req).ConfigureAwait(false)).FirstOrDefault()?.Adapt<QueryUserWalletRsp>();
#else
return await UpdateAsync(req).ConfigureAwait(false) > 0 ? await GetAsync(new QueryUserWalletReq { Id = req.Id }).ConfigureAwait(false) : null;
#endif
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryUserWalletReq> req)
{
req.ThrowIfInvalid();
return ExportAsync<QueryUserWalletReq, QueryUserWalletRsp>(QueryInternal, req, Ln.);
}
/// <inheritdoc />
public async Task<QueryUserWalletRsp> GetAsync(QueryUserWalletReq req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(new QueryReq<QueryUserWalletReq> { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false);
return ret.Adapt<QueryUserWalletRsp>();
}
/// <inheritdoc />
public async Task<PagedQueryRsp<QueryUserWalletRsp>> PagedQueryAsync(PagedQueryReq<QueryUserWalletReq> 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<QueryUserWalletRsp>(req.Page, req.PageSize, total, list.Adapt<IEnumerable<QueryUserWalletRsp>>());
}
/// <inheritdoc />
public async Task<IEnumerable<QueryUserWalletRsp>> QueryAsync(QueryReq<QueryUserWalletReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req).WithNoLockNoWait().Take(req.Count).ToListAsync(req).ConfigureAwait(false);
return ret.Adapt<IEnumerable<QueryUserWalletRsp>>();
}
private ISelect<Sys_UserWallet> QueryInternal(QueryReq<QueryUserWalletReq> req)
{
IEnumerable<long> deptIds = null;
if (req.Filter?.DeptId > 0) {
deptIds = Rpo.Orm.Select<Sys_Dept>().Where(a => a.Id == req.Filter.DeptId).AsTreeCte().ToList(a => a.Id);
}
return QueryInternal(req, deptIds);
}
private ISelect<Sys_UserWallet> QueryInternal(QueryReq<QueryUserWalletReq> req, IEnumerable<long> deptIds)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter)
.WhereIf(req.Filter?.Id > 0, a => a.Id == req.Filter.Id)
.WhereIf(deptIds != null, a => deptIds.Contains(a.Owner.DeptId))
.WhereIf( //
req.Filter?.RoleId > 0, a => a.Owner.Roles.Any(b => b.Id == req.Filter.RoleId));
// 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,145 @@
using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Sys.UserWallet;
using NetAdmin.Domain.Dto.Sys.WalletTrade;
using NetAdmin.Domain.Extensions;
namespace NetAdmin.SysComponent.Application.Services.Sys;
/// <inheritdoc cref="IWalletTradeService" />
public sealed class WalletTradeService(BasicRepository<Sys_WalletTrade, long> rpo) //
: RepositoryService<Sys_WalletTrade, long, IWalletTradeService>(rpo), IWalletTradeService
{
/// <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<QueryWalletTradeReq> req)
{
req.ThrowIfInvalid();
return QueryInternal(req).WithNoLockNoWait().CountAsync();
}
/// <inheritdoc />
public async Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryWalletTradeReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req with { Order = Orders.None })
.WithNoLockNoWait()
.GroupBy(req.GetToListExp<Sys_WalletTrade>())
.ToDictionaryAsync(a => a.Count())
.ConfigureAwait(false);
return ret.Select(x => new KeyValuePair<IImmutableDictionary<string, string>, int>(
req.RequiredFields.ToImmutableDictionary(
y => y, y => typeof(Sys_WalletTrade).GetProperty(y)!.GetValue(x.Key)?.ToString()), x.Value))
.Where(x => x.Key.Any(y => !y.Value.NullOrEmpty()))
.OrderByDescending(x => x.Value);
}
/// <inheritdoc />
public async Task<QueryWalletTradeRsp> CreateAsync(CreateWalletTradeReq req)
{
req.ThrowIfInvalid();
var userWalletService = S<IUserWalletService>();
var wallet = await userWalletService.GetAsync(new QueryUserWalletReq { Id = req.OwnerId!.Value }).ConfigureAwait(false);
if (wallet.AvailableBalance + req.Amount < 0) {
throw new NetAdminInvalidOperationException(Ln.);
}
_ = await userWalletService.EditAsync(wallet.Adapt<EditUserWalletReq>() with {
AvailableBalance = wallet.AvailableBalance + req.Amount
, TotalBalance = wallet.TotalBalance + req.Amount
})
.ConfigureAwait(false) ?? throw new NetAdminUnexpectedException(Ln.);
var ret = await Rpo.InsertAsync(req with { BalanceBefore = wallet.AvailableBalance, OwnerDeptId = wallet.OwnerDeptId }).ConfigureAwait(false);
return ret.Adapt<QueryWalletTradeRsp>();
}
/// <inheritdoc />
public Task<int> DeleteAsync(DelReq req)
{
req.ThrowIfInvalid();
return Rpo.DeleteAsync(a => a.Id == req.Id);
}
/// <inheritdoc />
public async Task<QueryWalletTradeRsp> EditAsync(EditWalletTradeReq req)
{
req.ThrowIfInvalid();
#if DBTYPE_SQLSERVER
return (await UpdateReturnListAsync(req).ConfigureAwait(false)).FirstOrDefault()?.Adapt<QueryWalletTradeRsp>();
#else
return await UpdateAsync(req).ConfigureAwait(false) > 0
? await GetAsync(new QueryWalletTradeReq { Id = req.Id }).ConfigureAwait(false)
: null;
#endif
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryWalletTradeReq> req)
{
req.ThrowIfInvalid();
return ExportAsync<QueryWalletTradeReq, QueryWalletTradeRsp>(QueryInternal, req, Ln.);
}
/// <inheritdoc />
public async Task<QueryWalletTradeRsp> GetAsync(QueryWalletTradeReq req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(new QueryReq<QueryWalletTradeReq> { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false);
return ret.Adapt<QueryWalletTradeRsp>();
}
/// <inheritdoc />
public async Task<PagedQueryRsp<QueryWalletTradeRsp>> PagedQueryAsync(PagedQueryReq<QueryWalletTradeReq> 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<QueryWalletTradeRsp>(req.Page, req.PageSize, total, list.Adapt<IEnumerable<QueryWalletTradeRsp>>());
}
/// <inheritdoc />
public async Task<IEnumerable<QueryWalletTradeRsp>> QueryAsync(QueryReq<QueryWalletTradeReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req).WithNoLockNoWait().Take(req.Count).ToListAsync(req).ConfigureAwait(false);
return ret.Adapt<IEnumerable<QueryWalletTradeRsp>>();
}
private ISelect<Sys_WalletTrade> QueryInternal(QueryReq<QueryWalletTradeReq> 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 IUserWalletCache : ICache<IDistributedCache, IUserWalletService>, IUserWalletModule;

View File

@ -0,0 +1,6 @@
namespace NetAdmin.SysComponent.Cache.Sys.Dependency;
/// <summary>
/// 钱包交易缓存
/// </summary>
public interface IWalletTradeCache : ICache<IDistributedCache, IWalletTradeService>, IWalletTradeModule;

View File

@ -0,0 +1,68 @@
using NetAdmin.Domain.Dto.Sys.UserWallet;
namespace NetAdmin.SysComponent.Cache.Sys;
/// <inheritdoc cref="IUserWalletCache" />
public sealed class UserWalletCache(IDistributedCache cache, IUserWalletService service)
: DistributedCache<IUserWalletService>(cache, service), IScoped, IUserWalletCache
{
/// <inheritdoc />
public Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
return Service.BulkDeleteAsync(req);
}
/// <inheritdoc />
public Task<long> CountAsync(QueryReq<QueryUserWalletReq> req)
{
return Service.CountAsync(req);
}
/// <inheritdoc />
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryUserWalletReq> req)
{
return Service.CountByAsync(req);
}
/// <inheritdoc />
public Task<QueryUserWalletRsp> CreateAsync(CreateUserWalletReq req)
{
return Service.CreateAsync(req);
}
/// <inheritdoc />
public Task<int> DeleteAsync(DelReq req)
{
return Service.DeleteAsync(req);
}
/// <inheritdoc />
public Task<QueryUserWalletRsp> EditAsync(EditUserWalletReq req)
{
return Service.EditAsync(req);
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryUserWalletReq> req)
{
return Service.ExportAsync(req);
}
/// <inheritdoc />
public Task<QueryUserWalletRsp> GetAsync(QueryUserWalletReq req)
{
return Service.GetAsync(req);
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryUserWalletRsp>> PagedQueryAsync(PagedQueryReq<QueryUserWalletReq> req)
{
return Service.PagedQueryAsync(req);
}
/// <inheritdoc />
public Task<IEnumerable<QueryUserWalletRsp>> QueryAsync(QueryReq<QueryUserWalletReq> req)
{
return Service.QueryAsync(req);
}
}

View File

@ -0,0 +1,68 @@
using NetAdmin.Domain.Dto.Sys.WalletTrade;
namespace NetAdmin.SysComponent.Cache.Sys;
/// <inheritdoc cref="IWalletTradeCache" />
public sealed class WalletTradeCache(IDistributedCache cache, IWalletTradeService service)
: DistributedCache<IWalletTradeService>(cache, service), IScoped, IWalletTradeCache
{
/// <inheritdoc />
public Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
return Service.BulkDeleteAsync(req);
}
/// <inheritdoc />
public Task<long> CountAsync(QueryReq<QueryWalletTradeReq> req)
{
return Service.CountAsync(req);
}
/// <inheritdoc />
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryWalletTradeReq> req)
{
return Service.CountByAsync(req);
}
/// <inheritdoc />
public Task<QueryWalletTradeRsp> CreateAsync(CreateWalletTradeReq req)
{
return Service.CreateAsync(req);
}
/// <inheritdoc />
public Task<int> DeleteAsync(DelReq req)
{
return Service.DeleteAsync(req);
}
/// <inheritdoc />
public Task<QueryWalletTradeRsp> EditAsync(EditWalletTradeReq req)
{
return Service.EditAsync(req);
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryWalletTradeReq> req)
{
return Service.ExportAsync(req);
}
/// <inheritdoc />
public Task<QueryWalletTradeRsp> GetAsync(QueryWalletTradeReq req)
{
return Service.GetAsync(req);
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryWalletTradeRsp>> PagedQueryAsync(PagedQueryReq<QueryWalletTradeReq> req)
{
return Service.PagedQueryAsync(req);
}
/// <inheritdoc />
public Task<IEnumerable<QueryWalletTradeRsp>> QueryAsync(QueryReq<QueryWalletTradeReq> req)
{
return Service.QueryAsync(req);
}
}

View File

@ -0,0 +1,97 @@
using NetAdmin.Domain.Dto.Sys.UserWallet;
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 UserWalletController(IUserWalletCache cache) : ControllerBase<IUserWalletCache, IUserWalletService>(cache), IUserWalletModule
{
/// <summary>
/// 批量删除用户钱包
/// </summary>
[Transaction]
public Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
return Cache.BulkDeleteAsync(req);
}
/// <summary>
/// 用户钱包计数
/// </summary>
public Task<long> CountAsync(QueryReq<QueryUserWalletReq> req)
{
return Cache.CountAsync(req);
}
/// <summary>
/// 用户钱包分组计数
/// </summary>
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryUserWalletReq> req)
{
return Cache.CountByAsync(req);
}
/// <summary>
/// 创建用户钱包
/// </summary>
[Transaction]
public Task<QueryUserWalletRsp> CreateAsync(CreateUserWalletReq req)
{
return Cache.CreateAsync(req);
}
/// <summary>
/// 删除用户钱包
/// </summary>
[Transaction]
public Task<int> DeleteAsync(DelReq req)
{
return Cache.DeleteAsync(req);
}
/// <summary>
/// 编辑用户钱包
/// </summary>
[Transaction]
public Task<QueryUserWalletRsp> EditAsync(EditUserWalletReq req)
{
return Cache.EditAsync(req);
}
/// <summary>
/// 导出用户钱包
/// </summary>
[NonAction]
public Task<IActionResult> ExportAsync(QueryReq<QueryUserWalletReq> req)
{
return Cache.ExportAsync(req);
}
/// <summary>
/// 获取单个用户钱包
/// </summary>
public Task<QueryUserWalletRsp> GetAsync(QueryUserWalletReq req)
{
return Cache.GetAsync(req);
}
/// <summary>
/// 分页查询用户钱包
/// </summary>
public Task<PagedQueryRsp<QueryUserWalletRsp>> PagedQueryAsync(PagedQueryReq<QueryUserWalletReq> req)
{
return Cache.PagedQueryAsync(req);
}
/// <summary>
/// 查询用户钱包
/// </summary>
[NonAction]
public Task<IEnumerable<QueryUserWalletRsp>> QueryAsync(QueryReq<QueryUserWalletReq> req)
{
return Cache.QueryAsync(req);
}
}

View File

@ -0,0 +1,97 @@
using NetAdmin.Domain.Dto.Sys.WalletTrade;
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 WalletTradeController(IWalletTradeCache cache) : ControllerBase<IWalletTradeCache, IWalletTradeService>(cache), IWalletTradeModule
{
/// <summary>
/// 批量删除钱包交易
/// </summary>
[Transaction]
public Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
return Cache.BulkDeleteAsync(req);
}
/// <summary>
/// 钱包交易计数
/// </summary>
public Task<long> CountAsync(QueryReq<QueryWalletTradeReq> req)
{
return Cache.CountAsync(req);
}
/// <summary>
/// 钱包交易分组计数
/// </summary>
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryWalletTradeReq> req)
{
return Cache.CountByAsync(req);
}
/// <summary>
/// 创建钱包交易
/// </summary>
[Transaction]
public Task<QueryWalletTradeRsp> CreateAsync(CreateWalletTradeReq req)
{
return Cache.CreateAsync(req);
}
/// <summary>
/// 删除钱包交易
/// </summary>
[Transaction]
public Task<int> DeleteAsync(DelReq req)
{
return Cache.DeleteAsync(req);
}
/// <summary>
/// 编辑钱包交易
/// </summary>
[Transaction]
public Task<QueryWalletTradeRsp> EditAsync(EditWalletTradeReq req)
{
return Cache.EditAsync(req);
}
/// <summary>
/// 导出钱包交易
/// </summary>
[NonAction]
public Task<IActionResult> ExportAsync(QueryReq<QueryWalletTradeReq> req)
{
return Cache.ExportAsync(req);
}
/// <summary>
/// 获取单个钱包交易
/// </summary>
public Task<QueryWalletTradeRsp> GetAsync(QueryWalletTradeReq req)
{
return Cache.GetAsync(req);
}
/// <summary>
/// 分页查询钱包交易
/// </summary>
public Task<PagedQueryRsp<QueryWalletTradeRsp>> PagedQueryAsync(PagedQueryReq<QueryWalletTradeReq> req)
{
return Cache.PagedQueryAsync(req);
}
/// <summary>
/// 查询钱包交易
/// </summary>
[NonAction]
public Task<IEnumerable<QueryWalletTradeRsp>> QueryAsync(QueryReq<QueryWalletTradeReq> req)
{
return Cache.QueryAsync(req);
}
}

View File

@ -45,21 +45,21 @@ public sealed class OperationLogger : IEventSubscriber
// 插入登录日志 // 插入登录日志
if (log.ApiPathCrc32 == Chars.FLG_PATH_API_SYS_USER_LOGIN_BY_PWD.Crc32()) { if (log.ApiPathCrc32 == Chars.FLG_PATH_API_SYS_USER_LOGIN_BY_PWD.Crc32()) {
_ = await App.GetService<ILoginLogCache>().CreateAsync(log.Adapt<CreateLoginLogReq>()).ConfigureAwait(false); _ = await S<ILoginLogCache>().CreateAsync(log.Adapt<CreateLoginLogReq>()).ConfigureAwait(false);
} }
} }
// 如果首尾日期不一致,要分别插入不同的日期分表 // 如果首尾日期不一致,要分别插入不同的日期分表
if (inserts[0].CreatedTime.Date != inserts[^1].CreatedTime.Date) { if (inserts[0].CreatedTime.Date != inserts[^1].CreatedTime.Date) {
foreach (var dayInserts in inserts.GroupBy(x => x.CreatedTime.Date)) { foreach (var dayInserts in inserts.GroupBy(x => x.CreatedTime.Date)) {
await App.GetService<IFreeSql>() await S<IFreeSql>()
.Insert<Sys_RequestLog>(dayInserts.Select(x => x)) .Insert<Sys_RequestLog>(dayInserts.Select(x => x))
.ExecuteSqlBulkCopyAsync(tableName: $"{nameof(Sys_RequestLog)}_{dayInserts.Key:yyyyMMdd}") .ExecuteSqlBulkCopyAsync(tableName: $"{nameof(Sys_RequestLog)}_{dayInserts.Key:yyyyMMdd}")
.ConfigureAwait(false); .ConfigureAwait(false);
} }
} }
else { else {
await App.GetService<IFreeSql>() await S<IFreeSql>()
.Insert<Sys_RequestLog>(inserts) .Insert<Sys_RequestLog>(inserts)
.ExecuteSqlBulkCopyAsync(tableName: $"{nameof(Sys_RequestLog)}_{inserts[0].CreatedTime:yyyyMMdd}") .ExecuteSqlBulkCopyAsync(tableName: $"{nameof(Sys_RequestLog)}_{inserts[0].CreatedTime:yyyyMMdd}")
.ConfigureAwait(false); .ConfigureAwait(false);

View File

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

View File

@ -10,8 +10,8 @@
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "2.3.1", "@element-plus/icons-vue": "2.3.1",
"ace-builds": "1.42.0", "ace-builds": "1.43.0",
"aieditor": "1.3.9", "aieditor": "1.4.0",
"axios": "1.10.0", "axios": "1.10.0",
"crypto-js": "4.2.0", "crypto-js": "4.2.0",
"dayjs": "1.11.13", "dayjs": "1.11.13",
@ -23,8 +23,8 @@
"nprogress": "0.2.0", "nprogress": "0.2.0",
"sortablejs": "1.15.6", "sortablejs": "1.15.6",
"vkbeautify": "0.99.3", "vkbeautify": "0.99.3",
"vue": "3.5.16", "vue": "3.5.17",
"vue-i18n": "11.1.6", "vue-i18n": "11.1.7",
"vue-router": "4.5.1", "vue-router": "4.5.1",
"vue3-ace-editor": "2.2.4", "vue3-ace-editor": "2.2.4",
"vue3-json-viewer": "2.4.0", "vue3-json-viewer": "2.4.0",
@ -32,12 +32,12 @@
"vuex": "4.1.0" "vuex": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "5.2.4", "@vitejs/plugin-vue": "6.0.0",
"prettier": "3.5.3", "prettier": "3.6.1",
"prettier-plugin-organize-attributes": "1.0.0", "prettier-plugin-organize-attributes": "1.0.0",
"sass": "1.89.2", "sass": "1.89.2",
"terser": "5.43.0", "terser": "5.43.1",
"vite": "6.3.5" "vite": "7.0.0"
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",

View File

@ -0,0 +1,95 @@
/**
* 用户钱包服务
* @module @/api/sys/user.wallet
*/
import config from '@/config'
import http from '@/utils/request'
export default {
/**
* 批量删除用户钱包
*/
bulkDelete: {
url: `${config.API_URL}/api/sys/user.wallet/bulk.delete`,
name: `批量删除用户钱包`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 用户钱包计数
*/
count: {
url: `${config.API_URL}/api/sys/user.wallet/count`,
name: `用户钱包计数`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 用户钱包分组计数
*/
countBy: {
url: `${config.API_URL}/api/sys/user.wallet/count.by`,
name: `用户钱包分组计数`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 创建用户钱包
*/
create: {
url: `${config.API_URL}/api/sys/user.wallet/create`,
name: `创建用户钱包`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 删除用户钱包
*/
delete: {
url: `${config.API_URL}/api/sys/user.wallet/delete`,
name: `删除用户钱包`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 编辑用户钱包
*/
edit: {
url: `${config.API_URL}/api/sys/user.wallet/edit`,
name: `编辑用户钱包`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 获取单个用户钱包
*/
get: {
url: `${config.API_URL}/api/sys/user.wallet/get`,
name: `获取单个用户钱包`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 分页查询用户钱包
*/
pagedQuery: {
url: `${config.API_URL}/api/sys/user.wallet/paged.query`,
name: `分页查询用户钱包`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
}

View File

@ -0,0 +1,95 @@
/**
* 钱包交易服务
* @module @/api/sys/wallet.trade
*/
import config from '@/config'
import http from '@/utils/request'
export default {
/**
* 批量删除钱包交易
*/
bulkDelete: {
url: `${config.API_URL}/api/sys/wallet.trade/bulk.delete`,
name: `批量删除钱包交易`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 钱包交易计数
*/
count: {
url: `${config.API_URL}/api/sys/wallet.trade/count`,
name: `钱包交易计数`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 钱包交易分组计数
*/
countBy: {
url: `${config.API_URL}/api/sys/wallet.trade/count.by`,
name: `钱包交易分组计数`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 创建钱包交易
*/
create: {
url: `${config.API_URL}/api/sys/wallet.trade/create`,
name: `创建钱包交易`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 删除钱包交易
*/
delete: {
url: `${config.API_URL}/api/sys/wallet.trade/delete`,
name: `删除钱包交易`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 编辑钱包交易
*/
edit: {
url: `${config.API_URL}/api/sys/wallet.trade/edit`,
name: `编辑钱包交易`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 获取单个钱包交易
*/
get: {
url: `${config.API_URL}/api/sys/wallet.trade/get`,
name: `获取单个钱包交易`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 分页查询钱包交易
*/
pagedQuery: {
url: `${config.API_URL}/api/sys/wallet.trade/paged.query`,
name: `分页查询钱包交易`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
}

View File

@ -60,6 +60,9 @@ export default {
hasPermission: function (p) { hasPermission: function (p) {
return this.permissions.includes('*/*/*') || this.permissions.some((a) => a === p) return this.permissions.includes('*/*/*') || this.permissions.some((a) => a === p)
}, },
hasApiPermission: function (p) {
return this.apiPermissions.includes('*/*/*') || this.apiPermissions.some((a) => a === p)
},
} }
app.use(JsonViewer) app.use(JsonViewer)

View File

@ -6,7 +6,7 @@
<el-button <el-button
@click=" @click="
() => { () => {
this.$router.push({ path: '/sys/job', query: { view: 'fail' } }) this.$router.push({ path: '/system/job', query: { view: 'fail' } })
this.$emit('closed') this.$emit('closed')
} }
" "
@ -22,7 +22,7 @@
<el-button <el-button
@click=" @click="
() => { () => {
this.$router.push({ path: '/sys/job' }) this.$router.push({ path: '/system/job' })
this.$emit('closed') this.$emit('closed')
} }
" "

View File

@ -624,6 +624,6 @@ export default {
链接: 'Link', 链接: 'Link',
框架: 'IFrame', 框架: 'IFrame',
按钮: 'Button', 按钮: 'Button',
倒序排序:'Sort-Descending', 倒序排序: 'Sort-Descending',
顺序排序:'Sort-Ascending', 顺序排序: 'Sort-Ascending',
} }

View File

@ -622,6 +622,6 @@ export default {
链接: '链接', 链接: '链接',
框架: '框架', 框架: '框架',
按钮: '按钮', 按钮: '按钮',
倒序排序:'倒序排序', 倒序排序: '倒序排序',
顺序排序:'顺序排序', 顺序排序: '顺序排序',
} }

View File

@ -51,5 +51,12 @@ export default {
global.user?.roles.findIndex((x) => x.ignorePermissionControl) >= 0 global.user?.roles.findIndex((x) => x.ignorePermissionControl) >= 0
? ['*/*/*'] ? ['*/*/*']
: tool.recursiveFindProperty(preloads[0]?.data, 'type', 'button').map((x) => x.tag) : tool.recursiveFindProperty(preloads[0]?.data, 'type', 'button').map((x) => x.tag)
global.apiPermissions =
global.user?.roles.findIndex((x) => x.ignorePermissionControl) >= 0
? ['*/*/*']
: preloads[1]?.data?.roles
?.map((x) => x.apiIds.join(','))
?.join(',')
.split(',')
}, },
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<el-card :header="$t('登录日志')" shadow="never"> <el-card :header="$t('登录日志')" shadow="never">
<login-log :keywords="$GLOBAL.user.id" :show-filter="false"></login-log> <login-log :ownerId="$GLOBAL.user.id.toString()" :show-filter="false"></login-log>
</el-card> </el-card>
</template> </template>

View File

@ -74,10 +74,19 @@
<na-search <na-search
:controls="[ :controls="[
{ {
type: 'input', type: 'select-input',
field: ['root', 'keywords'], field: [
placeholder: $t('日志编号 / 登录名 / 客户端IP'), 'dy',
[
{ label: $t('日志编号'), key: 'id' },
{ label: $t('用户编号'), key: 'owner.id' },
{ label: $t('登录名'), key: 'loginUserName' },
{ label: $t('客户端IP'), key: 'createdClientIp' },
],
],
placeholder: $t('匹配内容'),
style: 'width:25rem', style: 'width:25rem',
selectStyle: 'width:8rem',
}, },
]" ]"
:vue="this" :vue="this"
@ -150,7 +159,15 @@ export default {
naInfo, naInfo,
}, },
computed: {}, computed: {},
created() {}, created() {
if (this.ownerId) {
this.query.dynamicFilter.filters.push({
field: 'owner.id',
operator: 'eq',
value: this.ownerId,
})
}
},
data() { data() {
return { return {
statistics: { statistics: {
@ -227,8 +244,11 @@ export default {
this.$refs.search.search() this.$refs.search.search()
}, },
onReset() { onReset() {
if (!this.showFilter) return if (this.showFilter) {
Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = [''])) Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))
}
this.$refs.search.selectInputKey = 'id'
if (this.ownerId) this.$refs.search.selectInputKey = 'owner.id'
}, },
//搜索 //搜索
async onSearch(form) { async onSearch(form) {
@ -255,6 +275,13 @@ export default {
) )
} }
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.errorCode === 'string' && form.dy.errorCode.trim() !== '') { if (typeof form.dy.errorCode === 'string' && form.dy.errorCode.trim() !== '') {
this.query.dynamicFilter.filters.push({ this.query.dynamicFilter.filters.push({
field: 'errorCode', field: 'errorCode',
@ -279,6 +306,15 @@ export default {
}) })
} }
if (typeof form.dy['owner.id'] === 'string' && form.dy['owner.id'].trim() !== '') {
this.$refs.search.selectInputKey = 'owner.id'
this.query.dynamicFilter.filters.push({
field: 'owner.id',
operator: 'eq',
value: form.dy['owner.id'],
})
}
await this.$refs.table.upData() await this.$refs.table.upData()
}, },
@ -295,6 +331,15 @@ export default {
}, },
}, },
async mounted() { async mounted() {
if (this.ownerId) {
this.$refs.search.selectInputKey = 'owner.id'
this.$refs.search.form.dy['owner.id'] = this.ownerId
this.$refs.search.keeps.push({
field: 'owner.id',
value: this.ownerId,
type: 'dy',
})
}
if (this.keywords) { if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keeps.push({ this.$refs.search.keeps.push({
@ -303,8 +348,9 @@ export default {
type: 'root', type: 'root',
}) })
} }
this.onReset()
}, },
props: { keywords: { type: String }, showFilter: { type: Boolean, default: true } }, props: { keywords: { type: String }, showFilter: { type: Boolean, default: true }, ownerId: { type: String } },
watch: {}, watch: {},
} }
</script> </script>

View File

@ -67,7 +67,7 @@
config: { props: { label: 'userName', value: 'id' } }, config: { props: { label: 'userName', value: 'id' } },
placeholder: '用户', placeholder: '用户',
style: 'width:15rem', style: 'width:15rem',
condition: () => $GLOBAL.hasPermission('sys/log/operation/user'), condition: () => $GLOBAL.hasApiPermission('api/sys/user/query'),
}, },
{ {
multiple: true, multiple: true,

View File

@ -0,0 +1,323 @@
<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: 'tradeDirection',
options: [
{ label: '全部', value: '' },
...Object.entries(this.$GLOBAL.enums.tradeDirections).map((x) => {
return {
value: x[0],
label: x[1][1],
badge: this.statistics.tradeDirection?.find((y) => y.key.tradeDirection.toLowerCase() === x[0].toLowerCase())
?.value,
}
}),
],
},
{
title: $t('交易类型'),
key: 'tradeType',
options: [
{ label: '全部', value: '' },
...Object.entries(this.$GLOBAL.enums.tradeTypes).map((x) => {
return {
value: x[0],
label: x[1][1],
badge: this.statistics.tradeType?.find((y) => y.key.tradeType.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' },
],
],
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"></div>
</el-header>
<el-main class="nopadding">
<scTable
:context-menus="[
'id',
'ownerId',
'createdTime',
'tradeType',
'amount',
'balanceBefore',
'summary',
'owner.userName',
'tradeDirection',
]"
:context-multi="{ id: ['createdTime'], ownerId: ['owner.userName'] }"
:context-opers="['view']"
:default-sort="{ prop: 'id', order: 'descending' }"
:export-api="$API.sys_wallettrade.export"
:params="query"
:query-api="$API.sys_wallettrade.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.tradeDirections).map((x) => {
return { value: x[0], text: `${x[1][1]}`, type: x[1][2], pulse: x[1][3] === 'true' }
})
"
align="center"
prop="tradeDirection"
sortable="custom" />
<naColIndicator
:label="$t('交易类型')"
:options="
Object.entries(this.$GLOBAL.enums.tradeTypes).map((x) => {
return { value: x[0], text: `${x[1][1]}`, type: x[1][2], pulse: x[1][3] === 'true' }
})
"
align="center"
prop="tradeType"
sortable="custom" />
<el-table-column
:formatter="(row) => $TOOL.groupSeparator(row.balanceBefore)"
:label="$t('交易前余额')"
align="right"
prop="balanceBefore"
sortable="custom" />
<el-table-column
:formatter="(row) => $TOOL.groupSeparator(row.amount)"
:label="$t('发生金额')"
align="right"
prop="amount"
sortable="custom" />
<el-table-column :label="$t('交易后余额')" align="right">
<template #default="{ row }">
{{ $TOOL.groupSeparator(row.balanceBefore + row.amount) }}
</template>
</el-table-column>
<el-table-column :label="$t('备注')" min-width="100" prop="summary" show-overflow-tooltip sortable="custom" />
<naColOperation :buttons="[naColOperation.buttons[0]]" :vue="this" width="50" />
</scTable>
</el-main>
</el-container>
<save-dialog
v-if="dialog.save"
@closed="dialog.save = null"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => $refs.table.refresh()"
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_wallettrade.countBy.post({
dynamicFilter: {
filters: this.query.dynamicFilter.filters,
},
requiredFields: ['TradeDirection'],
}),
this.$API.sys_wallettrade.countBy.post({
dynamicFilter: {
filters: this.query.dynamicFilter.filters,
},
requiredFields: ['TradeType'],
}),
])
this.statistics.tradeDirection = res[0].data
this.statistics.tradeType = 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['tradeType'] === 'string' && form.dy['tradeType'].trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'tradeType',
operator: 'eq',
value: form.dy['tradeType'],
})
}
if (typeof form.dy['tradeDirection'] === 'string' && form.dy['tradeDirection'].trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'tradeDirection',
operator: 'eq',
value: form.dy['tradeDirection'],
})
}
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,119 @@
<template>
<scDialog v-model="visible" :title="`${titleMap[mode]}${form?.id ?? '...'}`" @closed="$emit('closed')" destroy-on-close full-screen>
<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'" :model="form" :rules="rules" label-width="15rem" ref="dialogForm">
<el-form-item :label="$t('唯一编码')" prop="id">
<el-input v-model="form.id" clearable />
</el-form-item>
<el-form-item :label="$t('交易方向')" prop="tradeDirection">
<el-input v-model="form.tradeDirection" clearable />
</el-form-item>
<el-form-item :label="$t('交易类型')" prop="tradeType">
<el-input v-model="form.tradeType" clearable />
</el-form-item>
<el-form-item :label="$t('交易前余额')" prop="balanceBefore">
<el-input v-model="form.balanceBefore" clearable />
</el-form-item>
<el-form-item :label="$t('交易金额')" prop="amount">
<el-input v-model="form.amount" clearable />
</el-form-item>
<el-form-item :label="$t('交易后余额')">
<el-input :value="form.balanceBefore + form.amount" clearable />
</el-form-item>
<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-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-input v-model="form.createdUserName" clearable />
</el-form-item>
<el-form-item :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 !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</scDialog>
</template>
<script>
export default {
components: {},
data() {
return {
//表单数据
form: {},
loading: true,
mode: 'add',
//验证规则
rules: {},
tabId: 'basic',
titleMap: {
add: this.$t('新增交易'),
edit: this.$t('编辑交易'),
view: this.$t('查看交易'),
},
visible: false,
}
},
emits: ['success', 'closed', 'mounted'],
methods: {
//显示
async open(data) {
this.visible = true
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_wallettrade.get.post({ id: data.row.id })
if (res.data) {
Object.assign(this.form, res.data)
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
//
this.loading = false
},
},
mounted() {
this.$emit('mounted')
},
}
</script>
<style scoped></style>

View File

@ -116,8 +116,8 @@
<naColAvatar :label="$t('用户名')" prop="userName" width="170" /> <naColAvatar :label="$t('用户名')" prop="userName" width="170" />
<el-table-column :label="$t('手机号 / 邮箱')" align="right" prop="mobile" sortable="custom" width="250"> <el-table-column :label="$t('手机号 / 邮箱')" align="right" prop="mobile" sortable="custom" width="250">
<template #default="{ row }"> <template #default="{ row }">
<p>{{ row.mobile }}</p> <p>{{ row.mobile ?? '-' }}</p>
<p>{{ row.email }}</p> <p>{{ row.email ?? '-' }}</p>
</template> </template>
</el-table-column> </el-table-column>
<naColTags <naColTags

View File

@ -7,7 +7,7 @@
destroy-on-close destroy-on-close
full-screen> full-screen>
<el-form <el-form
:disabled="mode === 'view' && tabId !== 'log'" :disabled="mode === 'view' && tabId !== 'log' && tabId !== 'wallet' && tabId !== 'trade'"
:model="form" :model="form"
:rules="rules" :rules="rules"
label-position="right" label-position="right"
@ -234,6 +234,12 @@
</el-col> </el-col>
</el-row> </el-row>
</el-tab-pane> </el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('钱包信息')" name="wallet">
<wallet v-if="tabId === 'wallet'" :id="form.id.toString()" />
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('交易流水')" name="trade">
<trade v-if="tabId === 'trade'" :ownerId="form.id.toString()" />
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('操作日志')" name="log"> <el-tab-pane v-if="mode === 'view'" :label="$t('操作日志')" name="log">
<log v-if="tabId === 'log'" :owner-id="form.id"></log> <log v-if="tabId === 'log'" :owner-id="form.id"></log>
</el-tab-pane> </el-tab-pane>
@ -259,12 +265,14 @@
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
const log = defineAsyncComponent(() => import('@/views/sys/log/operation')) const log = defineAsyncComponent(() => import('@/views/sys/log/operation'))
const trade = defineAsyncComponent(() => import('@/views/sys/trade'))
const wallet = defineAsyncComponent(() => import('@/views/sys/wallet'))
const naArea = defineAsyncComponent(() => import('@/components/naArea')) const naArea = defineAsyncComponent(() => import('@/components/naArea'))
const naDept = defineAsyncComponent(() => import('@/components/naDept')) const naDept = defineAsyncComponent(() => import('@/components/naDept'))
const scUpload = defineAsyncComponent(() => import('@/components/scUpload')) const scUpload = defineAsyncComponent(() => import('@/components/scUpload'))
const scSelect = defineAsyncComponent(() => import('@/components/scSelect')) const scSelect = defineAsyncComponent(() => import('@/components/scSelect'))
export default { export default {
components: { log, naArea, naDept, scUpload, scSelect }, components: { log, naArea, naDept, scUpload, scSelect, trade, wallet },
data() { data() {
return { return {
//表单数据 //表单数据

View File

@ -0,0 +1,315 @@
<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>
<div class="left-panel">
<na-search
:controls="[
{
type: 'select-input',
field: [
'dy',
[
{ label: $t('用户编号'), key: 'id' },
{ label: $t('用户名'), key: 'owner.userName' },
{ label: $t('电子邮箱'), key: 'owner.email' },
{ label: $t('手机号'), key: 'owner.mobile' },
],
],
placeholder: $t('匹配内容'),
style: 'width:25rem',
selectStyle: 'width:8rem',
},
{
type: 'remote-select',
field: ['filter', 'deptId'],
api: $API.sys_dept.query,
config: { props: { label: 'name', value: 'id' } },
placeholder: $t('所属部门'),
style: 'width:15rem',
condition: () => $GLOBAL.hasApiPermission('api/sys/dept/query'),
},
{
type: 'remote-select',
field: ['filter', 'roleId'],
api: $API.sys_role.query,
config: { props: { label: 'name', value: 'id' } },
placeholder: $t('所属角色'),
style: 'width:15rem',
condition: () => $GLOBAL.hasApiPermission('api/sys/dept/query'),
},
]"
: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"></div>
</el-header>
<el-main class="nopadding">
<scTable
:context-menus="[
'id',
'ownerId',
'owner.userName',
'createdTime',
'totalBalance',
'availableBalance',
'frozenBalance',
'totalIncome',
'totalExpenditure',
'modifiedTime',
]"
:context-multi="{ id: ['createdTime'], ownerId: ['owner.userName'] }"
:context-opers="['view']"
:default-sort="{ prop: 'id', order: 'descending' }"
:export-api="$API.sys_userwallet.export"
:params="query"
:query-api="$API.sys_userwallet.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('所属用户')"
nestProp="owner.userName"
nestProp2="ownerId"
prop="ownerId"
sortable="custom"
width="170"></naColUser>
<el-table-column
:formatter="(row) => $TOOL.groupSeparator(row.totalBalance)"
:label="$t('总余额')"
align="right"
prop="totalBalance"
sortable="custom" />
<el-table-column
:formatter="(row) => $TOOL.groupSeparator(row.availableBalance)"
:label="$t('可用余额')"
align="right"
prop="availableBalance"
sortable="custom" />
<el-table-column
:formatter="(row) => $TOOL.groupSeparator(row.frozenBalance)"
:label="$t('冻结余额')"
align="right"
prop="frozenBalance"
sortable="custom" />
<el-table-column
:formatter="(row) => $TOOL.groupSeparator(row.totalIncome)"
:label="$t('总收入')"
align="right"
prop="totalIncome"
sortable="custom" />
<el-table-column
:formatter="(row) => $TOOL.groupSeparator(row.totalExpenditure)"
:label="$t('总支出')"
align="right"
prop="totalExpenditure"
sortable="custom" />
<el-table-column v-tim :label="$t('最后交易时间')" align="right" prop="modifiedTime" sortable="custom" width="150">
<template #default="{ row }">
<span v-if="row.modifiedTime" v-time.tip="row.modifiedTime" :title="row.modifiedTime"></span>
</template>
</el-table-column>
<naColOperation
:buttons="[
naColOperation.buttons[0],
{
icon: 'el-icon-plus',
title: $t('新建交易'),
click: async (row, vue) => {
vue.dialog.trade = { row }
},
condition: () => {
return $GLOBAL.hasApiPermission('api/sys/wallet.trade/create')
},
},
]"
:vue="this"
width="120" />
</scTable>
</el-main>
</el-container>
<trade-dialog
v-if="dialog.trade"
@closed="dialog.trade = null"
@mounted="$refs.tradeDialog.open(dialog.trade)"
@success="(data, mode) => $refs.table.refresh()"
ref="tradeDialog"></trade-dialog>
<save-dialog
v-if="dialog.save"
@closed="dialog.save = null"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => $refs.table.refresh()"
ref="saveDialog"></save-dialog>
</template>
<script>
import { defineAsyncComponent } from 'vue'
import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
const tradeDialog = defineAsyncComponent(() => import('./trade.vue'))
const saveDialog = defineAsyncComponent(() => import('./save.vue'))
const naColUser = defineAsyncComponent(() => import('@/components/naColUser'))
export default {
components: {
tradeDialog,
saveDialog,
naColUser,
},
computed: {
naColOperation() {
return naColOperation
},
table() {
return table
},
},
async created() {
if (this.roleId) {
this.query.filter.roleId = this.roleId
}
if (this.deptId) {
this.query.filter.deptId = this.deptId
}
if (this.id) {
this.query.dynamicFilter.filters.push({ field: 'id', operator: 'eq', value: this.id })
}
},
data() {
return {
statistics: {
total: '...',
},
dialog: {},
loading: false,
query: {
dynamicFilter: {
filters: [],
},
filter: {},
keywords: this.keywords,
},
selection: [],
}
},
inject: ['reload'],
methods: {
async getStatistics() {
this.statistics.total = this.$refs.table?.total
},
//重置
onReset() {
if (this.id) {
this.$refs.search.selectInputKey = 'id'
}
},
//搜索
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['owner.email'] === 'string' && form.dy['owner.email'].trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'owner.email',
operator: 'eq',
value: form.dy['owner.email'],
})
}
if (typeof form.dy['owner.mobile'] === 'string' && form.dy['owner.mobile'].trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'owner.mobile',
operator: 'eq',
value: form.dy['owner.mobile'],
})
}
if (typeof form.dy['id'] === 'string' && form.dy['id'].trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'id',
operator: 'eq',
value: form.dy['id'],
})
}
await this.$refs.table.upData()
},
},
async mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keeps.push({
field: 'keywords',
value: this.keywords,
type: 'root',
})
}
if (this.id) {
this.$refs.search.selectInputKey = 'id'
this.$refs.search.form.dy.id = this.id
this.$refs.search.keeps.push({
field: 'id',
value: this.id,
type: 'dy',
})
}
if (this.roleId) {
this.$refs.search.form.filter.roleId = this.roleId
this.$refs.search.keeps.push({
field: 'roleId',
value: this.roleId,
type: 'filter',
})
}
if (this.deptId) {
this.$refs.search.form.filter.deptId = this.deptId
this.$refs.search.keeps.push({
field: 'deptId',
value: this.deptId,
type: 'filter',
})
}
this.onReset()
},
props: ['keywords', 'roleId', 'deptId', 'id'],
watch: {},
}
</script>
<style scoped></style>

View File

@ -0,0 +1,124 @@
<template>
<scDialog v-model="visible" :title="`${titleMap[mode]}${form?.id ?? '...'}`" @closed="$emit('closed')" destroy-on-close full-screen>
<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'" :model="form" :rules="rules" label-width="15rem" ref="dialogForm">
<el-form-item :label="$t('唯一编码')" prop="id">
<el-input v-model="form.id" clearable />
</el-form-item>
<el-form-item :label="$t('总余额')" prop="totalBalance">
<el-input v-model="form.totalBalance" clearable />
</el-form-item>
<el-form-item :label="$t('可用余额')" prop="availableBalance">
<el-input v-model="form.availableBalance" clearable />
</el-form-item>
<el-form-item :label="$t('冻结余额')" prop="frozenBalance">
<el-input v-model="form.frozenBalance" clearable />
</el-form-item>
<el-form-item :label="$t('总收入')" prop="totalIncome">
<el-input v-model="form.totalIncome" clearable />
</el-form-item>
<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-input v-model="form.ownerDeptId" clearable />
</el-form-item>
<el-form-item :label="$t('所有者用户编号')" prop="ownerId">
<el-input v-model="form.ownerId" clearable />
</el-form-item>
<el-form-item :label="$t('创建时间')" prop="createdTime">
<el-input v-model="form.createdTime" clearable />
</el-form-item>
<el-form-item :label="$t('修改时间')" prop="modifiedTime">
<el-input v-model="form.modifiedTime" clearable />
</el-form-item>
<el-form-item :label="$t('数据版本')" prop="version">
<el-input v-model="form.version" :disabled="mode === 'edit'" clearable />
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('交易流水')" name="trade">
<trade v-if="tabId === 'trade'" :ownerId="form.ownerId.toString()" />
</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 !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</scDialog>
</template>
<script>
import { defineAsyncComponent } from 'vue'
const trade = defineAsyncComponent(() => import('@/views/sys/trade'))
export default {
components: { trade },
data() {
return {
//表单数据
form: {},
loading: true,
mode: 'add',
//验证规则
rules: {},
tabId: 'basic',
titleMap: {
add: this.$t('新增钱包'),
edit: this.$t('编辑钱包'),
view: this.$t('查看钱包'),
},
visible: false,
}
},
emits: ['success', 'closed', 'mounted'],
methods: {
//显示
async open(data) {
this.visible = true
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_userwallet.get.post({ id: data.row.id })
if (res.data) {
Object.assign(this.form, res.data)
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
//
this.loading = false
},
},
mounted() {
this.$emit('mounted')
},
}
</script>
<style scoped></style>

View File

@ -0,0 +1,91 @@
<template>
<scDialog v-model="visible" :title="$t('新建交易')" @closed="$emit('closed')" append-to-body destroy-on-close>
<el-form :model="form" :rules="rules" label-position="right" label-width="12rem" ref="dialogForm" style="height: 100%">
<el-form-item>
<el-descriptions border column="1">
<el-descriptions-item :label="$t('用户名')">
<b>{{ row.owner.userName }}</b>
</el-descriptions-item>
<el-descriptions-item :label="$t('用户编号')">{{ row.owner.id }}</el-descriptions-item>
<el-descriptions-item :label="$t('可用余额')">{{ $TOOL.groupSeparator(row.availableBalance) }}</el-descriptions-item>
</el-descriptions>
</el-form-item>
<el-form-item :label="$t('交易类型')" prop="tradeType">
<el-select v-model="form.tradeType" clearable filterable>
<el-option v-for="(item, i) in $GLOBAL.enums.tradeTypes" :key="i" :label="item[1]" :value="i" />
</el-select>
</el-form-item>
<el-form-item :label="$t('交易金额')" prop="amount">
<el-input-number v-model="form.amount" :max="999999999" :min="-999999999" precision="0" style="width: 15rem"></el-input-number>
</el-form-item>
<el-form-item :label="$t('备注')" prop="summary">
<el-input v-model="form.summary" rows="3" type="textarea" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</scDialog>
</template>
<script>
import { defineAsyncComponent } from 'vue'
export default {
components: {},
data() {
return {
//表单数据
form: {},
row: {},
loading: true,
//验证规则
rules: {
tradeType: [{ required: true, message: this.$t('请选择交易类型') }],
amount: [{ required: true, message: this.$t('请输入交易金额') }],
},
tabId: '0',
titleMap: {
add: this.$t('新增用户'),
edit: this.$t('编辑用户'),
view: this.$t('查看用户'),
},
visible: false,
}
},
emits: ['success', 'closed', 'mounted'],
methods: {
//显示
async open(data) {
this.row = data.row
this.form.ownerId = data.row.id
this.visible = true
this.loading = false
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_wallettrade.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>