36 Commits
7.7 ... 7.8

Author SHA1 Message Date
wintel
fe152777d0 fix: 强化代码生成器 2025-05-18 21:14:28 +08:00
wintel
aeaaa29122 Merge branch 'main' of https://gitee.com/dotnetchina/OpenAuth.Net into main 2025-05-18 17:55:23 +08:00
yubaolee
78b627ef18 docs: 增加多表代码生成视频 2025-05-18 17:33:26 +08:00
yubaolee
16fd40174c docs: 增加多表代码生成文档 2025-05-18 15:49:33 +08:00
wintel
935064f440 chore: 完善gitignore 2025-05-17 09:41:17 +08:00
yubaolee
dde08ed0d7 docs: update 2025-05-16 15:16:21 +08:00
yubaolee
61cf133db0 fix: #I7XLQK 2025-05-16 14:34:16 +08:00
wintel
9a7d114658 fix: I7XLQK checkbox控件值表单条件判断 2025-05-16 00:24:21 +08:00
yubaolee
41bbeb6b2f fix: 添加记录后对打印参数的影响 2025-05-15 16:40:06 +08:00
yubaolee
591ae5a09a refactor: 删除FlowinstanceTransitionHistory 2025-05-15 15:39:15 +08:00
yubaolee
cfe60345d2 refactor: 调整为sqlsugar模式 2025-05-14 17:02:35 +08:00
yubaolee
0d098a9a29 refactor: 分类调整为sqlsugar 2025-05-14 11:19:07 +08:00
yubaolee
c5df5a51fd feat: 字典分类增加高级查询 2025-05-14 10:46:19 +08:00
yubaolee
a31d66d069 docs: 完善代码生成文档 2025-05-14 08:58:42 +08:00
wintel
c2cdc65cbe feat: 打印增加高级查询 2025-05-13 21:46:31 +08:00
wintel
7ab6c5393f feat: 数据权限调整为sqlsugar 2025-05-13 21:20:11 +08:00
wintel
08bf4ba5c4 fix: 增加代码生成器高级查询过滤条件 2025-05-13 20:40:24 +08:00
wintel
eb77732a7d feat: 代码生成器单表支持高级查询和列设置 2025-05-13 20:37:21 +08:00
wintel
15c54f71a7 doc: fix doc error 2025-05-12 21:27:24 +08:00
yubaolee
371efbbe87 docs: 更新组件文档 2025-05-12 16:14:59 +08:00
yubaolee
5581f2bca9 docs: 优化组件文档 2025-05-12 14:36:01 +08:00
wintel
22975a3f34 Merge branch 'main' of https://gitee.com/dotnetchina/OpenAuth.Net into main 2025-05-11 22:25:02 +08:00
wintel
3946fc7e81 feat: 增加根据表和数据源获取表结构 2025-05-11 22:24:57 +08:00
李玉宝
fd35eb0f63 update README.md.
Signed-off-by: 李玉宝 <yubaolee@163.com>
2025-05-10 16:34:54 +00:00
wintel
eddb32c6b6 refactor: 优化提交字段校验 2025-05-11 00:22:54 +08:00
wintel
9b78b9e6c7 fix: 优化代码生成器 2025-05-11 00:15:10 +08:00
wintel
0cce0a8f43 feat: 增强vue3代码生成 2025-05-11 00:11:23 +08:00
wintel
a137aaca40 feat: 优化代码生成 2025-05-11 00:04:11 +08:00
wintel
eb3d8bb4b3 fix: 增强vue3动态头部代码生成 2025-05-10 22:00:32 +08:00
wintel
9c0fa9d16a feat: 强化代码生成器 2025-05-10 16:46:09 +08:00
wintel
b23be0571f feat: 强化多表代码生成 2025-05-10 16:29:00 +08:00
yubaolee
de12924fac docs: 修复文档链接 2025-05-08 17:30:30 +08:00
yubaolee
c40f473d82 chore: 增加入库订单单元测试 2025-05-08 16:56:49 +08:00
yubaolee
415da9f69c fix: 修复入库订单更新异常 2025-05-08 16:24:28 +08:00
yubaolee
6ef7c91896 docs: 完善sqlsugar文档 2025-05-08 14:54:03 +08:00
yubaolee
6ae0c4b9b2 chore: 增加cursor codebase indexing忽略配置 2025-05-08 08:59:27 +08:00
43 changed files with 2491 additions and 1242 deletions

17
.cursorignore Normal file
View File

@@ -0,0 +1,17 @@
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
/OpenAuth.App/bin
/OpenAuth.App/obj
/OpenAuth.Mvc/bin
/OpenAuth.Mvc/obj
/OpenAuth.WebApi/bin
/OpenAuth.WebApi/obj
/Infrastructure/bin
/Infrastructure/obj
/OpenAuth.Identity/bin
/OpenAuth.Identity/obj
/newdocs/node_modules
/newdocs/docs/.vuepress/.cache
/newdocs/docs/.vuepress/.temp
/newdocs/docs/.vuepress/dist

6
.gitignore vendored
View File

@@ -22,6 +22,12 @@ bld/
[Oo]bj/
[Ll]og/
# docs
node_modules/
.cache/
.temp/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot

View File

@@ -100,7 +100,7 @@ namespace OpenAuth.App
return result;
}
public string Add(AddOrUpdateBuilderTableReq req)
public void Check(AddOrUpdateBuilderTableReq req)
{
if (string.IsNullOrEmpty(req.TableName))
{
@@ -112,11 +112,18 @@ namespace OpenAuth.App
throw new Exception("模块名称不能为空");
}
if (string.IsNullOrEmpty(req.Namespace))
if(!string.IsNullOrEmpty(req.ParentTableId))
{
throw new Exception("命名空间不能为空");
if(string.IsNullOrEmpty(req.ForeignKey))
{
throw new Exception("当前表为子表时,外键不能为空");
}
}
}
public string Add(AddOrUpdateBuilderTableReq req)
{
Check(req);
var obj = AddTableAndColumns(req.MapTo<BuilderTable>());
UnitWork.Save();
@@ -176,6 +183,7 @@ namespace OpenAuth.App
public void Update(AddOrUpdateBuilderTableReq obj)
{
Check(obj);
var user = _auth.GetCurrentUser().User;
UnitWork.Update<BuilderTable>(u => u.Id == obj.Id, u => new BuilderTable
{
@@ -272,32 +280,103 @@ namespace OpenAuth.App
CheckExistsModule(sysTableInfo.ModuleCode);
string domainContent = string.Empty;
if (sysTableInfo.IsDynamicHeader) //使用动态头部的模版
string updateColumns = string.Empty;
string insertColumns = string.Empty;
foreach (var column in sysColumns)
{
domainContent = FileHelper.ReadFile(@"Template\\SingleTable\\BuildAppWithDynamicHeader.html");
if(column.ColumnName.ToLower() == "id")
{
continue;
}
if(column.ColumnName.ToLower() == "createtime" || column.ColumnName.ToLower() == "updatetime")
{
updateColumns += $" {column.ColumnName} = DateTime.Now,";
insertColumns += $" obj.{column.ColumnName} = DateTime.Now;";
updateColumns += "\r\n";
insertColumns += "\r\n";
continue;
}
if(column.ColumnName.ToLower() == "createuserid" || column.ColumnName.ToLower() == "updateuserid")
{
updateColumns += $" {column.ColumnName} = user.Id,";
insertColumns += $" obj.{column.ColumnName} = user.Id;";
updateColumns += "\r\n";
insertColumns += "\r\n";
continue;
}
if(column.ColumnName.ToLower() == "createusername" || column.ColumnName.ToLower() == "updateusername")
{
updateColumns += $" {column.ColumnName} = user.Name,";
insertColumns += $" obj.{column.ColumnName} = user.Name;";
updateColumns += "\r\n";
insertColumns += "\r\n";
continue;
}
//insert有map函数所以不需要处理只处理update即可
updateColumns += $" {column.ColumnName} = request.{column.ColumnName},";
updateColumns += "\r\n";
}
else
{
domainContent = FileHelper.ReadFile(@"Template\\SingleTable\\BuildApp.html");
}
domainContent = domainContent
.Replace("{TableName}", sysTableInfo.TableName)
.Replace("{ModuleCode}", sysTableInfo.ModuleCode)
.Replace("{ModuleName}", sysTableInfo.ModuleName)
.Replace("{ClassName}", sysTableInfo.ClassName)
.Replace("{StartName}", StratName);
if (!string.IsNullOrEmpty(sysTableInfo.ForeignKey))
{ //替换外键模版
var foreignTemplate = $"objs = objs.Where(u => u.{sysTableInfo.ForeignKey} == request.{sysTableInfo.ForeignKey});";
string domainContent = string.Empty;
//查找是否存在子表的情况
var subTable = Repository.FirstOrDefault(u => u.ParentTableId == sysTableInfo.Id);
if (subTable == null) //如果子表不存在,则用单模版生成
{
if (sysTableInfo.IsDynamicHeader) //使用动态头部的模版
{
domainContent = FileHelper.ReadFile(@"Template\\SingleTable\\BuildAppWithDynamicHeader.html");
}
else
{
domainContent = FileHelper.ReadFile(@"Template\\SingleTable\\BuildApp.html");
}
domainContent = domainContent
.Replace("{ForeignKeyTemplate}", foreignTemplate);
.Replace("{TableName}", sysTableInfo.TableName)
.Replace("{ModuleCode}", sysTableInfo.ModuleCode)
.Replace("{ModuleName}", sysTableInfo.ModuleName)
.Replace("{ClassName}", sysTableInfo.ClassName)
.Replace("{UpdateColumns}", updateColumns)
.Replace("{InsertColumns}", insertColumns)
.Replace("{StartName}", StratName);
//如果有外键(是一个子表)
if (!string.IsNullOrEmpty(sysTableInfo.ForeignKey))
{ //替换外键模版
var foreignTemplate = $"objs = objs.Where(u => u.{sysTableInfo.ForeignKey} == request.{sysTableInfo.ForeignKey});";
domainContent = domainContent
.Replace("{ForeignKeyTemplate}", foreignTemplate);
}
else
{
domainContent = domainContent
.Replace("{ForeignKeyTemplate}", "");
}
}
else
{
if (sysTableInfo.IsDynamicHeader) //使用动态头部的模版
{
domainContent = FileHelper.ReadFile(@"Template\\MultiTable\\BuildAppWithDynamicHeader.html");
}
else
{
domainContent = FileHelper.ReadFile(@"Template\\MultiTable\\BuildApp.html");
}
domainContent = domainContent
.Replace("{ForeignKeyTemplate}", "");
.Replace("{TableName}", sysTableInfo.TableName)
.Replace("{ModuleCode}", sysTableInfo.ModuleCode)
.Replace("{ModuleName}", sysTableInfo.ModuleName)
.Replace("{ClassName}", sysTableInfo.ClassName)
.Replace("{SubForeignKey}", subTable.ForeignKey)
.Replace("{SubClassName}", subTable.ClassName)
.Replace("{SubModuleCode}", subTable.ModuleCode)
.Replace("{UpdateColumns}", updateColumns)
.Replace("{InsertColumns}", insertColumns)
.Replace("{StartName}", StratName);
}
var primarykey = sysColumns.FirstOrDefault(u => u.IsKey);
@@ -383,6 +462,15 @@ namespace OpenAuth.App
attributeBuilder.Append("\r\n\r\n ");
}
//查找是否存在子表的情况
var subTable = Repository.FirstOrDefault(u => u.ParentTableId == sysTableInfo.Id);
if (subTable != null) //如果子表存在,需要加子表的请求参数
{
attributeBuilder.Append($" public List<AddOrUpdate{subTable.ClassName}Req> {subTable.ClassName}Reqs {{ get; set; }}");
attributeBuilder.Append("\r\n\r\n ");
}
domainContent = domainContent.Replace("{ClassName}", sysTableInfo.ClassName)
.Replace("{AttributeList}", attributeBuilder.ToString());
@@ -537,9 +625,6 @@ namespace OpenAuth.App
mapPath +
$"\\OpenAuth.Repository\\Domain\\", tableInfo.ClassName + ".cs",
domainContent);
string openAuthDBContextPath = mapPath + "\\OpenAuth.Repository\\OpenAuthDBContext.cs";
FileHelper.RegxAddContentByParenthesis(openAuthDBContextPath, "public virtual DbSet<" + tableInfo.ClassName + "> " + tableInfo.TableName + "s { get; set; }");
}
Dictionary<string, Type> PrimitiveTypes = new Dictionary<string, Type>()
@@ -569,7 +654,8 @@ namespace OpenAuth.App
{
return "DateTime.Now";
}
else if (type == "bool"){
else if (type == "bool")
{
return "false";
}
return Activator.CreateInstance(t).ToString();

View File

@@ -3,18 +3,15 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Infrastructure;
using Microsoft.EntityFrameworkCore;
using OpenAuth.App.Interface;
using OpenAuth.App.Request;
using OpenAuth.App.Response;
using OpenAuth.Repository;
using OpenAuth.Repository.Domain;
using OpenAuth.Repository.Interface;
using SqlSugar;
namespace OpenAuth.App
{
public class CategoryApp : BaseStringApp<Category,OpenAuthDBContext>
public class CategoryApp : SqlSugarBaseApp<Category>
{
/// <summary>
/// 加载列表
@@ -40,7 +37,7 @@ namespace OpenAuth.App
}
var result = new TableData();
var objs = UnitWork.Find<Category>(null);
var objs = SugarClient.Queryable<Category>();
if (!string.IsNullOrEmpty(request.TypeId))
{
objs = objs.Where(u => u.TypeId == request.TypeId);
@@ -51,11 +48,16 @@ namespace OpenAuth.App
objs = objs.Where(u => u.Id.Contains(request.key) || u.Name.Contains(request.key));
}
if (!string.IsNullOrEmpty(request.sqlWhere))
{
objs = objs.Where(request.sqlWhere);
}
var propertyStr = string.Join(',', columnFields.Select(u =>u.ColumnName));
result.columnFields = columnFields;
result.data = objs.OrderBy(u => u.DtCode)
.Skip((request.page - 1) * request.limit)
.Take(request.limit).Select($"new ({propertyStr})");
.Take(request.limit).Select($"{propertyStr}").ToList();
result.count = await objs.CountAsync();
return result;
}
@@ -67,13 +69,13 @@ namespace OpenAuth.App
var user = _auth.GetCurrentUser().User;
obj.CreateUserId = user.Id;
obj.CreateUserName = user.Name;
await Repository.AddAsync(obj);
await SugarClient.Insertable(obj).ExecuteCommandAsync();
}
public void Update(AddOrUpdateCategoryReq obj)
{
var user = _auth.GetCurrentUser().User;
UnitWork.Update<Category>(u => u.Id == obj.Id, u => new Category
Repository.Update(u => new Category
{
Name = obj.Name,
Enable = obj.Enable,
@@ -84,7 +86,7 @@ namespace OpenAuth.App
UpdateUserId = user.Id,
UpdateUserName = user.Name,
SortNo = obj.SortNo
});
},u => u.Id == obj.Id);
}
@@ -95,10 +97,10 @@ namespace OpenAuth.App
/// <returns></returns>
public List<Category> LoadByTypeId(string typeId)
{
return Repository.Find(u => u.TypeId == typeId).ToList();
return SugarClient.Queryable<Category>().Where(u => u.TypeId == typeId).ToList();
}
public CategoryApp(IUnitWork<OpenAuthDBContext> unitWork, IRepository<Category,OpenAuthDBContext> repository,IAuth auth) : base(unitWork, repository, auth)
public CategoryApp(ISqlSugarClient client, IAuth auth) : base(client, auth)
{
}
}

View File

@@ -2,20 +2,16 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using Infrastructure;
using Microsoft.EntityFrameworkCore;
using OpenAuth.App.Interface;
using OpenAuth.App.Request;
using OpenAuth.App.Response;
using OpenAuth.Repository;
using OpenAuth.Repository.Domain;
using OpenAuth.Repository.Interface;
using SqlSugar;
namespace OpenAuth.App
{
public class DataPrivilegeRuleApp : BaseStringApp<DataPrivilegeRule,OpenAuthDBContext>
public class DataPrivilegeRuleApp : SqlSugarBaseApp<DataPrivilegeRule>
{
private RevelanceManagerApp _revelanceApp;
/// <summary>
/// 加载列表
/// </summary>
@@ -34,36 +30,40 @@ namespace OpenAuth.App
}
var result = new TableData();
var objs = UnitWork.Find<DataPrivilegeRule>(null);
var objs = SugarClient.Queryable<DataPrivilegeRule>();
if (!string.IsNullOrEmpty(request.key))
{
objs = objs.Where(u => u.Id.Contains(request.key) || u.SourceCode.Contains(request.key) || u.Description.Contains(request.key));
}
if (!string.IsNullOrEmpty(request.sqlWhere))
{
objs = objs.Where(request.sqlWhere);
}
var propertyStr = string.Join(',', columnFields.Select(u => u.ColumnName));
result.columnFields = columnFields;
result.data = objs.OrderBy(u => u.Id)
.Skip((request.page - 1) * request.limit)
.Take(request.limit).Select($"new ({propertyStr})");
.Take(request.limit).Select($"{propertyStr}").ToList();
result.count = await objs.CountAsync();
return result;
}
public void Add(AddOrUpdateDataPriviReq req)
{
if (Repository.Any(u => u.SourceCode == req.SourceCode))
if (Repository.IsAny(u => u.SourceCode == req.SourceCode))
{
throw new Exception($"已经存在{req.SourceCode}的数据规则,如果想调整规制请直接修改");
}
var obj = req.MapTo<DataPrivilegeRule>();
obj.CreateUserId = _auth.GetCurrentUser().User.Id;
obj.CreateTime = DateTime.Now;
Repository.Add(obj);
SugarClient.Insertable(obj).ExecuteCommand();
}
public void Update(AddOrUpdateDataPriviReq obj)
{
UnitWork.Update<DataPrivilegeRule>(u => u.Id == obj.Id, u => new DataPrivilegeRule
Repository.Update(u => new DataPrivilegeRule
{
SortNo = obj.SortNo,
SourceCode = obj.SourceCode,
@@ -73,24 +73,22 @@ namespace OpenAuth.App
PrivilegeRules = obj.PrivilegeRules,
Enable = obj.Enable
//todo:要修改的字段赋值
});
},u => u.Id == obj.Id);
}
public DataPrivilegeRuleApp(IUnitWork<OpenAuthDBContext> unitWork, IRepository<DataPrivilegeRule,OpenAuthDBContext> repository,
RevelanceManagerApp app, IAuth auth) : base(unitWork, repository, auth)
public DataPrivilegeRuleApp(ISqlSugarClient client, IAuth auth) : base(client, auth)
{
_revelanceApp = app;
}
public DataPrivilegeRule GetByModuleName(string moduleName)
{
return Repository.FirstOrDefault(u=>u.SourceCode == moduleName);
return Repository.GetFirst(u=>u.SourceCode == moduleName);
}
public void Clear()
{
Repository.Delete(u =>true);
SugarClient.Deleteable<DataPrivilegeRule>(u => true).ExecuteCommand();
}
}
}

View File

@@ -85,6 +85,11 @@ namespace OpenAuth.App
/// <returns></returns>
public IList<SysTableColumn> GetDbTableStructure(string tableName)
{
if(string.IsNullOrEmpty(tableName))
{
return new List<SysTableColumn>();
}
//如果是空,说明没有通过ProcessExternalDb设置过直接从本地读取
if(string.IsNullOrEmpty(_dbTypeStr)){
_dbTypeStr = _appConfiguration.Value.DbTypes[_httpContextAccessor.GetTenantId()];
@@ -414,6 +419,22 @@ namespace OpenAuth.App
return new List<SysTableColumn>();
}
/// <summary>
/// 通过表名和外部数据源ID获取表结构
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="externalDataSourceId">外部数据源ID</param>
/// <returns></returns>
public IList<SysTableColumn> GetDbTableStructure(string tableName, string externalDataSourceId){
var connection = SugarClient.Queryable<ExternalDataSource>().First(u => u.Id == externalDataSourceId);
if (connection != null)
{
var dbType = connection.Dbtype;
SetConnection(connection.Connectionstring, dbType);
}
return GetDbTableStructure(tableName);
}
/// <summary>
/// 处理外部数据库
/// </summary>

View File

@@ -34,27 +34,156 @@ namespace OpenAuth.App.Flow
bool result = true;
foreach (var compare in Compares)
{
bool isDecimal = decimal.TryParse(compare.Value, out decimal value);
var fieldVal = frmDataJson.GetValue(compare.FieldName.ToLower()).ToString();
if (isDecimal) //如果是数字或小数
object objVal = frmDataJson.GetValue(compare.FieldName.ToLower());
string fieldVal = null;
List<string> fieldVals = new List<string>();
if(compare.FieldType == "checkbox")
{
decimal frmvalue = 0;
if (fieldVal.Contains("["))//表单中的数据包含[],为数组,一般是checkbox的值
if (objVal is JArray jArray)
{
var tempvals = JsonHelper.Instance.Deserialize<List<decimal>>(fieldVal);
frmvalue = tempvals.Max();
var tempvals = jArray.ToObject<List<string>>();
if (tempvals != null && tempvals.Any())
{
fieldVals.AddRange(tempvals);
}
}
}
else
{
fieldVal = objVal?.ToString();
}
// 判断是否为空值操作符
if (compare.Operation == DataCompare.IsNull || compare.Operation == DataCompare.IsNotNull)
{
var fieldExists = objVal != null;
var fieldEmpty = !fieldExists || string.IsNullOrWhiteSpace(objVal.ToString());
if (compare.Operation == DataCompare.IsNull)
{
result &= fieldEmpty;
}
else
{
frmvalue = decimal.Parse(fieldVal); //表单中填写的值
result &= !fieldEmpty;
}
continue;
}
// 确保字段存在且不为空
if (objVal == null)
{
result = false;
continue;
}
// checkbox类型特殊处理
if (compare.FieldType == "checkbox")
{
// 针对checkbox的IN和NOT IN操作
if (compare.Operation == DataCompare.In || compare.Operation == DataCompare.NotIn)
{
if (compare.ValueList == null || compare.ValueList.Length == 0)
{
result = false;
continue;
}
// 检查是否有交集
bool hasIntersection = fieldVals.Any(v => compare.ValueList.Contains(v));
result &= compare.Operation == DataCompare.In ? hasIntersection : !hasIntersection;
continue;
}
// 针对checkbox的相等和不等操作
if (compare.Operation == DataCompare.Equal)
{
result &= fieldVals.Contains(compare.Value);
continue;
}
else if (compare.Operation == DataCompare.NotEqual)
{
result &= !fieldVals.Contains(compare.Value);
continue;
}
// 其他操作不适用于checkbox默认为false
result = false;
continue;
}
// 以下是非checkbox类型的处理逻辑
// 范围操作符处理
if (compare.Operation == DataCompare.In || compare.Operation == DataCompare.NotIn)
{
if (compare.ValueList == null || compare.ValueList.Length == 0)
{
result = false;
continue;
}
bool inList = compare.ValueList.Contains(fieldVal);
result &= compare.Operation == DataCompare.In ? inList : !inList;
continue;
}
// Between操作符处理
if (compare.Operation == DataCompare.Between)
{
if (compare.ValueRange == null || compare.ValueRange.Length != 2)
{
result = false;
continue;
}
bool isDecimal = decimal.TryParse(fieldVal, out decimal fieldDecimal);
if (isDecimal)
{
decimal min = decimal.Parse(compare.ValueRange[0]);
decimal max = decimal.Parse(compare.ValueRange[1]);
result &= fieldDecimal >= min && fieldDecimal <= max;
}
else
{
string min = compare.ValueRange[0];
string max = compare.ValueRange[1];
result &= string.Compare(fieldVal, min, false) >= 0 && string.Compare(fieldVal, max, false) <= 0;
}
continue;
}
// 文本特殊操作符处理
if (compare.Operation == DataCompare.Like || compare.Operation == DataCompare.NotLike ||
compare.Operation == DataCompare.StartWith || compare.Operation == DataCompare.EndWith)
{
switch (compare.Operation)
{
case DataCompare.Like:
result &= fieldVal.Contains(compare.Value);
break;
case DataCompare.NotLike:
result &= !fieldVal.Contains(compare.Value);
break;
case DataCompare.StartWith:
result &= fieldVal.StartsWith(compare.Value);
break;
case DataCompare.EndWith:
result &= fieldVal.EndsWith(compare.Value);
break;
}
continue;
}
bool isDecimalValue = decimal.TryParse(compare.Value, out decimal value);
if (isDecimalValue) //如果是数字或小数
{
decimal frmvalue = decimal.Parse(fieldVal);
switch (compare.Operation)
{
case DataCompare.Equal:
result &= compare.Value == fieldVal;
result &= frmvalue == value;
break;
case DataCompare.Larger:
result &= frmvalue > value;
@@ -68,47 +197,35 @@ namespace OpenAuth.App.Flow
case DataCompare.LessEqual:
result &= frmvalue <= value;
break;
case DataCompare.NotEqual:
case DataCompare.NotEqual:
result &= frmvalue != value;
break;
}
}
else //如果只是字符串,只判断相等
else //如果只是字符串
{
if (fieldVal.Contains("["))//表单中的数据包含[],为数组,一般是checkbox的值
switch (compare.Operation)
{
var tempvals = JsonHelper.Instance.Deserialize<List<string>>(fieldVal);
result &= tempvals.Contains(compare.Value);
case DataCompare.Equal:
result &= compare.Value == fieldVal;
break;
case DataCompare.Larger:
result &= string.Compare(fieldVal, compare.Value, false) > 0;
break;
case DataCompare.Less:
result &= string.Compare(fieldVal, compare.Value, false) < 0;
break;
case DataCompare.LargerEqual:
result &= string.Compare(fieldVal, compare.Value, false) >= 0;
break;
case DataCompare.LessEqual:
result &= string.Compare(fieldVal, compare.Value, false) <= 0;
break;
case DataCompare.NotEqual:
result &= compare.Value != fieldVal;
break;
}
else
{
switch (compare.Operation)
{
case DataCompare.Equal:
result &= compare.Value == fieldVal;
break;
case DataCompare.Larger:
result &= string.Compare(compare.Value, fieldVal, false) > 0;
break;
case DataCompare.Less:
result &= string.Compare(compare.Value, fieldVal, false) < 0;
break;
case DataCompare.LargerEqual:
result &= string.Compare(compare.Value, fieldVal, false) >= 0;
break;
case DataCompare.LessEqual:
result &= string.Compare(compare.Value, fieldVal, false) <= 0;
break;
case DataCompare.NotEqual:
result &= compare.Value != fieldVal;
break;
}
}
}
}
return result;
@@ -120,6 +237,7 @@ namespace OpenAuth.App.Flow
/// </summary>
public class DataCompare
{
// 基本比较操作符
public const string Larger = ">";
public const string Less = "<";
public const string LargerEqual = ">=";
@@ -127,6 +245,21 @@ namespace OpenAuth.App.Flow
public const string NotEqual = "!=";
public const string Equal = "=";
// 文本操作符
public const string Like = "LIKE";
public const string NotLike = "NOT LIKE";
public const string StartWith = "START_WITH";
public const string EndWith = "END_WITH";
// 范围操作符
public const string In = "IN";
public const string NotIn = "NOT IN";
public const string Between = "BETWEEN";
// 空值操作符
public const string IsNull = "IS NULL";
public const string IsNotNull = "IS NOT NULL";
/// <summary>操作类型比如大于/等于/小于</summary>
public string Operation { get; set; }
@@ -138,5 +271,11 @@ namespace OpenAuth.App.Flow
/// <summary>比较的值</summary>
public string Value { get; set; }
/// <summary> 值范围BETWEEN </summary>
public string[] ValueRange { get; set; }
/// <summary> IN 和 NOT IN 操作符 </summary>
public string[] ValueList { get; set; }
}
}

View File

@@ -124,8 +124,14 @@ namespace OpenAuth.App.Flow
if (FrmData == "" || FrmData == "{}") return lines[0].to;
FrmData = FrmData.ToLower(); //统一转小写
var frmDataJson = FrmData.ToJObject(); //获取数据内容
// 将frmDataJson中的所有key转小写
var properties = frmDataJson.Properties().ToList();
foreach (var property in properties)
{
frmDataJson[property.Name.ToLower()] = property.Value;
frmDataJson.Remove(property.Name);
}
foreach (var l in lines)
{
@@ -375,7 +381,6 @@ namespace OpenAuth.App.Flow
flowInstance.ActivityName = Nodes[rejectNode].name;
flowInstance.MakerList =
GetNodeMarkers(Nodes[rejectNode], flowInstance.CreateUserId);
SaveTransitionHis();
}
flowInstance.SchemeContent = JsonHelper.Instance.Serialize(ToSchemeObj());
@@ -405,8 +410,6 @@ namespace OpenAuth.App.Flow
flowInstance.ActivityName = Nodes[startNodeId].name;
flowInstance.MakerList = GetNodeMarkers(Nodes[startNodeId], flowInstance.CreateUserId);
SaveTransitionHis();
var sugarClient = AutofacContainerModule.GetService<ISqlSugarClient>();
sugarClient.Updateable(flowInstance).ExecuteCommand();
@@ -478,32 +481,6 @@ namespace OpenAuth.App.Flow
SaveOperationHis(user.Id, user.Name, opHis);
}
/// <summary>
/// 保存本次扭转记录
/// </summary>
/// <returns></returns>
public void SaveTransitionHis()
{
var user = AutofacContainerModule.GetService<IAuth>().GetCurrentUser().User;
var SugarClient = AutofacContainerModule.GetService<ISqlSugarClient>();
var transitionHistory = new FlowInstanceTransitionHistory
{
InstanceId = flowInstanceId,
CreateUserId = user.Id,
CreateUserName = user.Name,
FromNodeId = currentNodeId,
FromNodeName = currentNode.name,
FromNodeType = GetCurrentNodeType(),
ToNodeId = nextNodeId,
ToNodeName = nextNode?.name,
ToNodeType = GetNextNodeType(),
IsFinish = IsFinish() ? FlowInstanceStatus.Finished : FlowInstanceStatus.Running,
TransitionSate = 0
};
SugarClient.Insertable(transitionHistory).ExecuteCommand();
}
/// <summary>
/// 通知三方系统,节点执行情况
/// </summary>
@@ -580,12 +557,10 @@ namespace OpenAuth.App.Flow
currentNode.setInfo.Description = "";
currentNode.setInfo.TagedTime = "";
//删除当前节点的扭转记录
//删除当前节点的审批记录(只删除最新的一条)
var user = AutofacContainerModule.GetService<IAuth>().GetCurrentUser().User;
var SugarClient = AutofacContainerModule.GetService<ISqlSugarClient>();
SugarClient.Deleteable<FlowInstanceTransitionHistory>().Where(u => u.InstanceId == flowInstanceId && u.CreateUserId == user.Id && u.FromNodeId == currentNodeId).ExecuteCommand();
//删除当前节点的审批记录(只删除最新的一条)
var latestRecord = SugarClient.Queryable<FlowInstanceOperationHistory>()
.Where(u => u.InstanceId == flowInstanceId && u.CreateUserId == user.Id)
.OrderByDescending(u => u.CreateDate)

View File

@@ -219,7 +219,6 @@ namespace OpenAuth.App
wfruntime.SaveOperationHis($"【创建】{user.User.Name}创建了流程实例【{addFlowInstanceReq.CustomName}】");
wfruntime.SaveTransitionHis();
SugarClient.Ado.CommitTran();
return flowInstance.Id;
}
@@ -408,14 +407,11 @@ namespace OpenAuth.App
: FlowInstanceStatus.Running;
flowInstance.MakerList =
wfruntime.IsFinish() ? "" : wfruntime.GetNextMakers();
wfruntime.SaveTransitionHis();
}
else
{
//网关过程中,需要更新用户
flowInstance.MakerList = wfruntime.GetForkNodeMakers(wfruntime.currentNodeId);
wfruntime.SaveTransitionHis();
}
flowInstance.SchemeContent = JsonHelper.Instance.Serialize(wfruntime.ToSchemeObj());
@@ -898,7 +894,6 @@ namespace OpenAuth.App
#endregion
wfruntime.SaveOperationHis($"【启动】由用户{user.User.Name}启动");
wfruntime.SaveTransitionHis();
SugarClient.Ado.CommitTran();
}

View File

@@ -8,21 +8,18 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using OpenAuth.App.Extensions;
using OpenAuth.App.Interface;
using OpenAuth.App.Jobs;
using OpenAuth.App.Request;
using OpenAuth.App.Response;
using OpenAuth.Repository;
using OpenAuth.Repository.Domain;
using OpenAuth.Repository.Interface;
using Quartz;
using SqlSugar;
namespace OpenAuth.App
{
/// <summary>
/// 系统定时任务管理
/// </summary>
public class OpenJobApp : BaseStringApp<OpenJob, OpenAuthDBContext>
public class OpenJobApp : SqlSugarBaseApp<OpenJob>
{
private SysLogApp _sysLogApp;
private IScheduler _scheduler;
@@ -34,7 +31,7 @@ namespace OpenAuth.App
public async Task<TableData> Load(QueryOpenJobListReq request)
{
var result = new TableData();
var objs = Repository.Find(null);
var objs = SugarClient.Queryable<OpenJob>();
if (!string.IsNullOrEmpty(request.key))
{
objs = objs.Where(u => u.Id.Contains(request.key));
@@ -54,7 +51,7 @@ namespace OpenAuth.App
/// <returns></returns>
public async Task StartAll()
{
var jobs = await Repository.Find(u => u.Status == (int) JobStatus.Running).ToListAsync();
var jobs = await SugarClient.Queryable<OpenJob>().Where(u => u.Status == (int) JobStatus.Running).ToListAsync();
foreach (var job in jobs)
{
job.Start(_scheduler);
@@ -69,13 +66,13 @@ namespace OpenAuth.App
var user = _auth.GetCurrentUser().User;
obj.CreateUserId = user.Id;
obj.CreateUserName = user.Name;
Repository.Add(obj);
SugarClient.Insertable(obj).ExecuteCommand();
}
public void Update(AddOrUpdateOpenJobReq obj)
{
var user = _auth.GetCurrentUser().User;
UnitWork.Update<OpenJob>(u => u.Id == obj.Id, u => new OpenJob
Repository.Update(u => new OpenJob
{
JobName = obj.JobName,
JobType = obj.JobType,
@@ -87,7 +84,7 @@ namespace OpenAuth.App
UpdateTime = DateTime.Now,
UpdateUserId = user.Id,
UpdateUserName = user.Name
});
},u => u.Id == obj.Id);
}
#region
@@ -109,7 +106,7 @@ namespace OpenAuth.App
public void ChangeJobStatus(ChangeJobStatusReq req)
{
var job = Repository.FirstOrDefault(u => u.Id == req.Id);
var job = Repository.GetFirst(u => u.Id == req.Id);
if (job == null)
{
throw new Exception("任务不存在");
@@ -140,7 +137,7 @@ namespace OpenAuth.App
/// <param name="jobId"></param>
public void RecordRun(string jobId)
{
var job = Repository.FirstOrDefault(u => u.Id == jobId);
var job = Repository.GetFirst(u => u.Id == jobId);
if (job == null)
{
_sysLogApp.Add(new SysLog
@@ -170,9 +167,7 @@ namespace OpenAuth.App
#endregion
public OpenJobApp(IUnitWork<OpenAuthDBContext> unitWork, IRepository<OpenJob, OpenAuthDBContext> repository,
IAuth auth, SysLogApp sysLogApp, IScheduler scheduler, ILogger<OpenJobApp> logger) : base(unitWork,
repository, auth)
public OpenJobApp(ISqlSugarClient client, IAuth auth, SysLogApp sysLogApp, IScheduler scheduler, ILogger<OpenJobApp> logger) : base(client, auth)
{
_sysLogApp = sysLogApp;
_scheduler = scheduler;

View File

@@ -96,6 +96,11 @@ namespace OpenAuth.App
resources = resources.Where(u => u.AppId == request.appId);
}
if (!string.IsNullOrEmpty(request.sqlWhere))
{
resources = resources.Where(request.sqlWhere);
}
var columnnames = columnFields.Select(u => u.ColumnName);
var propertyStr = string.Join(',', columnnames);

View File

@@ -39,6 +39,11 @@ namespace OpenAuth.App
objs = objs.Where(u => u.Name.Contains(request.key));
}
if (!string.IsNullOrEmpty(request.sqlWhere))
{
objs = objs.Where(request.sqlWhere);
}
var columnnames = columnFields.Select(u => u.ColumnName);
var propertyStr = string.Join(',', columnnames);

View File

@@ -0,0 +1,131 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Infrastructure;
using Infrastructure.Cache;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using NUnit.Framework;
using OpenAuth.App.Request;
using OpenAuth.App.SSO;
using System.Collections.Generic;
using Yitter.IdGenerator;
namespace OpenAuth.App.Test
{
public class TestWmsInbound : TestBase
{
public override ServiceCollection GetService()
{
var services = new ServiceCollection();
var cachemock = new Mock<ICacheContext>();
cachemock.Setup(x => x.Get<UserAuthSession>("tokentest"))
.Returns(new UserAuthSession { Account = Define.SYSTEM_USERNAME });
services.AddScoped(x => cachemock.Object);
var httpContextAccessorMock = new Mock<IHttpContextAccessor>();
httpContextAccessorMock.Setup(x => x.HttpContext.Request.Query[Define.TOKEN_NAME])
.Returns("tokentest");
services.AddScoped(x => httpContextAccessorMock.Object);
return services;
}
[Test]
public async Task Add()
{
var app = _autofacServiceProvider.GetService<WmsInboundOrderTblApp>();
var options = new IdGeneratorOptions()
{
Method = 1,
WorkerId = 1
};
YitIdHelper.SetIdGenerator(options);
//添加
var addReq = new AddOrUpdateWmsInboundOrderTblReq()
{
Id = "673870840217669",
ExternalNo = "123456",
ExternalType = "1",
Status = 1,
OrderType = "1",
StockId = "123456",
OwnerId = "123456",
Remark = "123456",
WmsInboundOrderDtblReqs = new List<AddOrUpdateWmsInboundOrderDtblReq>()
{
new()
{
GoodsId = "123456",
GoodsBatch = "123456",
QualityFlg = "1",
OrderNum = 1,
InNum = 1,
LeaveNum = 1,
HoldNum = 1,
OwnerId = "123456",
}
}
};
app.Add(addReq);
//异步测试,延长主线程,防止程序退出
Thread.Sleep(1000);
}
[Test]
public async Task Update()
{
var app = _autofacServiceProvider.GetService<WmsInboundOrderTblApp>();
var updateReq = new AddOrUpdateWmsInboundOrderTblReq()
{
Id = "673870840217669",
ExternalNo = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
ExternalType = "1",
Status = 1,
OrderType = "1",
StockId = "123456",
OwnerId = "123456",
Remark = "123456",
WmsInboundOrderDtblReqs = new List<AddOrUpdateWmsInboundOrderDtblReq>()
{
new()
{ //这个用来测试更新
Id = "52c69eb9-e28c-47d9-8888-3e5c1090716b",
OrderId = "673870840217669",
GoodsId = "123456",
OwnerId = "123456",
GoodsBatch = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
QualityFlg = "1",
OrderNum = 1,
InNum = 1,
LeaveNum = 1,
HoldNum = 1
},
new()
{ //这个用来测试添加
OrderId = "673870840217669",
GoodsId = "123456",
OwnerId = "123456",
GoodsBatch = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
QualityFlg = "1",
OrderNum = 1,
InNum = 1,
LeaveNum = 1,
HoldNum = 1
}
}
};
app.Update(updateReq);
//异步测试,延长主线程,防止程序退出
Thread.Sleep(1000);
}
}
}

View File

@@ -6,9 +6,7 @@ using OpenAuth.App.Interface;
using OpenAuth.App.Request;
using OpenAuth.App.Response;
using OpenAuth.Repository.Domain;
using SqlSugar;
namespace OpenAuth.App
{
public class WmsInboundOrderDtblApp : SqlSugarBaseApp<WmsInboundOrderDtbl>
@@ -18,13 +16,11 @@ namespace OpenAuth.App
/// </summary>
public async Task<TableData> Load(QueryWmsInboundOrderDtblListReq request)
{
var loginContext = _auth.GetCurrentUser();
if (loginContext == null)
{
throw new CommonException("登录已过期", Define.INVALID_TOKEN);
}
var properties = loginContext.GetTableColumns("WmsInboundOrderDtbl");
if (properties == null || properties.Count == 0)
{
@@ -36,12 +32,10 @@ namespace OpenAuth.App
{
objs = objs.Where(u => u.OrderId == request.InboundOrderId);
}
if (!string.IsNullOrEmpty(request.key))
{
objs = objs.Where(u => u.GoodsId.Contains(request.key));
}
var propertyStr = string.Join(',', properties.Select(u => u.ColumnName));
result.columnFields = properties;
result.data = objs.OrderBy(u => u.Id)
@@ -50,17 +44,9 @@ namespace OpenAuth.App
result.count = await objs.CountAsync();
return result;
}
public void Add(AddOrUpdateWmsInboundOrderDtblReq req)
public void Add(AddOrUpdateWmsInboundOrderDtblReq request)
{
SugarClient.Ado.BeginTran();
AddNoSave(req);
SugarClient.Ado.CommitTran();
}
public void AddNoSave(AddOrUpdateWmsInboundOrderDtblReq req)
{
var obj = req.MapTo<WmsInboundOrderDtbl>();
var obj = request.MapTo<WmsInboundOrderDtbl>();
//todo:补充或调整自己需要的字段
obj.CreateTime = DateTime.Now;
var user = _auth.GetCurrentUser().User;
@@ -69,36 +55,33 @@ namespace OpenAuth.App
obj.CreateUserName = user.Name;
SugarClient.Insertable(obj).ExecuteCommand();
}
public void Update(AddOrUpdateWmsInboundOrderDtblReq obj)
public void Update(AddOrUpdateWmsInboundOrderDtblReq request)
{
var user = _auth.GetCurrentUser().User;
Repository.Update(u => new WmsInboundOrderDtbl
{
Price = obj.Price,
PriceNoTax = obj.PriceNoTax,
InStockStatus = obj.InStockStatus,
AsnStatus = obj.AsnStatus,
GoodsId = obj.GoodsId,
GoodsBatch = obj.GoodsBatch,
QualityFlg = obj.QualityFlg,
OrderNum = obj.OrderNum,
InNum = obj.InNum,
LeaveNum = obj.LeaveNum,
HoldNum = obj.HoldNum,
ProdDate = obj.ProdDate,
ExpireDate = obj.ExpireDate,
TaxRate = obj.TaxRate,
OwnerId = obj.OwnerId,
Remark = obj.Remark,
Price = request.Price,
PriceNoTax = request.PriceNoTax,
InStockStatus = request.InStockStatus,
AsnStatus = request.AsnStatus,
GoodsId = request.GoodsId,
GoodsBatch = request.GoodsBatch,
QualityFlg = request.QualityFlg,
OrderNum = request.OrderNum,
InNum = request.InNum,
LeaveNum = request.LeaveNum,
HoldNum = request.HoldNum,
ProdDate = request.ProdDate,
ExpireDate = request.ExpireDate,
TaxRate = request.TaxRate,
OwnerId = request.OwnerId,
Remark = request.Remark,
UpdateTime = DateTime.Now,
UpdateUserId = user.Id,
UpdateUserName = user.Name
//todo:补充或调整自己需要的字段
}, u => u.Id == obj.Id);
}, u => u.Id == request.Id);
}
public WmsInboundOrderDtblApp(ISqlSugarClient client, IAuth auth) : base(client, auth)
{
}

View File

@@ -7,14 +7,11 @@ using OpenAuth.App.Request;
using OpenAuth.App.Response;
using OpenAuth.Repository.Domain;
using SqlSugar;
namespace OpenAuth.App
{
public class WmsInboundOrderTblApp : SqlSugarBaseApp<WmsInboundOrderTbl>
{
private WmsInboundOrderDtblApp _wmsInboundOrderDtblApp;
/// <summary>
/// 加载列表
/// </summary>
@@ -25,29 +22,22 @@ namespace OpenAuth.App
{
throw new CommonException("登录已过期", Define.INVALID_TOKEN);
}
var columns = loginContext.GetTableColumns("WmsInboundOrderTbl");
if (columns == null || columns.Count == 0)
{
throw new Exception("请在代码生成界面配置WmsInboundOrderTbl表的字段属性");
}
var result = new TableData();
result.columnFields = columns;
var objs = GetDataPrivilege("u");
if (!string.IsNullOrEmpty(request.key))
{
objs = objs.Where(u => u.Id.Contains(request.key));
}
if (!string.IsNullOrEmpty(request.sqlWhere))
{
objs = objs.Where(request.sqlWhere);
}
var propertyStr = string.Join(',', columns.Select(u => u.ColumnName));
result.data = objs.OrderBy(u => u.Id)
.Skip((request.page - 1) * request.limit)
@@ -55,88 +45,81 @@ namespace OpenAuth.App
result.count = await objs.CountAsync();
return result;
}
public void Add(AddOrUpdateWmsInboundOrderTblReq req)
public void Add(AddOrUpdateWmsInboundOrderTblReq request)
{
var obj = req.MapTo<WmsInboundOrderTbl>();
var obj = request.MapTo<WmsInboundOrderTbl>();
//todo:补充或调整自己需要的字段
obj.CreateTime = DateTime.Now;
var user = _auth.GetCurrentUser().User;
obj.CreateUserId = user.Id;
obj.CreateUserName = user.Name;
SugarClient.Ado.BeginTran();
SugarClient.Insertable(obj).ExecuteCommand();
if (req.WmsInboundOrderDtblReqs != null && req.WmsInboundOrderDtblReqs.Any())
if(obj.KeyIsNull()) //如果主键为空,则生成默认值
{
foreach (var detail in req.WmsInboundOrderDtblReqs)
{
detail.OrderId = obj.Id;
_wmsInboundOrderDtblApp.AddNoSave(detail);
}
obj.GenerateDefaultKeyVal();
}
SugarClient.Ado.CommitTran();
}
public void Update(AddOrUpdateWmsInboundOrderTblReq obj)
{
var user = _auth.GetCurrentUser().User;
SugarClient.Ado.BeginTran();
if (obj.WmsInboundOrderDtblReqs != null && obj.WmsInboundOrderDtblReqs.Any())
SugarClient.Insertable(obj).ExecuteCommand();
if (request.WmsInboundOrderDtblReqs != null && request.WmsInboundOrderDtblReqs.Any())
{
//id为空的添加
foreach (var detail in obj.WmsInboundOrderDtblReqs.Where(u => string.IsNullOrEmpty(u.Id)))
foreach (var detail in request.WmsInboundOrderDtblReqs)
{
detail.OrderId = obj.Id;
_wmsInboundOrderDtblApp.AddNoSave(detail);
_wmsInboundOrderDtblApp.Add(detail);
}
//id比数据库少的删除
var containids = obj.WmsInboundOrderDtblReqs.Select(u => u.Id)
}
SugarClient.Ado.CommitTran();
}
public void Update(AddOrUpdateWmsInboundOrderTblReq request)
{
var user = _auth.GetCurrentUser().User;
SugarClient.Ado.BeginTran();
if (request.WmsInboundOrderDtblReqs != null && request.WmsInboundOrderDtblReqs.Any())
{
//请求的id不在数据库的记录需要删除
var containids = request.WmsInboundOrderDtblReqs.Select(u => u.Id)
.Where(u => !string.IsNullOrEmpty(u)).ToList();
if (containids.Any())
{
SugarClient.Deleteable<WmsInboundOrderDtbl>(u => (!containids.Contains(u.Id)) && u.OrderId == obj.Id).ExecuteCommand();
SugarClient.Deleteable<WmsInboundOrderDtbl>(u => (!containids.Contains(u.Id)) && u.OrderId == request.Id).ExecuteCommand();
}
//id为空的添加
foreach (var detail in request.WmsInboundOrderDtblReqs.Where(u => string.IsNullOrEmpty(u.Id)))
{
detail.OrderId = request.Id;
_wmsInboundOrderDtblApp.Add(detail);
}
//更新id相同的
foreach (var detail in obj.WmsInboundOrderDtblReqs.Where(u => !string.IsNullOrEmpty(u.Id)))
foreach (var detail in request.WmsInboundOrderDtblReqs.Where(u => !string.IsNullOrEmpty(u.Id)))
{
_wmsInboundOrderDtblApp.Update(detail);
}
}
Repository.Update(u => new WmsInboundOrderTbl
{
ExternalNo = obj.ExternalNo,
ExternalType = obj.ExternalType,
Status = obj.Status,
OrderType = obj.OrderType,
GoodsType = obj.GoodsType,
PurchaseNo = obj.PurchaseNo,
StockId = obj.StockId,
OwnerId = obj.OwnerId,
ShipperId = obj.ShipperId,
SupplierId = obj.SupplierId,
ScheduledInboundTime = obj.ScheduledInboundTime,
Remark = obj.Remark,
Enable = obj.Enable,
TransferType = obj.TransferType,
InBondedArea = obj.InBondedArea,
ReturnBoxNum = obj.ReturnBoxNum,
ExternalNo = request.ExternalNo,
ExternalType = request.ExternalType,
Status = request.Status,
OrderType = request.OrderType,
GoodsType = request.GoodsType,
PurchaseNo = request.PurchaseNo,
StockId = request.StockId,
OwnerId = request.OwnerId,
ShipperId = request.ShipperId,
SupplierId = request.SupplierId,
ScheduledInboundTime = request.ScheduledInboundTime,
Remark = request.Remark,
Enable = request.Enable,
TransferType = request.TransferType,
InBondedArea = request.InBondedArea,
ReturnBoxNum = request.ReturnBoxNum,
UpdateTime = DateTime.Now,
UpdateUserId = user.Id,
UpdateUserName = user.Name
//todo:补充或调整自己需要的字段
}, u => u.Id == obj.Id);
}, u => u.Id == request.Id);
SugarClient.Ado.CommitTran();
}
public WmsInboundOrderTblApp(ISqlSugarClient client, IAuth auth,
WmsInboundOrderDtblApp wmsInboundOrderDtblApp) : base(client, auth)
{

View File

@@ -1,101 +0,0 @@
//------------------------------------------------------------------------------
// <autogenerated>
// This code was generated by a CodeSmith Template.
//
// DO NOT MODIFY contents of this file. Changes to this
// file will be lost if the code is regenerated.
// Author:Yubao Li
// </autogenerated>
//------------------------------------------------------------------------------
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using Infrastructure.Const;
using OpenAuth.Repository.Core;
namespace OpenAuth.Repository.Domain
{
/// <summary>
/// 流程实例流转记录
/// <para>记录某个流程实例所有已审批的从一个活动节点到下一个活动节点的操作人、操作时间</para>
/// </summary>
[Table("FlowInstanceTransitionHistory")]
public partial class FlowInstanceTransitionHistory : StringEntity
{
public FlowInstanceTransitionHistory()
{
this.InstanceId = string.Empty;
this.FromNodeId = string.Empty;
this.FromNodeName = string.Empty;
this.ToNodeId = string.Empty;
this.ToNodeName = string.Empty;
this.TransitionSate = 0;
this.IsFinish = FlowInstanceStatus.Running;
this.CreateDate = DateTime.Now;
this.CreateUserId = string.Empty;
this.CreateUserName = string.Empty;
}
/// <summary>
/// 实例Id
/// </summary>
[Description("实例Id")]
public string InstanceId { get; set; }
/// <summary>
/// 开始节点Id
/// </summary>
[Description("开始节点Id")]
public string FromNodeId { get; set; }
/// <summary>
/// 开始节点类型
/// </summary>
[Description("开始节点类型")]
public string FromNodeType { get; set; }
/// <summary>
/// 开始节点名称
/// </summary>
[Description("开始节点名称")]
public string FromNodeName { get; set; }
/// <summary>
/// 结束节点Id
/// </summary>
[Description("结束节点Id")]
public string ToNodeId { get; set; }
/// <summary>
/// 结束节点类型
/// </summary>
[Description("结束节点类型")]
public string ToNodeType { get; set; }
/// <summary>
/// 结束节点名称
/// </summary>
[Description("结束节点名称")]
public string ToNodeName { get; set; }
/// <summary>
/// 转化状态
/// </summary>
[Description("转化状态")]
public int TransitionSate { get; set; }
/// <summary>
/// 是否结束
/// </summary>
[Description("是否结束")]
public int IsFinish { get; set; }
/// <summary>
/// 转化时间
/// </summary>
[Description("转化时间")]
public System.DateTime CreateDate { get; set; }
/// <summary>
/// 操作人Id
/// </summary>
[Description("操作人Id")]
public string CreateUserId { get; set; }
/// <summary>
/// 操作人名称
/// </summary>
[Description("操作人名称")]
public string CreateUserName { get; set; }
}
}

View File

@@ -143,7 +143,6 @@ namespace OpenAuth.Repository
public virtual DbSet<CategoryType> CategoryTypes { get; set; }
public virtual DbSet<FlowInstance> FlowInstances { get; set; }
public virtual DbSet<FlowInstanceOperationHistory> FlowInstanceOperationHistorys { get; set; }
public virtual DbSet<FlowInstanceTransitionHistory> FlowInstanceTransitionHistorys { get; set; }
public virtual DbSet<FlowScheme> FlowSchemes { get; set; }
public virtual DbSet<Form> Forms { get; set; }
public virtual DbSet<Module> Modules { get; set; }

View File

@@ -68,7 +68,7 @@ namespace OpenAuth.WebApi.Controllers
/// <param name="obj"></param>
/// <returns></returns>
[HttpPost]
public Response Update(AddOrUpdateOpenJobReq obj)
public Response Update([FromBody]AddOrUpdateOpenJobReq obj)
{
var result = new Response();
try

View File

@@ -1,8 +1,13 @@
using Infrastructure;
using System;
using System.Collections.Generic;
using System.Linq;
using Infrastructure;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using OpenAuth.App;
using OpenAuth.App.Response;
using OpenAuth.Repository.QueryObj;
namespace OpenAuth.WebApi.Controllers
{
@@ -12,13 +17,15 @@ namespace OpenAuth.WebApi.Controllers
[Route("api/[controller]/[action]")]
[ApiController]
[ApiExplorerSettings(GroupName = "系统配置_SysConf")]
public class SysConfController :ControllerBase
public class SysConfController : ControllerBase
{
private IOptions<AppSetting> _appConfiguration;
private DbExtension _dbExtension;
public SysConfController(IOptions<AppSetting> appConfiguration)
public SysConfController(IOptions<AppSetting> appConfiguration, DbExtension dbExtension)
{
_appConfiguration = appConfiguration;
_dbExtension = dbExtension;
}
/// <summary>
@@ -34,5 +41,53 @@ namespace OpenAuth.WebApi.Controllers
Result = _appConfiguration.Value.IsIdentityAuth
};
}
/// <summary>
/// 获取数据库表结构
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="externalDataSourceId">外部数据源ID</param>
/// <returns></returns>
[HttpGet]
public Response<List<SysTableColumn>> GetDbTableStructure(string tableName, string externalDataSourceId)
{
var result = new Response<List<SysTableColumn>>();
try
{
result.Result = _dbExtension.GetDbTableStructure(tableName, externalDataSourceId).ToList();
}
catch (Exception ex)
{
result.Code = 500;
result.Message = ex.Message;
}
return result;
}
/// <summary>
/// 获取数据库表结构(用于下拉框)
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="externalDataSourceId">外部数据源ID</param>
/// <returns></returns>
[HttpGet]
public TableData GetDbTableStructureForSelect(string tableName, string externalDataSourceId)
{
var result = new TableData();
try
{
result.data = _dbExtension.GetDbTableStructure(tableName, externalDataSourceId).Select(u => new
{
Id = u.ColumnName,
Name = u.ColumnName
});
}
catch (Exception ex)
{
result.code = 500;
result.msg = ex.Message;
}
return result;
}
}
}

View File

@@ -6,6 +6,7 @@
// Author:Yubao Li
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using OpenAuth.Repository.Core;

View File

@@ -0,0 +1,104 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Infrastructure;
using OpenAuth.App.Interface;
using OpenAuth.App.Request;
using OpenAuth.App.Response;
using OpenAuth.Repository.Domain;
using SqlSugar;
namespace OpenAuth.App
{
public class {ModuleCode} : SqlSugarBaseApp<{ClassName}>
{
private {SubModuleCode} _{SubClassName}App;
/// <summary>
/// 加载列表
/// </summary>
public async Task<TableData> Load(Query{ClassName}ListReq request)
{
var loginContext = _auth.GetCurrentUser();
if (loginContext == null)
{
throw new CommonException("登录已过期", Define.INVALID_TOKEN);
}
var result = new TableData();
var objs = GetDataPrivilege("u");
if (!string.IsNullOrEmpty(request.key))
{
objs = objs.Where(u => u.Id.Contains(request.key));
}
if (!string.IsNullOrEmpty(request.sqlWhere))
{
objs = objs.Where(request.sqlWhere);
}
result.data = objs.OrderBy(u => u.Id)
.Skip((request.page - 1) * request.limit)
.Take(request.limit).ToList();
result.count = await objs.CountAsync();
return result;
}
public void Add(AddOrUpdate{ClassName}Req request)
{
var obj = request.MapTo<{ClassName}>();
var user = _auth.GetCurrentUser().User;
//todo:补充或调整自己需要的字段
{InsertColumns}
if(obj.KeyIsNull()) //如果主键为空,则生成默认值
{
obj.GenerateDefaultKeyVal();
}
SugarClient.Ado.BeginTran();
SugarClient.Insertable(obj).ExecuteCommand();
if (request.{SubClassName}Reqs != null && request.{SubClassName}Reqs.Any())
{
foreach (var detail in request.{SubClassName}Reqs)
{
detail.{SubForeignKey} = obj.Id;
_{SubClassName}App.Add(detail);
}
}
SugarClient.Ado.CommitTran();
}
public void Update(AddOrUpdate{ClassName}Req request)
{
var user = _auth.GetCurrentUser().User;
SugarClient.Ado.BeginTran();
if (request.{SubClassName}Reqs != null && request.{SubClassName}Reqs.Any())
{
//请求的id不在数据库的记录需要删除
var containids = request.{SubClassName}Reqs.Select(u => u.Id)
.Where(u => !string.IsNullOrEmpty(u)).ToList();
if (containids.Any())
{
SugarClient.Deleteable<{SubClassName}>(u => (!containids.Contains(u.Id)) && u.{SubForeignKey} == request.Id).ExecuteCommand();
}
//id为空的添加
foreach (var detail in request.{SubClassName}Reqs.Where(u => string.IsNullOrEmpty(u.Id)))
{
detail.{SubForeignKey} = request.Id;
_{SubClassName}App.Add(detail);
}
//更新id相同的
foreach (var detail in request.{SubClassName}Reqs.Where(u => !string.IsNullOrEmpty(u.Id)))
{
_{SubClassName}App.Update(detail);
}
}
Repository.Update(u => new {ClassName}
{
{UpdateColumns}
//todo:补充或调整自己需要的字段
}, u => u.Id == request.Id);
SugarClient.Ado.CommitTran();
}
public {ModuleCode}(ISqlSugarClient client, IAuth auth,
{SubModuleCode} {SubClassName}App) : base(client, auth)
{
_{SubClassName}App = {SubClassName}App;
}
}
}

View File

@@ -0,0 +1,109 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Infrastructure;
using OpenAuth.App.Interface;
using OpenAuth.App.Request;
using OpenAuth.App.Response;
using OpenAuth.Repository.Domain;
using SqlSugar;
namespace OpenAuth.App
{
public class {ModuleCode} : SqlSugarBaseApp<{ClassName}>
{
private {SubModuleCode} _{SubClassName}App;
/// <summary>
/// 加载列表
/// </summary>
public async Task<TableData> Load(Query{ClassName}ListReq request)
{
var loginContext = _auth.GetCurrentUser();
if (loginContext == null)
{
throw new CommonException("登录已过期", Define.INVALID_TOKEN);
}
var columns = loginContext.GetTableColumns("{ClassName}");
if (columns == null || columns.Count == 0)
{
throw new Exception("请在代码生成界面配置{ClassName}表的字段属性");
}
var result = new TableData();
result.columnFields = columns;
var objs = GetDataPrivilege("u");
if (!string.IsNullOrEmpty(request.key))
{
objs = objs.Where(u => u.Id.Contains(request.key));
}
if (!string.IsNullOrEmpty(request.sqlWhere))
{
objs = objs.Where(request.sqlWhere);
}
var propertyStr = string.Join(',', columns.Select(u => u.ColumnName));
result.data = objs.OrderBy(u => u.Id)
.Skip((request.page - 1) * request.limit)
.Take(request.limit).Select($"{propertyStr}").ToList();
result.count = await objs.CountAsync();
return result;
}
public void Add(AddOrUpdate{ClassName}Req request)
{
var obj = request.MapTo<{ClassName}>();
var user = _auth.GetCurrentUser().User;
//todo:补充或调整自己需要的字段
{InsertColumns}
if(obj.KeyIsNull()) //如果主键为空,则生成默认值
{
obj.GenerateDefaultKeyVal();
}
SugarClient.Ado.BeginTran();
SugarClient.Insertable(obj).ExecuteCommand();
if (request.{SubClassName}Reqs != null && request.{SubClassName}Reqs.Any())
{
foreach (var detail in request.{SubClassName}Reqs)
{
detail.{SubForeignKey} = obj.Id;
_{SubClassName}App.Add(detail);
}
}
SugarClient.Ado.CommitTran();
}
public void Update(AddOrUpdate{ClassName}Req request)
{
var user = _auth.GetCurrentUser().User;
SugarClient.Ado.BeginTran();
if (request.{SubClassName}Reqs != null && request.{SubClassName}Reqs.Any())
{
//请求的id不在数据库的记录需要删除
var containids = request.{SubClassName}Reqs.Select(u => u.Id)
.Where(u => !string.IsNullOrEmpty(u)).ToList();
if (containids.Any())
{
SugarClient.Deleteable<{SubClassName}>(u => (!containids.Contains(u.Id)) && u.{SubForeignKey} == request.Id).ExecuteCommand();
}
//id为空的添加
foreach (var detail in request.{SubClassName}Reqs.Where(u => string.IsNullOrEmpty(u.Id)))
{
detail.{SubForeignKey} = request.Id;
_{SubClassName}App.Add(detail);
}
//更新id相同的
foreach (var detail in request.{SubClassName}Reqs.Where(u => !string.IsNullOrEmpty(u.Id)))
{
_{SubClassName}App.Update(detail);
}
}
Repository.Update(u => new {ClassName}
{
{UpdateColumns}
//todo:补充或调整自己需要的字段
}, u => u.Id == request.Id);
SugarClient.Ado.CommitTran();
}
public {ModuleCode}(ISqlSugarClient client, IAuth auth,
{SubModuleCode} {SubClassName}App) : base(client, auth)
{
_{SubClassName}App = {SubClassName}App;
}
}
}

View File

@@ -1,395 +1,489 @@
<!--
* @Author: yubaolee <yubaolee@163.com> | ahfu~ <954478625@qq.com>
* @Description: 代码生成界面,采用的是固定头部结构
* @
* @Copyright (c) 2024 by yubaolee | ahfu~ , All Rights Reserved.
* @Description: 代码生成器,多表模版,动态头部
* @Copyright (c) 2025 by yubaolee | ahfu~ , All Rights Reserved.
-->
<template>
<div class="flex-column flex">
<div class="flex flex-column">
<sticky>
<div class="search-box">
<el-input @keyup.enter="handleFilter" size="small" class="w-200 custom-input" :placeholder="'名称'"
v-model="firstQuery.key"></el-input>
<el-button class="filter-item custom-button" icon="el-icon-search" size="small" @click="handleFilter">
搜索
</el-button>
<div class="flex items-center">
<query-builder storageName="{FirstTableName}s_query_conditions" :columns="firstHeaderList"
:query-options="firstQuery" @search="handleFilter" class="flex-1" />
<column-setting storageName="{FirstTableName}s_column_settings" :columns="firstHeaderList"
@update:columns="handleColumnsUpdate" class="ml-2" />
</div>
</div>
<permission-btn v-on:btn-event="onBtnClicked"></permission-btn>
</sticky>
<div class="flex-item flex flex-column main-context">
<el-card shadow="never" class="custom-card flex-item flex flex-column">
<auth-table size="small" ref="mainTable" :select-type="'radio'" :table-fields="firstHeaderList" :data="mainList"
:v-loading="listLoading" @row-click="rowClickFirstTable"></auth-table>
</el-card>
<div class="flex flex-direction-r b-w p-r-10 p-t-10 p-b-10">
<el-pagination v-show="firstTotal > 0" :total="firstTotal" v-model:currentPage="firstQuery.page"
v-model:page-size="firstQuery.limit" @current-change="handleCurrentChange" />
</div>
<div class="flex-item flex">
<div v-if="showTitleDialog" class="flex flex-column" style="width: 500px;">
<el-card shadow="never" class="demo-card custom-card fh">
<template #header>
<div>
<span v-if="radio == ''" class="f-12 f-b">表信息</span>
<span v-else class="f-12 f-b">{{ tableName }}表信息</span>
</div>
</template>
<auth-form ref="dataForm" :rules="mainRules" :edit-model="editModel" :form-fields="firstHeaderList"
v-model="firstTemp" :data="firstTemp" :col-num="2"></auth-form>
</el-card>
<!-- 主表 -->
<div class="flex-item flex flex-column b-w" v-show="showNotFullScreen && !showDetailInTable">
<auth-table size="small" ref="mainTable" id="mainTable" :table-fields="firstHeaderList" :data="mainList" :v-loading="listLoading" @row-click="rowClickFirstTable"
@row-dblclick="rowDblClickFirstTable"></auth-table>
<div class="flex flex-direction-r p-r-10 b-w p-b-5 p-t-5">
<el-pagination size="small" background v-show="firstTotal > 0" v-model:currentPage="firstQuery.page"
v-model:page-size="firstQuery.limit" :page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next, jumper" :total="firstTotal" @size-change="handleSizeChange"
@current-change="handleCurrentChange" />
</div>
<div class="flex-item flex flex-column">
<el-card shadow="never" class="custom-card flex-item flex flex-column" id="secondCard">
</div>
<!-- 主表表单(双击显示在主表位置) -->
<div class="flex-item flex flex-column b-w" v-show="showNotFullScreen && showDetailInTable">
<el-card shadow="never" class="custom-card flex-item flex flex-column">
<template #header>
<div class="flex flex-center">
<el-button size="small" class="custom-button" :icon="ArrowLeft" @click="returnToList">
返回列表
</el-button>
<el-button size="small" v-if="editModel" :icon="Close" class="custom-button" @click="cancelEdit">取消</el-button>
<el-button size="small" class="custom-button" :icon="Check" v-if="editModel && dialogStatus == 'create'" type="primary"
@click="createData">
确认
</el-button>
<el-button size="small" class="custom-button" :icon="Check" v-else-if="editModel" type="primary"
@click="updateData">确认</el-button>
</div>
</template>
<auth-form ref="dataForm" :rules="mainRules" :edit-model="editModel" :form-fields="firstHeaderList"
v-model="firstTemp" :data="firstTemp" :col-num="4"></auth-form>
</el-card>
</div>
<!-- 明细列表 -->
<div class="flex-item flex">
<div class="flex flex-column flex-item b-w" id="secondCard">
<el-card shadow="never" class="custom-card flex-item flex flex-column">
<template #header>
<div class="flex flex-center">
<el-icon v-if="showTitleDialog" @click="showTitleDialog = !showTitleDialog" :size="14">
<ArrowLeftBold />
</el-icon>
<el-icon v-else @click="showTitleDialog = !showTitleDialog" :size="14">
<ArrowRightBold />
</el-icon>
<span v-if="radio == ''" class="flex-item f-12 f-b">
字段信息(修改后,编辑框内回车生效)
明细列表(修改后,编辑框内回车生效)
</span>
<span v-else class="flex-item f-12 f-b">
{{ tableName }}表字段信息(修改后,编辑框内回车生效)
{{ tableName }}明细列表(修改后,编辑框内回车生效)
</span>
<el-button size="small" v-if="editModel" :icon="Plus" @click="onBtnClicked('btnAddDetail')">
添加
</el-button>
<el-button size="small" v-if="editModel" :icon="Delete" @click="onBtnClicked('btnDelDetail')">
删除
</el-button>
<column-setting buttonClass="filter-item" storageName="{SecondTableName}s_column_settings"
:columns="secondHeaderList" @update:columns="updateSecondColumns" class="ml-2" />
<el-button size="small" :icon="showNotFullScreen ? FullScreen : Close"
@click="showNotFullScreen = !showNotFullScreen">
{{ showNotFullScreen ? '全屏' : '退出全屏' }}
</el-button>
</div>
</template>
<auth-table size="small" ref="secondTable" :editModel="editModel" :table-fields="secondHeaderList"
:data="secondList" @row-click="rowClickSecondTable" @selection-change="selChangeSecondTable"
@item-change="handleUpdateDetail"></auth-table>
:data="secondList" @row-click="rowClickSecondTable" @selection-change="selChangeSecondTable"
@item-change="handleUpdateDetail"></auth-table>
</el-card>
<div class="flex flex-direction-r p-r-10 b-w p-b-5 p-t-5">
<el-pagination v-show="secondTotal > 0" :total="secondTotal" v-model:page="secondQuery.page"
v-model:limit="secondQuery.limit" @current-change="handleSecondPage" />
<el-pagination size="small" background v-show="secondTotal > 0" :currentPage="secondQuery.page"
:page-size="secondQuery.limit" :total="secondTotal" @current-change="handleSecondPage" />
</div>
</div>
</div>
<div v-if="editModel" class="p-t-5 p-b-5 t-r b-w p-r-10 border-t-2">
<el-button @click="editModel = false">取消</el-button>
<el-button v-if="dialogStatus == 'create'" type="primary" @click="createData">
确认
</el-button>
<el-button v-else type="primary" @click="updateData">确认</el-button>
</div>
</div>
<print-preview ref="preView" />
</div>
</template>
<script setup>
//引入核心框架
import { ElMessage, ElNotification } from 'element-plus'
import { Refresh, Delete } from '@element-plus/icons-vue'
import { onMounted, ref, reactive, nextTick, computed } from 'vue'
import { mapGetters, useStore } from 'vuex'
//引入API共用方法
import * as {FirstTableName}s from '@/api/{FirstTableName}s'
import * as {SecondTableName}s from '@/api/{SecondTableName}s'
import ColumnDefine from '@/interface/columnDefine'
import { typeLists } from '@/utils/const.js'
import { parseTime, defaultVal } from '@/utils/index'
import { delrows } from '@/utils/delRows.js'
//引入组件
import Sticky from '@/components/Sticky/index.vue'
import permissionBtn from '@/components/PermissionBtn/index.vue'
import AuthForm from '../../components/Base/AuthForm.vue'
import AuthTable from '../../components/Base/AuthTable.vue'
const firstHeaderList = ref([]) //列表头
const secondHeaderList = ref([]) //明细列表头
const multipleSelection = ref([]) //明细中checkbox选中的值
const mainList = ref([]) //主列表值
const secondList = ref([]) //明细列表值
const firstTotal = ref(0) //主列表总条数
const secondTotal = ref(0) //列表总条数
const listLoading = ref(true) //进度条
const editModel = ref(false) //是否为编辑状态
const editType = ref('edit')
const dialogStatus = ref('') //主修改对话框状态create/update
const radio = ref('') //主列表选项值
const tableName = ref('')
let firstTemp = reactive({}) //当前选中的头信息
let selectRow = reactive({})
const firstQuery = reactive({
// 查询条件
page: 1,
limit: 20,
key: undefined,
})
const secondQuery = reactive({
// 明细查询条件
page: 1,
limit: 20,
customerKey: undefined,
})
const showTitleDialog = ref(true) //是否显示左下主列表详情值
const mainRules = reactive({
Id: [
{
required: true,
message: 'id不能为空'
},
],
})
//组件refs
const mainTable = ref(null)
const dataForm = ref(null)
const secondTable = ref(null)
const store = useStore()
const defaultorgid = computed(() => store.getters.defaultorgid)
onMounted(() => {
initCfg()
getList()
})
const initCfg = function () {
//引入核心框架
import { ElMessage, ElNotification } from 'element-plus'
import { Plus, Delete, FullScreen, Close, Check, ArrowLeft } from '@element-plus/icons-vue'
import { onMounted, ref, reactive, nextTick, computed, defineComponent, watch } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
//引入API共用方法
import * as {FirstTableName}s from '@/api/{FirstTableName}s'
import * as {SecondTableName}s from '@/api/{SecondTableName}s'
import { parseTime, defaultVal } from '@/utils/index'
import common from "@/extensions/common.js"
import { getItem } from '@/utils/storage'
import ColumnDefine from '@/interface/columnDefine'
//引入组件
import Sticky from '@/components/Sticky/index.vue'
import permissionBtn from '@/components/PermissionBtn/index.vue'
import AuthForm from '../../components/Base/AuthForm.vue'
import AuthTable from '../../components/Base/AuthTable.vue'
import QueryBuilder from '@/components/QueryBuilder/index.vue'
import ColumnSetting from '@/components/ColumnSetting/index.vue'
const firstHeaderList = ref([]) //列表
const secondHeaderList = ref([]) //明细列表头
const multipleSelection = ref([]) //明细中checkbox选中的值
const mainList = ref([]) //主列表值
const secondList = ref([]) //明细列表值
const firstTotal = ref(0) //主列表总条数
const secondTotal = ref(0) //主列表总条数
const listLoading = ref(true) //进度条
const editModel = ref(false) //是否为编辑状态
const editType = ref('edit')
const dialogStatus = ref('') //主修改对话框状态create/update
const radio = ref('') //主列表选项值
const tableName = ref('')
const showDetailInTable = ref(false) // 是否在主表位置显示详情
let firstTemp = reactive({}) //当前选中的主表项
let selectRow = reactive({}) //当前选中的主表项
//组件refs
const mainTable = ref(null)
const dataForm = ref(null)
const secondTable = ref(null)
const preView = ref(null)
const store = useStore()
const router = useRouter()
const defaultorgid = computed(() => store.getters.defaultorgid)
const { showPreview, printPreview, createFlowInstance } = common()
const firstQuery = reactive({
// 查询条件
page: 1,
limit: 20,
key: undefined,
})
const secondQuery = reactive({
// 明细查询条件
page: 1,
limit: 20,
customerKey: undefined,
})
const showNotFullScreen = ref(true) //明细列表是否全屏
watch(showNotFullScreen, () => {
secondQuery.limit = showNotFullScreen.value ? 10 : 20
querySecondList(radio.value)
})
const mainRules = reactive({
Id: [
{
required: true,
message: '入库通知单号不能为空',
trigger: 'change',
},
],
})
onMounted(() => {
initCfg()
getList()
})
const initCfg = function () {
firstHeaderList.value = [
{FirstHeaderList}
]
secondHeaderList.value = [
{SecondHeaderList}
]
}
// ------------------------通用处理函数-------------------------------------
const onBtnClicked = function (domId, callback) {
switch (domId) {
case 'btnAdd': // 添加新记录
resetFirstTemp()
secondList.value = []
dialogStatus.value = 'create'
editModel.value = true
tableName.value = '新建'
editType.value = 'add'
nextTick(() => {
dataForm.value.clearValidate()
})
break
case 'btnEdit': // 编辑头
Object.assign(firstTemp, selectRow)
if (firstTemp.id === '') {
editModel.value = false
ElMessage({
message: '请选择要修改的项',
type: 'error',
})
return
}
dialogStatus.value = 'update'
editModel.value = true
editType.value = 'edit'
nextTick(() => {
dataForm.value.clearValidate()
})
break
case 'btnDel': // 删除主表
if (firstTemp.id === '') {
ElMessage({
message: '请选择要删除的项',
type: 'error',
})
return
}
handleFirstDel(firstTemp)
break
case 'btnAddDetail': // 添加明细行
handleAddOrderDetail()
break
case 'btnDelDetail': // 删除明细行
if (multipleSelection.value.length < 1) {
ElMessage({
message: '至少删除一个',
type: 'error',
})
return
}
handleSecondDel(multipleSelection.value)
break
case 'btnExport':
mainTable.value.exportExcel(`表结构${parseTime(new Date())}`, callback)
break
default:
break
// 尝试从存储中加载列设置
const savedSettings = getItem('{FirstTableName}s_column_settings')
if (savedSettings) {
// 将保存的设置合并到当前列表
firstHeaderList.value = savedSettings
}
const secondSavedSettings = getItem('{SecondTableName}s_column_settings')
if (secondSavedSettings) {
// 将保存的设置合并到当前列表
secondHeaderList.value = secondSavedSettings
}
}
// ------------------------主数据列表处理------------------------------------
const getList = function () {
listLoading.value = true
{FirstTableName}s.getList(firstQuery).then(response => {
mainList.value = response.data
firstTotal.value = response.count
if (firstTotal.value > 0) {
rowClickFirstTable(mainList.value[0])
// ------------------------通用处理函数-------------------------------------
const onBtnClicked = (domId, args, callback) => {
switch (domId) {
case 'btnAdd': // 添加
resetFirstTemp()
secondList.value = []
dialogStatus.value = 'create'
editModel.value = true
tableName.value = '新建'
editType.value = 'add'
showDetailInTable.value = true // 显示详情编辑界面
nextTick(() => {
dataForm.value.clearValidate()
})
break
case 'btnEdit': // 编辑
Object.assign(firstTemp, selectRow)
if (firstTemp.id === '') {
editModel.value = false
ElMessage.error('请选择要修改的项')
return
}
listLoading.value = false
})
}
const rowClickFirstTable = function (row) {
// 点击行
radio.value = row.id
tableName.value = row.tableName
secondQuery.page = 1
secondQuery.limit = 10
querySecondList(radio.value)
showTitleDetail(row)
}
const handleFilter = function () {
firstQuery.page = 1
getList()
}
const handleSizeChange = function (val) {
firstQuery.limit = val
getList()
}
const handleCurrentChange = function (val) {
firstQuery.page = val
getList()
}
const resetFirstTemp = function () {
firstHeaderList.value.forEach(item => {
firstTemp[item.columnName] = defaultVal(item.entityType)
})
firstTemp.namespace = 'OpenAuth.Repository.Domain'
dialogStatus.value = 'update'
editModel.value = true
editType.value = 'edit'
showDetailInTable.value = true // 编辑时显示详情界面
nextTick(() => {
dataForm.value.clearValidate()
})
break
case 'btnDel': // 删除
if (firstTemp.id === '') {
ElMessage.error('请选择要删除的项')
return
}
handleFirstDel(firstTemp)
break
case 'btnPrint':
if (firstTemp.id === '') {
ElMessage.error('请选择要打印的内容')
return
}
showPreview(preView.value, args, firstTemp);
break
case 'btnFlowscheme':
if (firstTemp.id === '') {
ElMessage.error('请选择要送审的内容')
return
}
createFlowInstance(args, firstTemp);
break
case 'btnAddDetail': // 添加明细行
handleAddOrderDetail()
break
case 'btnDelDetail': // 删除明细行
if (multipleSelection.value.length < 1) {
ElMessage.error('至少删除一个')
return
}
handleSecondDel(multipleSelection.value)
break
case 'btnExport': //导出
mainTable.value.exportExcel(`表结构${parseTime(new Date())}`, callback)
break
default:
break
}
}
const setDetails = tempData => {
// 处理明细
tempData.{SecondTableName}Reqs = []
secondList.value.length > 0 &&
// ------------------------主数据列表处理------------------------------------
const getList = () => {
listLoading.value = true
{FirstTableName}s.getList(firstQuery).then(response => {
mainList.value = response.data
firstTotal.value = response.count
if (firstTotal.value > 0) {
rowClickFirstTable(mainList.value[0])
}
listLoading.value = false
})
}
// 处理列设置更新
const handleColumnsUpdate = (columns) => {
if (columns) {
// 更新列显示配置
firstHeaderList.value = columns
} else {
// 如果是null则重置回默认配置
initCfg()
}
}
const updateSecondColumns = (columns) => {
if (columns) {
// 更新列显示配置
secondHeaderList.value = columns
} else {
// 如果是null则重置回默认配置
initCfg()
}
}
const rowClickFirstTable = row => {
// 点击行
mainTable.value.clearSelection()
mainTable.value.toggleRowSelection(row)
radio.value = row.id
tableName.value = row.id
secondQuery.page = 1
secondQuery.limit = 10
querySecondList(radio.value)
showTitleDetail(row)
}
const handleFilter = () => {
firstQuery.page = 1
getList()
}
const handleSizeChange = val => {
firstQuery.limit = val
getList()
}
const handleCurrentChange = val => {
firstQuery.page = val
getList()
}
const resetFirstTemp = () => {
firstHeaderList.value.forEach(item => {
firstTemp[item.columnName] = defaultVal(item.entityType)
})
}
const createData = () => {
// 保存提交
dataForm.value.validate(() => {
let tempData = Object.assign({}, firstTemp)
tempData = setDetails(tempData)
tempData.OrgId = defaultorgid.value
{FirstTableName}s.add(tempData).then(resp => {
if (resp.result != undefined) {
//如果服务器返回生成的ID
firstTemp.id = resp.result
}
mainList.value.unshift(firstTemp)
editModel.value = false
showDetailInTable.value = false // 保存后返回列表视图
rowClickFirstTable(firstTemp)
ElNotification.success('创建成功')
})
})
}
const showTitleDetail = row => {
// 弹出编辑框
Object.assign(selectRow, row) // 新增订单时保存当前选中行
Object.assign(firstTemp, row) // copy obj
nextTick(() => {
dataForm.value.clearValidate()
})
}
const setDetails = tempData => {
// 处理明细
tempData.{SecondTableName}Reqs = []
secondList.value.length > 0 &&
secondList.value.forEach(item => {
const obj = {}
secondHeaderList.value.forEach(header => {
obj[header.columnName] =
item[header.columnName] || defaultVal(header.entityType)
item[header.columnName] || defaultVal(header.entityType)
})
tempData.{SecondTableName}Reqs.push(obj)
})
return tempData
}
const createData = function () {
// 保存提交
dataForm.value.validate(() => {
let tempData = Object.assign({}, firstTemp)
tempData = setDetails(tempData)
tempData.OrgId = defaultorgid.value
{FirstTableName}s.add(tempData).then(resp => {
tempData.id = resp.result
mainList.value.unshift(tempData)
editModel.value = false
rowClickFirstTable(tempData)
ElNotification({
title: '成功',
message: '创建成功',
type: 'success',
duration: 2000,
})
})
})
}
const showTitleDetail = function (row) {
// 弹出编辑框
Object.assign(selectRow, row) // 新增订单时保存当前选中行
Object.assign(firstTemp, row) // copy obj
nextTick(() => {
dataForm.value.clearValidate()
})
}
const updateData = function () {
// 更新提交
dataForm.value.validate(() => {
let tempData = Object.assign({}, firstTemp)
tempData = setDetails(tempData)
{FirstTableName}s.update(tempData).then(() => {
for (const v of mainList.value) {
if (v.id === firstTemp.id) {
const index = mainList.value.indexOf(v)
mainList.value.splice(index, 1, tempData)
break
}
return tempData
}
const updateData = () => {
// 更新提交
dataForm.value.validate(() => {
let tempData = Object.assign({}, firstTemp)
tempData = setDetails(tempData)
{FirstTableName}s.update(tempData).then(() => {
for (const v of mainList.value) {
if (v.id === firstTemp.id) {
const index = mainList.value.indexOf(v)
mainList.value.splice(index, 1, tempData)
break
}
editModel.value = false
ElNotification({
title: '成功',
message: '更新成功',
type: 'success',
duration: 2000,
})
})
})
}
const handleFirstDel = function (row) {
// 删除头
{FirstTableName}s.del([row.id]).then(() => {
ElNotification({
title: '成功',
message: '删除成功',
type: 'success',
duration: 2000,
})
mainList.value = mainList.value.filter(item => item.id !== row.id)
if (mainList.value.length > 0) {
nextTick(() => {
rowClickFirstTable(mainList.value[0])
})
return
}
secondList.value = []
showTitleDetail({})
editModel.value = false
showDetailInTable.value = false // 保存后返回列表视图
ElNotification.success('更新成功')
})
}
// ------------------------明细列表处理-------------------------------------
const handleSecondPage = function (e) {
secondQuery.page = e
querySecondList(radio.value)
}
const querySecondList = id => {
{SecondTableName}s
.getList({
{ParentTableId}: id,
page: secondQuery.page,
limit: secondQuery.limit,
key: secondQuery.customerKey,
})
.then(res => {
}
const handleFirstDel = row => {
// 主表删除处理
{FirstTableName}s.del([row.id]).then(() => {
ElNotification.success('删除成功')
mainList.value = mainList.value.filter(item => item.id !== row.id)
if (mainList.value.length > 0) {
nextTick(() => {
rowClickFirstTable(mainList.value[0])
})
return
}
secondList.value = []
showTitleDetail({})
})
}
//返回列表
const returnToList = () => {
showDetailInTable.value = false
cancelEdit()
}
// 取消编辑
const cancelEdit = () => {
editModel.value = false
if (dialogStatus.value === 'create') {
showDetailInTable.value = false
}
}
// 主列表双击行事件
const rowDblClickFirstTable = row => {
rowClickFirstTable(row) // 先执行单击行的逻辑
showDetailInTable.value = true // 在主表位置显示详情
}
// ------------------------明细列表处理-------------------------------------
const handleSecondPage = e => {
secondQuery.page = e
querySecondList(radio.value)
}
const querySecondList = id => {
{SecondTableName}s
.getList({
{ParentTableId}: id,
page: secondQuery.page,
limit: secondQuery.limit,
key: secondQuery.customerKey,
})
.then(res => {
secondTotal.value = res.count
secondList.value = res.data
})
}
const rowClickSecondTable = function (row) {
// 行点击事件
secondTable.value.clearSelection()
secondTable.value.toggleRowSelection(row)
}
const handleSecondDel = function (rows) {
{SecondTableName}s.del(rows.map(item => item.id)).then(() => {
rows.forEach(row => {
secondList.value = secondList.value.filter(item => item.id !== row.id)
})
ElNotification({
title: '成功',
message: '删除成功',
type: 'success',
duration: 2000,
})
}
const rowClickSecondTable = row => {
// 行点击事件
secondTable.value.clearSelection()
secondTable.value.toggleRowSelection(row)
}
const handleSecondDel = function (rows) {
{SecondTableName}s.del(rows.map(item => item.id)).then(() => {
rows.forEach(row => {
secondList.value = secondList.value.filter(item => item.id !== row.id)
})
}
const selChangeSecondTable = function (val) {
// 明细选中事件
multipleSelection.value = val
}
const handleUpdateDetail = function (item) {
// 同步表数据结构
{SecondTableName}s.update(item).then(() => {
ElNotification({
title: '成功',
message: '更新成功',
type: 'success',
duration: 2000,
})
})
}
ElNotification.success('删除成功')
})
}
const selChangeSecondTable = val => {
// 明细选中事件
multipleSelection.value = val
}
const handleAddOrderDetail = () => {
// 添加明细
const obj = {}
secondHeaderList.value.forEach(header => {
obj[header.columnName] = defaultVal(header.entityType)
})
obj.orderId = firstTemp.id
secondList.value.push(Object.assign({}, obj))
}
const handleUpdateDetail = item => {
// 回车保存明细,如果不需要可以不用
{SecondTableName}s.update(item).then(() => {
ElNotification.success('成功')
})
}
</script>

View File

@@ -1,409 +1,491 @@
<!--
* @Author: yubaolee <yubaolee@163.com> | ahfu~ <954478625@qq.com>
* @Description: 代码生成界面,动态头部
* @
* @Copyright (c) 2024 by yubaolee | ahfu~ , All Rights Reserved.
* @Description: 代码生成器,多表模版,动态头部
* @Copyright (c) 2025 by yubaolee | ahfu~ , All Rights Reserved.
-->
<template>
<div class="flex-column flex">
<div class="flex flex-column">
<sticky>
<div class="search-box">
<el-input @keyup.enter="handleFilter" size="small" class="w-200 custom-input" :placeholder="'名称'"
v-model="firstQuery.key"></el-input>
<el-button class="filter-item custom-button" icon="el-icon-search" size="small" @click="handleFilter">
搜索
</el-button>
<div class="flex items-center">
<query-builder storageName="{FirstTableName}s_query_conditions" :columns="firstHeaderList"
:query-options="firstQuery" @search="handleFilter" class="flex-1" />
<column-setting storageName="{FirstTableName}s_column_settings" :columns="firstHeaderList"
@update:columns="handleColumnsUpdate" class="ml-2" />
</div>
</div>
<permission-btn v-on:btn-event="onBtnClicked"></permission-btn>
</sticky>
<div class="flex-item flex flex-column main-context">
<el-card shadow="never" class="custom-card flex-item flex flex-column">
<auth-table size="small" ref="mainTable" :select-type="'radio'" :table-fields="firstHeaderList" :data="mainList"
:v-loading="listLoading" @row-click="rowClickFirstTable"></auth-table>
</el-card>
<div class="flex flex-direction-r b-w p-r-10 p-t-10 p-b-10">
<el-pagination v-show="firstTotal > 0" :total="firstTotal" v-model:currentPage="firstQuery.page"
v-model:page-size="firstQuery.limit" @current-change="handleCurrentChange" />
</div>
<div class="flex-item flex">
<div v-if="showTitleDialog" class="flex flex-column" style="width: 500px;">
<el-card shadow="never" class="demo-card custom-card fh">
<template #header>
<div>
<span v-if="radio == ''" class="f-12 f-b">表信息</span>
<span v-else class="f-12 f-b">{{ tableName }}表信息</span>
</div>
</template>
<auth-form ref="dataForm" :rules="mainRules" :edit-model="editModel" :form-fields="firstHeaderList"
v-model="firstTemp" :data="firstTemp" :col-num="2"></auth-form>
</el-card>
<!-- 主表 -->
<div class="flex-item flex flex-column b-w" v-show="showNotFullScreen && !showDetailInTable">
<auth-table size="small" ref="mainTable" id="mainTable" :table-fields="firstHeaderList" :data="mainList" :v-loading="listLoading" @row-click="rowClickFirstTable"
@row-dblclick="rowDblClickFirstTable"></auth-table>
<div class="flex flex-direction-r p-r-10 b-w p-b-5 p-t-5">
<el-pagination size="small" background v-show="firstTotal > 0" v-model:currentPage="firstQuery.page"
v-model:page-size="firstQuery.limit" :page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next, jumper" :total="firstTotal" @size-change="handleSizeChange"
@current-change="handleCurrentChange" />
</div>
<div class="flex-item flex flex-column">
<el-card shadow="never" class="custom-card flex-item flex flex-column" id="secondCard">
</div>
<!-- 主表表单(双击显示在主表位置) -->
<div class="flex-item flex flex-column b-w" v-show="showNotFullScreen && showDetailInTable">
<el-card shadow="never" class="custom-card flex-item flex flex-column">
<template #header>
<div class="flex flex-center">
<el-button size="small" class="custom-button" :icon="ArrowLeft" @click="returnToList">
返回列表
</el-button>
<el-button size="small" v-if="editModel" :icon="Close" class="custom-button" @click="cancelEdit">取消</el-button>
<el-button size="small" class="custom-button" :icon="Check" v-if="editModel && dialogStatus == 'create'" type="primary"
@click="createData">
确认
</el-button>
<el-button size="small" class="custom-button" :icon="Check" v-else-if="editModel" type="primary"
@click="updateData">确认</el-button>
</div>
</template>
<auth-form ref="dataForm" :rules="mainRules" :edit-model="editModel" :form-fields="firstHeaderList"
v-model="firstTemp" :data="firstTemp" :col-num="4"></auth-form>
</el-card>
</div>
<!-- 明细列表 -->
<div class="flex-item flex">
<div class="flex flex-column flex-item b-w" id="secondCard">
<el-card shadow="never" class="custom-card flex-item flex flex-column">
<template #header>
<div class="flex flex-center">
<el-icon v-if="showTitleDialog" @click="showTitleDialog = !showTitleDialog" :size="14">
<ArrowLeftBold />
</el-icon>
<el-icon v-else @click="showTitleDialog = !showTitleDialog" :size="14">
<ArrowRightBold />
</el-icon>
<span v-if="radio == ''" class="flex-item f-12 f-b">
字段信息(修改后,编辑框内回车生效)
明细列表(修改后,编辑框内回车生效)
</span>
<span v-else class="flex-item f-12 f-b">
{{ tableName }}表字段信息(修改后,编辑框内回车生效)
{{ tableName }}明细列表(修改后,编辑框内回车生效)
</span>
<el-button size="small" v-if="editModel" :icon="Plus" @click="onBtnClicked('btnAddDetail')">
添加
</el-button>
<el-button size="small" v-if="editModel" :icon="Delete" @click="onBtnClicked('btnDelDetail')">
删除
</el-button>
<column-setting buttonClass="filter-item" storageName="{SecondTableName}s_column_settings"
:columns="secondHeaderList" @update:columns="updateSecondColumns" class="ml-2" />
<el-button size="small" :icon="showNotFullScreen ? FullScreen : Close"
@click="showNotFullScreen = !showNotFullScreen">
{{ showNotFullScreen ? '全屏' : '退出全屏' }}
</el-button>
</div>
</template>
<auth-table size="small" ref="secondTable" :editModel="editModel" :table-fields="secondHeaderList"
:data="secondList" @row-click="rowClickSecondTable" @selection-change="selChangeSecondTable"
@item-change="handleUpdateDetail"></auth-table>
:data="secondList" @row-click="rowClickSecondTable" @selection-change="selChangeSecondTable"
@item-change="handleUpdateDetail"></auth-table>
</el-card>
<div class="flex flex-direction-r p-r-10 b-w p-b-5 p-t-5">
<el-pagination v-show="secondTotal > 0" :total="secondTotal" v-model:page="secondQuery.page"
v-model:limit="secondQuery.limit" @current-change="handleSecondPage" />
<el-pagination size="small" background v-show="secondTotal > 0" :currentPage="secondQuery.page"
:page-size="secondQuery.limit" :total="secondTotal" @current-change="handleSecondPage" />
</div>
</div>
</div>
<div v-if="editModel" class="p-t-5 p-b-5 t-r b-w p-r-10 border-t-2">
<el-button @click="editModel = false">取消</el-button>
<el-button v-if="dialogStatus == 'create'" type="primary" @click="createData">
确认
</el-button>
<el-button v-else type="primary" @click="updateData">确认</el-button>
</div>
</div>
<print-preview ref="preView" />
</div>
</template>
<script setup>
//引入核心框架
import { ElMessage, ElNotification } from 'element-plus'
import { Refresh, Delete } from '@element-plus/icons-vue'
import { onMounted, ref, reactive, nextTick, computed } from 'vue'
import { mapGetters, useStore } from 'vuex'
//引入API共用方法
import * as {FirstTableName}s from '@/api/{FirstTableName}s'
import * as {SecondTableName}s from '@/api/{SecondTableName}s'
import ColumnDefine from '@/interface/columnDefine'
import { typeLists } from '@/utils/const.js'
import { parseTime, defaultVal } from '@/utils/index'
import { delrows } from '@/utils/delRows.js'
//引入组件
import Sticky from '@/components/Sticky/index.vue'
import permissionBtn from '@/components/PermissionBtn/index.vue'
import AuthForm from '../../components/Base/AuthForm.vue'
import AuthTable from '../../components/Base/AuthTable.vue'
const firstHeaderList = ref([]) //列表头
const secondHeaderList = ref([]) //明细列表头
const multipleSelection = ref([]) //明细中checkbox选中的值
const mainList = ref([]) //主列表值
const secondList = ref([]) //明细列表值
const firstTotal = ref(0) //主列表总条数
const secondTotal = ref(0) //主列表总条数
const listLoading = ref(true) //进度条
const editModel = ref(false) //是否为编辑状态
const editType = ref('edit')
const dialogStatus = ref('') //主修改对话框状态create/update
const radio = ref('') //主列表选项值
const tableName = ref('')
let firstTemp = reactive({}) //当前选中的头信息
let selectRow = reactive({})
const firstQuery = reactive({
// 查询条件
page: 1,
limit: 20,
key: undefined,
})
const secondQuery = reactive({
// 明细查询条件
page: 1,
limit: 20,
customerKey: undefined,
})
const showTitleDialog = ref(true) //是否显示左下主列表详情值
const mainRules = reactive({
Id: [
{
required: true,
message: 'id不能为空'
},
],
})
//组件refs
const mainTable = ref(null)
const dataForm = ref(null)
const secondTable = ref(null)
const store = useStore()
const defaultorgid = computed(() => store.getters.defaultorgid)
onMounted(() => {
initCfg()
getList()
})
const initCfg = function () {
firstHeaderList.value = [
{FirstHeaderList}
]
secondHeaderList.value = [
{SecondHeaderList}
]
}
// ------------------------通用处理函数-------------------------------------
const onBtnClicked = function (domId, callback) {
switch (domId) {
case 'btnAdd': // 添加新记录
resetFirstTemp()
secondList.value = []
dialogStatus.value = 'create'
editModel.value = true
tableName.value = '新建'
editType.value = 'add'
nextTick(() => {
dataForm.value.clearValidate()
})
break
case 'btnEdit': // 编辑头
Object.assign(firstTemp, selectRow)
if (firstTemp.id === '') {
editModel.value = false
ElMessage({
message: '请选择要修改的项',
type: 'error',
})
return
}
dialogStatus.value = 'update'
editModel.value = true
editType.value = 'edit'
nextTick(() => {
dataForm.value.clearValidate()
})
break
case 'btnDel': // 删除主表
if (firstTemp.id === '') {
ElMessage({
message: '请选择要删除的项',
type: 'error',
})
return
}
handleFirstDel(firstTemp)
break
case 'btnAddDetail': // 添加明细行
handleAddOrderDetail()
break
case 'btnDelDetail': // 删除明细行
if (multipleSelection.value.length < 1) {
ElMessage({
message: '至少删除一个',
type: 'error',
})
return
}
handleSecondDel(multipleSelection.value)
break
case 'btnExport':
mainTable.value.exportExcel(`表结构${parseTime(new Date())}`, callback)
break
default:
break
}
}
//引入核心框架
import { ElMessage, ElNotification } from 'element-plus'
import { Plus, Delete, FullScreen, Close, Check, ArrowLeft } from '@element-plus/icons-vue'
import { onMounted, ref, reactive, nextTick, computed, defineComponent, watch } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
// ------------------------主数据列表处理------------------------------------
const getList = () => {
listLoading.value = true
{FirstTableName}s.getList(firstQuery).then(response => {
response.columnFields.forEach(item => {
// 首字母小写
item.columnName =
item.columnName.substring(0, 1).toLowerCase() +
item.columnName.substring(1)
//引入API共用方法
import * as {FirstTableName}s from '@/api/{FirstTableName}s'
import * as {SecondTableName}s from '@/api/{SecondTableName}s'
import { parseTime, defaultVal } from '@/utils/index'
import common from "@/extensions/common.js"
import { getItem } from '@/utils/storage'
//引入组件
import Sticky from '@/components/Sticky/index.vue'
import permissionBtn from '@/components/PermissionBtn/index.vue'
import AuthForm from '../../components/Base/AuthForm.vue'
import AuthTable from '../../components/Base/AuthTable.vue'
import QueryBuilder from '@/components/QueryBuilder/index.vue'
import ColumnSetting from '@/components/ColumnSetting/index.vue'
const firstHeaderList = ref([]) //列表头
const secondHeaderList = ref([]) //明细列表头
const multipleSelection = ref([]) //明细中checkbox选中的值
const mainList = ref([]) //主列表值
const secondList = ref([]) //明细列表值
const firstTotal = ref(0) //主列表总条数
const secondTotal = ref(0) //主列表总条数
const listLoading = ref(true) //进度条
const editModel = ref(false) //是否为编辑状态
const editType = ref('edit')
const dialogStatus = ref('') //主修改对话框状态create/update
const radio = ref('') //主列表选项值
const tableName = ref('')
const showDetailInTable = ref(false) // 是否在主表位置显示详情
let firstTemp = reactive({}) //当前选中的主表项
let selectRow = reactive({}) //当前选中的主表项
//组件refs
const mainTable = ref(null)
const dataForm = ref(null)
const secondTable = ref(null)
const preView = ref(null)
const store = useStore()
const router = useRouter()
const defaultorgid = computed(() => store.getters.defaultorgid)
const { showPreview, printPreview, createFlowInstance } = common()
const firstQuery = reactive({
// 查询条件
page: 1,
limit: 20,
key: undefined,
})
const secondQuery = reactive({
// 明细查询条件
page: 1,
limit: 20,
customerKey: undefined,
})
const showNotFullScreen = ref(true) //明细列表是否全屏
watch(showNotFullScreen, () => {
secondQuery.limit = showNotFullScreen.value ? 10 : 20
querySecondList(radio.value)
})
const mainRules = reactive({
Id: [
{
required: true,
message: '入库通知单号不能为空',
trigger: 'change',
},
],
})
onMounted(() => {
getList()
})
// ------------------------通用处理函数-------------------------------------
const onBtnClicked = (domId, args, callback) => {
switch (domId) {
case 'btnAdd': // 添加
resetFirstTemp()
secondList.value = []
dialogStatus.value = 'create'
editModel.value = true
tableName.value = '新建'
editType.value = 'add'
showDetailInTable.value = true // 显示详情编辑界面
nextTick(() => {
dataForm.value.clearValidate()
})
firstHeaderList.value = response.columnFields
mainList.value = response.data
firstTotal.value = response.count
if (firstTotal.value > 0) {
rowClickFirstTable(mainList.value[0])
break
case 'btnEdit': // 编辑
Object.assign(firstTemp, selectRow)
if (firstTemp.id === '') {
editModel.value = false
ElMessage.error('请选择要修改的项')
return
}
listLoading.value = false
})
}
const rowClickFirstTable = function (row) {
// 点击行
radio.value = row.id
tableName.value = row.tableName
secondQuery.page = 1
secondQuery.limit = 10
querySecondList(radio.value)
showTitleDetail(row)
}
const handleFilter = function () {
firstQuery.page = 1
getList()
}
const handleSizeChange = function (val) {
firstQuery.limit = val
getList()
}
const handleCurrentChange = function (val) {
firstQuery.page = val
getList()
}
const resetFirstTemp = function () {
firstHeaderList.value.forEach(item => {
firstTemp[item.columnName] = defaultVal(item.entityType)
})
firstTemp.namespace = 'OpenAuth.Repository.Domain'
dialogStatus.value = 'update'
editModel.value = true
editType.value = 'edit'
showDetailInTable.value = true // 编辑时显示详情界面
nextTick(() => {
dataForm.value.clearValidate()
})
break
case 'btnDel': // 删除
if (firstTemp.id === '') {
ElMessage.error('请选择要删除的项')
return
}
handleFirstDel(firstTemp)
break
case 'btnPrint':
if (firstTemp.id === '') {
ElMessage.error('请选择要打印的内容')
return
}
showPreview(preView.value, args, firstTemp);
break
case 'btnFlowscheme':
if (firstTemp.id === '') {
ElMessage.error('请选择要送审的内容')
return
}
createFlowInstance(args, firstTemp);
break
case 'btnAddDetail': // 添加明细行
handleAddOrderDetail()
break
case 'btnDelDetail': // 删除明细行
if (multipleSelection.value.length < 1) {
ElMessage.error('至少删除一个')
return
}
handleSecondDel(multipleSelection.value)
break
case 'btnExport': //导出
mainTable.value.exportExcel(`表结构${parseTime(new Date())}`, callback)
break
default:
break
}
}
const setDetails = tempData => {
// 处理明细
tempData.{SecondTableName}Reqs = []
secondList.value.length > 0 &&
// ------------------------主数据列表处理------------------------------------
const getList = () => {
listLoading.value = true
{FirstTableName}s.getList(firstQuery).then(response => {
response.columnFields.forEach(item => {
// 首字母小写
item.columnName =
item.columnName.substring(0, 1).toLowerCase() +
item.columnName.substring(1)
})
firstHeaderList.value = response.columnFields
// 尝试从存储中加载列设置
const savedSettings = getItem('{FirstTableName}s_column_settings')
if (savedSettings) {
// 将保存的设置合并到当前列表
firstHeaderList.value = savedSettings
}
mainList.value = response.data
firstTotal.value = response.count
if (firstTotal.value > 0) {
rowClickFirstTable(mainList.value[0])
}
listLoading.value = false
})
}
// 处理列设置更新
const handleColumnsUpdate = (columns) => {
if (columns) {
// 更新列显示配置
firstHeaderList.value = columns
} else {
// 如果是null则重置回默认配置
getList()
}
}
const updateSecondColumns = (columns) => {
if (columns) {
// 更新列显示配置
secondHeaderList.value = columns
} else {
// 如果是null则重置回默认配置
querySecondList(radio.value)
}
}
const rowClickFirstTable = row => {
// 点击行
mainTable.value.clearSelection()
mainTable.value.toggleRowSelection(row)
radio.value = row.id
tableName.value = row.id
secondQuery.page = 1
secondQuery.limit = 10
querySecondList(radio.value)
showTitleDetail(row)
}
const handleFilter = () => {
firstQuery.page = 1
getList()
}
const handleSizeChange = val => {
firstQuery.limit = val
getList()
}
const handleCurrentChange = val => {
firstQuery.page = val
getList()
}
const resetFirstTemp = () => {
firstHeaderList.value.forEach(item => {
firstTemp[item.columnName] = defaultVal(item.entityType)
})
}
const createData = () => {
// 保存提交
dataForm.value.validate(() => {
let tempData = Object.assign({}, firstTemp)
tempData = setDetails(tempData)
tempData.OrgId = defaultorgid.value
{FirstTableName}s.add(tempData).then(resp => {
if (resp.result != undefined) {
//如果服务器返回生成的ID
firstTemp.id = resp.result
}
mainList.value.unshift(firstTemp)
editModel.value = false
showDetailInTable.value = false // 保存后返回列表视图
rowClickFirstTable(firstTemp)
ElNotification.success('创建成功')
})
})
}
const showTitleDetail = row => {
// 弹出编辑框
Object.assign(selectRow, row) // 新增订单时保存当前选中行
Object.assign(firstTemp, row) // copy obj
nextTick(() => {
dataForm.value.clearValidate()
})
}
const setDetails = tempData => {
// 处理明细
tempData.{SecondTableName}Reqs = []
secondList.value.length > 0 &&
secondList.value.forEach(item => {
const obj = {}
secondHeaderList.value.forEach(header => {
obj[header.columnName] =
item[header.columnName] || defaultVal(header.entityType)
item[header.columnName] || defaultVal(header.entityType)
})
tempData.{SecondTableName}Reqs.push(obj)
})
return tempData
}
const createData = function () {
// 保存提交
dataForm.value.validate(() => {
let tempData = Object.assign({}, firstTemp)
tempData = setDetails(tempData)
tempData.OrgId = defaultorgid.value
{FirstTableName}s.add(tempData).then(resp => {
tempData.id = resp.result
mainList.value.unshift(tempData)
editModel.value = false
rowClickFirstTable(tempData)
ElNotification({
title: '成功',
message: '创建成功',
type: 'success',
duration: 2000,
})
})
})
}
const showTitleDetail = function (row) {
// 弹出编辑框
Object.assign(selectRow, row) // 新增订单时保存当前选中行
Object.assign(firstTemp, row) // copy obj
nextTick(() => {
dataForm.value.clearValidate()
})
}
const updateData = function () {
// 更新提交
dataForm.value.validate(() => {
let tempData = Object.assign({}, firstTemp)
tempData = setDetails(tempData)
{FirstTableName}s.update(tempData).then(() => {
for (const v of mainList.value) {
if (v.id === firstTemp.id) {
const index = mainList.value.indexOf(v)
mainList.value.splice(index, 1, tempData)
break
}
return tempData
}
const updateData = () => {
// 更新提交
dataForm.value.validate(() => {
let tempData = Object.assign({}, firstTemp)
tempData = setDetails(tempData)
{FirstTableName}s.update(tempData).then(() => {
for (const v of mainList.value) {
if (v.id === firstTemp.id) {
const index = mainList.value.indexOf(v)
mainList.value.splice(index, 1, tempData)
break
}
editModel.value = false
ElNotification({
title: '成功',
message: '更新成功',
type: 'success',
duration: 2000,
})
})
})
}
const handleFirstDel = function (row) {
// 删除头
{FirstTableName}s.del([row.id]).then(() => {
ElNotification({
title: '成功',
message: '删除成功',
type: 'success',
duration: 2000,
})
mainList.value = mainList.value.filter(item => item.id !== row.id)
if (mainList.value.length > 0) {
nextTick(() => {
rowClickFirstTable(mainList.value[0])
})
return
}
secondList.value = []
showTitleDetail({})
editModel.value = false
showDetailInTable.value = false // 保存后返回列表视图
ElNotification.success('更新成功')
})
}
// ------------------------明细列表处理-------------------------------------
const handleSecondPage = function (e) {
secondQuery.page = e
querySecondList(radio.value)
}
const querySecondList = id => {
{SecondTableName}s
.getList({
{ParentTableId}: id,
page: secondQuery.page,
limit: secondQuery.limit,
key: secondQuery.customerKey,
})
.then(res => {
}
const handleFirstDel = row => {
// 主表删除处理
{FirstTableName}s.del([row.id]).then(() => {
ElNotification.success('删除成功')
mainList.value = mainList.value.filter(item => item.id !== row.id)
if (mainList.value.length > 0) {
nextTick(() => {
rowClickFirstTable(mainList.value[0])
})
return
}
secondList.value = []
showTitleDetail({})
})
}
//返回列表
const returnToList = () => {
showDetailInTable.value = false
cancelEdit()
}
// 取消编辑
const cancelEdit = () => {
editModel.value = false
if (dialogStatus.value === 'create') {
showDetailInTable.value = false
}
}
// 主列表双击行事件
const rowDblClickFirstTable = row => {
rowClickFirstTable(row) // 先执行单击行的逻辑
showDetailInTable.value = true // 在主表位置显示详情
}
// ------------------------明细列表处理-------------------------------------
const handleSecondPage = e => {
secondQuery.page = e
querySecondList(radio.value)
}
const querySecondList = id => {
{SecondTableName}s
.getList({
{ParentTableId}: id,
page: secondQuery.page,
limit: secondQuery.limit,
key: secondQuery.customerKey,
})
.then(res => {
res.columnFields.forEach(item => {
// 首字母小写
item.columnName =
item.columnName.substring(0, 1).toLowerCase() +
item.columnName.substring(1)
item.columnName.substring(0, 1).toLowerCase() +
item.columnName.substring(1)
})
secondHeaderList.value = res.columnFields
// 尝试从存储中加载列设置
const savedSettings = getItem('{SecondTableName}s_column_settings')
if (savedSettings) {
// 将保存的设置合并到当前列表
secondHeaderList.value = savedSettings
}
secondTotal.value = res.count
secondList.value = res.data
})
}
const rowClickSecondTable = function (row) {
// 行点击事件
secondTable.value.clearSelection()
secondTable.value.toggleRowSelection(row)
}
const handleSecondDel = function (rows) {
{SecondTableName}s.del(rows.map(item => item.id)).then(() => {
rows.forEach(row => {
secondList.value = secondList.value.filter(item => item.id !== row.id)
})
ElNotification({
title: '成功',
message: '删除成功',
type: 'success',
duration: 2000,
})
}
const rowClickSecondTable = row => {
// 行点击事件
secondTable.value.clearSelection()
secondTable.value.toggleRowSelection(row)
}
const handleSecondDel = function (rows) {
{SecondTableName}s.del(rows.map(item => item.id)).then(() => {
rows.forEach(row => {
secondList.value = secondList.value.filter(item => item.id !== row.id)
})
}
const selChangeSecondTable = function (val) {
// 明细选中事件
multipleSelection.value = val
}
const handleUpdateDetail = function (item) {
// 同步表数据结构
{SecondTableName}s.update(item).then(() => {
ElNotification({
title: '成功',
message: '更新成功',
type: 'success',
duration: 2000,
})
})
}
ElNotification.success('删除成功')
})
}
const selChangeSecondTable = val => {
// 明细选中事件
multipleSelection.value = val
}
const handleAddOrderDetail = () => {
// 添加明细
const obj = {}
secondHeaderList.value.forEach(header => {
obj[header.columnName] = defaultVal(header.entityType)
})
obj.orderId = firstTemp.id
secondList.value.push(Object.assign({}, obj))
}
const handleUpdateDetail = item => {
// 回车保存明细,如果不需要可以不用
{SecondTableName}s.update(item).then(() => {
ElNotification.success('成功')
})
}
</script>

View File

@@ -32,6 +32,10 @@ namespace OpenAuth.App
{
objs = objs.Where(u => u.Name.Contains(request.key));
}
if (!string.IsNullOrEmpty(request.sqlWhere))
{
objs = objs.Where(request.sqlWhere);
}
{ForeignKeyTemplate}
@@ -42,29 +46,26 @@ namespace OpenAuth.App
return result;
}
public void Add(AddOrUpdate{ClassName}Req req)
public void Add(AddOrUpdate{ClassName}Req request)
{
var obj = req.MapTo<{ClassName}>();
obj.Id = Guid.NewGuid().ToString();
//todo: 根据业务需要调整字段
obj.Createtime = DateTime.Now;
var obj = request.MapTo<{ClassName}>();
var user = _auth.GetCurrentUser().User;
obj.Createuserid = user.Id;
obj.Createusername = user.Name;
//todo:补充或调整自己需要的字段
{InsertColumns}
if(obj.KeyIsNull()) //如果主键为空,则生成默认值
{
obj.GenerateDefaultKeyVal();
}
Repository.Insert(obj);
}
public void Update(AddOrUpdate{ClassName}Req obj)
public void Update(AddOrUpdate{ClassName}Req request)
{
var user = _auth.GetCurrentUser().User;
Repository.Update(u => new {ClassName}
{
//todo: 根据业务需要调整字段
{ClassName} = obj.{ClassName},
Updatetime = DateTime.Now,
Updateuserid = user.Id,
Updateusername = user.Name
},u => u.Id == obj.Id);
{UpdateColumns}
},u => u.Id == request.Id);
}

View File

@@ -52,29 +52,27 @@ namespace OpenAuth.App
return result;
}
public void Add(AddOrUpdate{ClassName}Req req)
public void Add(AddOrUpdate{ClassName}Req request)
{
var obj = req.MapTo<{ClassName}>();
obj.Id = Guid.NewGuid().ToString();
//todo: 根据业务需要调整字段
obj.Createtime = DateTime.Now;
var obj = request.MapTo<{ClassName}>();
var user = _auth.GetCurrentUser().User;
obj.Createuserid = user.Id;
obj.Createusername = user.Name;
//todo:补充或调整自己需要的字段
{InsertColumns}
if(obj.KeyIsNull()) //如果主键为空,则生成默认值
{
obj.GenerateDefaultKeyVal();
}
Repository.Insert(obj);
}
public void Update(AddOrUpdate{ClassName}Req obj)
public void Update(AddOrUpdate{ClassName}Req request)
{
var user = _auth.GetCurrentUser().User;
Repository.Update(u => new {ClassName}
{
//todo: 根据业务需要调整字段
Name = obj.Name,
Updatetime = DateTime.Now,
Updateuserid = user.Id,
Updateusername = user.Name
},u => u.Id == obj.Id);
{UpdateColumns}
},u => u.Id == request.Id);
}

View File

@@ -7,10 +7,11 @@
<div class="flex flex-column">
<sticky>
<div class="search-box">
<el-input @keyup.enter="handleFilter" size="small" class="custom-input w-200" :placeholder="'名称'"
v-model="listQuery.key"></el-input>
<el-button size="small" class="custom-button filter-item" icon="el-icon-search"
@click="handleFilter">搜索</el-button>
<div class="flex items-center">
<query-builder storageName="{TableName}_query_conditions" :columns="headerList" :query-options="listQuery" @search="handleFilter" class="flex-1" />
<column-setting storageName="{TableName}_column_settings" :columns="headerList"
@update:columns="handleColumnsUpdate" class="ml-2" />
</div>
</div>
<permission-btn v-on:btn-event="onBtnClicked"></permission-btn>
</sticky>
@@ -59,6 +60,8 @@ const listLoading = ref(true) //进度条
const dialogFormVisible = ref(false) //是否显示编辑框
const dialogStatus = ref(0) //0:新增 1:编辑
let temp = reactive({}) //新增(编辑)绑定对话框
import ColumnSetting from '@/components/ColumnSetting/index.vue'
import QueryBuilder from '@/components/QueryBuilder/index.vue'
const listQuery = reactive({
// 查询条件
page: 1,
@@ -71,12 +74,37 @@ const rules = reactive({
//组件refs
const mainTableRef = ref(null)
const dataFormRef = ref(null)
onMounted(() => {
initHeaderList()
getList()
})
// 初始化表头
const initHeaderList = () => {
headerList.value = [
{HeaderList}
]
getList()
})
// 尝试从存储中加载列设置
const savedSettings = getItem('{TableName}_column_settings')
if (savedSettings) {
// 将保存的设置合并到当前列表
headerList.value = savedSettings
}
}
// 处理列设置更新
const handleColumnsUpdate = (columns) => {
if (columns) {
// 更新列显示配置
headerList.value = columns
} else {
// 如果是null则重置回默认配置
initHeaderList()
}
}
const rowClick = function (row) {
mainTableRef.value.clearSelection()
mainTableRef.value.toggleRowSelection(row)

View File

@@ -7,10 +7,11 @@
<div class="flex flex-column">
<sticky>
<div class="search-box">
<el-input @keyup.enter="handleFilter" size="small" class="custom-input w-200" :placeholder="'名称'"
v-model="listQuery.key"></el-input>
<el-button size="small" class="custom-button filter-item" icon="el-icon-search"
@click="handleFilter">搜索</el-button>
<div class="flex items-center">
<query-builder storageName="{TableName}_query_conditions" :columns="headerList" :query-options="listQuery" @search="handleFilter" class="flex-1" />
<column-setting storageName="{TableName}_column_settings" :columns="headerList"
@update:columns="handleColumnsUpdate" class="ml-2" />
</div>
</div>
<permission-btn v-on:btn-event="onBtnClicked"></permission-btn>
</sticky>
@@ -59,6 +60,8 @@ const listLoading = ref(true) //进度条
const dialogFormVisible = ref(false) //是否显示编辑框
const dialogStatus = ref(0) //0:新增 1:编辑
let temp = reactive({}) //新增(编辑)绑定对话框
import ColumnSetting from '@/components/ColumnSetting/index.vue'
import QueryBuilder from '@/components/QueryBuilder/index.vue'
const listQuery = reactive({
// 查询条件
page: 1,
@@ -118,10 +121,29 @@ const getList = function () {
item.columnName.substring(1)
})
headerList.value = response.columnFields
// 尝试从存储中加载列设置
const savedSettings = getItem('{TableName}_column_settings')
if (savedSettings) {
// 将保存的设置合并到当前列表
headerList.value = savedSettings
}
total.value = response.count
listLoading.value = false
})
}
// 处理列设置更新
const handleColumnsUpdate = (columns) => {
if (columns) {
// 更新列显示配置
headerList.value = columns
} else {
// 如果是null则重置回默认配置
getList()
}
}
const handleFilter = function () {
listQuery.page = 1
getList()

View File

@@ -1,6 +1,8 @@
🔥.Net权限管理及快速开发框架、最好用的权限工作流系统。源于Martin Fowler企业级应用开发思想及最新技术组合SqlSugar、EF、Quartz、AutoFac、WebAPI、Swagger、Mock、NUnit、Vue2/3、Element-ui/plus、IdentityServer等。核心模块包括角色授权、代码生成、API鉴权、智能打印、表单设计、工作流、定时任务等。架构易扩展是中小企业的首选。
![LOGO](https://gitee.com/uploads/images/2018/0425/163228_7077c3fd_362401.png "1.png")
<p align="center">
<img src="https://gitee.com/uploads/images/2018/0425/163228_7077c3fd_362401.png" alt="LOGO" width="300" />
</p>
**logo图标含义** OpenAuth中OA字母的结合体整体像鱼授人以渔你非说像咸鱼那也是积极向上的咸鱼中心是个笑脸微笑面对生活(✿◡‿◡)。
@@ -36,9 +38,9 @@
* Vue2演示地址 http://demo.openauth.net.cn:1803
* 移动H5演示地址(请使用浏览器移动模式或直接用手机打开): http://demo.openauth.net.cn:1804
* 移动H5演示地址http://demo.openauth.net.cn:1804
* 开源版演示地址: http://demo.openauth.net.cn:1802
* 开源版演示地址http://demo.openauth.net.cn:1802
## 📺视频教程
@@ -52,12 +54,15 @@
[OpenAuth.Net视频合集--表单设计](https://www.bilibili.com/video/BV1dagEeFEVA/)
[OpenAuth.Net视频合集--多表代码生成](https://www.bilibili.com/video/BV1r4J3zAEsx/)
## 📸效果预览
#### 后端启动效果
![API启动效果图](https://images.gitee.com/uploads/images/2021/0422/012037_ec51b7e6_362401.png "api启动.png")
#### vue版本效果
![主列表](snapshot/maintable.jpg)
![工作流](snapshot/image.png)
![打印方案设计](snapshot/sysprinterplan.png)
![打印方案挂载业务系统](snapshot/sysprinterplan2.png)
@@ -87,17 +92,17 @@
* 超强的自定义权限控制功能,请参考:[通用权限设计与实现](https://www.cnblogs.com/yubaolee/p/DataPrivilege.html)
* 完整API鉴权可以控制角色可访问的API资源及模块功能字段可见及是否返回请参考[按角色授权API资源](http://doc.openauth.net.cn/core/apiauth.html#%E6%8C%89%E8%A7%92%E8%89%B2%E6%8E%88%E6%9D%83api%E8%B5%84%E6%BA%90) 及 [字段权限](http://doc.openauth.net.cn/core/datapropertyrule.html)
* 完整API鉴权可以控制角色可访问的API资源及模块功能字段可见及是否返回请参考[按角色授权API资源](http://doc.openauth.net.cn/core/apiauth.html#%E6%8C%89%E8%A7%92%E8%89%B2%E6%8E%88%E6%9D%83api%E8%B5%84%E6%BA%90) 及 [字段权限](http://doc.openauth.net.cn/core/datapropertyrule/)
* 可拖拽的表单设计。详情:[可拖拽表单](http://doc.openauth.net.cn/pro/dragform.html)
* 可拖拽的表单设计。详情:[可拖拽表单](http://doc.openauth.net.cn/pro/dragform/)
* 可视化流程设计。[可视化流程设计](http://doc.openauth.net.cn/pro/startflow.html)
* 可视化流程设计。[可视化流程设计](http://doc.openauth.net.cn/pro/startflow/)
* 全网最好用的打印解决方案。详情:[智能打印](http://doc.openauth.net.cn/pro/printerplan.html)
* 全网最好用的打印解决方案。详情:[智能打印](http://doc.openauth.net.cn/pro/printerplan/)
* 基于Quartz.Net的定时任务控制,可随时启/停可视化配置Cron表达式功能请参考[定时任务](http://doc.openauth.net.cn/core/job.html)
* 基于Quartz.Net的定时任务控制,可随时启/停可视化配置Cron表达式功能请参考[定时任务](http://doc.openauth.net.cn/core/job/)
* 零代码动态API接口详情[动态API](http://doc.openauth.net.cn/core/dynamicapi.html)
* 零代码动态API接口详情[动态API](http://doc.openauth.net.cn/core/dynamicapi/)
* 基于CodeSmith的代码生成功能可快速生成带有头/明细结构的页面
@@ -107,7 +112,7 @@
* 支持多租户
* 支持搭建自己的IdentityServer服务器实现基于OAuth2的登录体系请参考[登录认证及OAuth集成](http://doc.openauth.net.cn/core/identity.html)
* 支持搭建自己的IdentityServer服务器实现基于OAuth2的登录体系请参考[登录认证及OAuth集成](http://doc.openauth.net.cn/core/identity/)
* 建立三方对接规范,已有系统可以无缝对接流程引擎

View File

@@ -1,7 +1,7 @@
/*
* @Author: yubaolee <yubaolee@163.com> | ahfu~ <954478625@qq.com>
* @Date: 2025-04-23 20:26:48
* @LastEditTime: 2025-05-07 20:49:08
* @LastEditTime: 2025-05-17 21:19:46
* @Description: 笔记配置
* Copyright (c) 2025 by yubaolee | ahfu~ , All Rights Reserved.
*/
@@ -54,12 +54,12 @@ const proNote = defineNoteConfig({
{
text: '基础开发',
collapsed: false,
items: ['start','deploy','structure','devnew', 'router','openurl','keepalive','datapropertyrule']
items: ['start','deploy','structure','devnew','devnewmulti', 'router','openurl','keepalive','datapropertyrule']
},
{
text: '内置组件',
collapsed: false,
items: ['table','querybuilder','columnsetting','components']
items: ['table','querybuilder','columnsetting','authselect','authform']
},
'printerplan',
{

View File

@@ -39,7 +39,7 @@ OpenAuth.Net工作流基于国际标准的BPMN2.0规范,并在此基础上做
4. 流程审批:【流程中心/待处理流程】中进行审批;
这里只介绍开源版本的操作流程vue版本操作流程请参考👉[操作手册](/pro/startflow.md)
这里只介绍开源版本的操作流程vue版本操作流程请参考👉[操作手册](/pro/startflow/)
## 流程设计
@@ -63,13 +63,13 @@ OpenAuth.Net工作流基于国际标准的BPMN2.0规范,并在此基础上做
* 指定用户:指定用户审批,当选中这项时需要选择指定的用户
* 指定角色:指定角色审批,当选中这项时需要选择指定的角色
👉使用[vue版本](/pro/startflow)解锁更多类型指定SQL、上一节点执行人的直属上级、连续多级直属上级、部门负责人、运行时指定角色、运行时指定用户等等当然你也可以为[开源版本](https://gitee.com/dotnetchina/OpenAuth.Net)贡献代码,添加更多类型。
👉使用[vue版本](/pro/startflow/)解锁更多类型指定SQL、上一节点执行人的直属上级、连续多级直属上级、部门负责人、运行时指定角色、运行时指定用户等等当然你也可以为[开源版本](https://gitee.com/dotnetchina/OpenAuth.Net)贡献代码,添加更多类型。
👉使用[vue版本](/pro/startflow),设置连线条件属性。你也可以为[开源版本](https://gitee.com/dotnetchina/OpenAuth.Net)贡献代码,让它支持该属性。
👉使用[vue版本](/pro/startflow/),设置连线条件属性。你也可以为[开源版本](https://gitee.com/dotnetchina/OpenAuth.Net)贡献代码,让它支持该属性。
## 流程发起
开源版本只能发起无业务关联的流程,使用[vue版本](/pro/startflow),解锁有业务类型关联的流程。当然你也可以为[开源版本](https://gitee.com/dotnetchina/OpenAuth.Net)贡献代码,支持更多类型。
开源版本只能发起无业务关联的流程,使用[vue版本](/pro/startflow/),解锁有业务类型关联的流程。当然你也可以为[开源版本](https://gitee.com/dotnetchina/OpenAuth.Net)贡献代码,支持更多类型。
### 无业务关联流程
@@ -97,7 +97,7 @@ OpenAuth.Net工作流基于国际标准的BPMN2.0规范,并在此基础上做
* 驳回:可以驳回到指定的步骤,该步骤的人可以重新审批提交;
👉使用[vue版本](/pro/startflow),解锁更多加签、撤销审批操作。你也可以为[开源版本](https://gitee.com/dotnetchina/OpenAuth.Net)贡献代码,让它支持这些功能。
👉使用[vue版本](/pro/startflow/),解锁更多加签、撤销审批操作。你也可以为[开源版本](https://gitee.com/dotnetchina/OpenAuth.Net)贡献代码,让它支持这些功能。

View File

@@ -183,10 +183,7 @@ stop
### ActivityId: 当前活动节点,即待审批的节点
与流程实例密切相关的还有两个表流程实例的操作记录FlowInstanceOperationHistory及流转记录FlowInstanceTransitionHistory。它们有不同的作用
与流程实例密切相关的还有流程实例的操作记录FlowInstanceOperationHistory
## 操作记录FlowInstanceOperationHistory
该表记录了流程实例的所有操作记录,包括流程的创建、撤回、驳回、同意、不同意等操作。
## 流转记录FlowInstanceTransitionHistory
记录某个流程实例所有已审批的从一个活动节点到下一个活动节点的操作人、操作时间。

View File

@@ -74,3 +74,45 @@ public class ResourceApp:SqlSugarBaseApp<Resource>
}
```
## 事务
SqlSugar提供了强大的事务功能在OpenAuth.Net中只需要在业务开始时调用`SugarClient.Ado.BeginTran()`开启事务,在业务结束时,调用`SugarClient.Ado.CommitTran()`提交事务,即可使用事务。
比如一个复杂的主从表业务无论中间过程使用的是SugarClient还是Repository都可以用这种方式来实现事务
```csharp
SugarClient.Ado.BeginTran();
// 更新从表中的字段
if (obj.SubTableReqs != null && obj.SubTableReqs.Any())
{
//id比数据库少的删除
var containids = obj.SubTableReqs.Select(u => u.Id)
.Where(u => !string.IsNullOrEmpty(u)).ToList();
if (containids.Any())
{
SugarClient.Deleteable<SubTable>(u => !containids.Contains(u.Id) && u.MainTableId == obj.Id).ExecuteCommand();
}
//id为空时添加
foreach (var detail in obj.SubTableReqs.Where(u => string.IsNullOrEmpty(u.Id)))
{
_subTableApp.Add(detail);
}
//更新id相同的
foreach (var detail in obj.SubTableReqs.Where(u => !string.IsNullOrEmpty(u.Id)))
{
_subTableApp.Update(detail);
}
}
// 更新主表中的字段
Repository.Update(u => new MainTable
{
NeedUpdateField = obj.NeedUpdateField,
//todo:补充或调整自己需要的字段
}, u => u.Id == obj.Id);
SugarClient.Ado.CommitTran();
```

View File

@@ -0,0 +1,125 @@
---
title: 通用表单组件
createTime: 2025/04/23 23:43:26
permalink: /pro/authform/
---
通用表单组件`AuthForm`是一个通用的表单组件,支持多种表单控件类型,具有字段联动功能,能满足大部分表单场景需求。
## 基本用法
```vue
<auth-form ref="dataForm" :edit-model="editModel" :form-fields="firstHeaderList" :data="firstTemp" :col-num="3"></auth-form>
```
## 属性
| 属性名 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| editModel | Boolean | false | 是否可编辑 |
| colNum | Number | 1 | 表单列数 |
| data | Object | - | 表单绑定的数据对象 |
| formFields | Array | [] | 表单字段定义 |
| rules | Object | - | 表单验证规则 |
| fieldRelations | Array | [] | 字段联动关系配置 |
## 事件
| 事件名 | 说明 | 回调参数 |
| --- | --- | --- |
| update:data | 表单数据更新时触发 | 更新后的表单数据对象 |
| field-change | 表单字段值变化时触发 | `{ fieldName, value }` |
## 方法
| 方法名 | 说明 | 参数 |
| --- | --- | --- |
| validate | 表单验证 | callback: 验证成功时的回调函数 |
| clearValidate | 清除验证信息 | - |
| applyFieldRelations | 手动应用字段联动关系 | fieldName: 字段名, value: 字段值 |
| handleFieldChange | 手动触发字段值变化 | fieldName: 字段名, value: 字段值 |
## formFields 字段配置
表单属性定义。为一个`ColumnDefine`的数组。
```javascript
{
columnName: 'fieldName', // 字段名,必填
remark: '显示名称', // 字段显示名称,必填
isEdit: true, // 是否在表单中显示,必填
editType: 'input', // 编辑类型,必填
sort: 1, // 排序值,越大越靠前
dataSource: [] // 下拉选项数据源当editType为select或selectDynamic时必填
}
```
### 支持的编辑类型 (editType)
| 类型 | 说明 |
| --- | --- |
| input | 普通文本输入框 |
| textarea | 多行文本输入框 |
| select | 静态下拉选择框 |
| selectDynamic | 动态下拉选择框 |
| date | 日期选择器 |
| datetime | 日期时间选择器 |
| switch | 开关控件 |
| checkbox | 复选框 |
| radio | 单选框 |
| file | 文件上传 |
## 字段联动配置
fieldRelations 属性用于配置字段间的联动关系,每个联动关系对象结构如下:
```javascript
{
sourceField: 'fieldName', // 源字段名称
targetField: 'targetFieldName', // 目标字段名称
condition: { // 触发条件
type: 'equals', // 条件类型
value: 'someValue' // 条件值
},
actionType: 'setValue', // 联动动作类型
value: 'newValue', // 设置的新值
elseAction: { // 可选,条件不满足时的动作
actionType: 'setValue',
value: 'defaultValue'
}
}
```
### 支持的条件类型
| 类型 | 说明 |
| --- | --- |
| equals | 等于 |
| notEquals | 不等于 |
| contains | 包含 |
| greaterThan | 大于 |
| lessThan | 小于 |
| empty | 为空 |
| notEmpty | 不为空 |
| custom | 自定义条件 |
### 支持的动作类型
| 类型 | 说明 |
| --- | --- |
| setValue | 设置字段值 |
| setVisible | 设置字段可见性 |
| setDisabled | 设置字段是否禁用 |
| clearValue | 清空字段值 |
| setDataSource | 设置数据源 |
## 文件上传
组件支持文件上传功能,上传的文件将被发送到 `/Files/Upload` 接口。上传成功后,文件路径会被自动设置到对应的表单字段中。
## 注意事项
1. 字段联动功能依赖于正确配置的fieldRelations属性
2. 文件上传功能需要后端提供正确的接口支持
3. 动态数据源需要配合AuthSelect组件使用
4. 编辑模式(editModel)设置为false时所有表单项将被禁用

View File

@@ -0,0 +1,145 @@
---
title: 通用下拉组件
createTime: 2025/05/12 10:24:28
permalink: /pro/authselect/
---
通用下拉选择`AuthSelect`用于渲染和编辑字典结构和动态列表返回key-valuel格式的数据。该组件与其他组件的关系如下
![20211230063538](http://img.openauth.net.cn/20211230063538.png)
## 基本用法
下拉选择组件目前支持`静态字典数据``动态接口数据`两种模式。
### 字典数据模式
静态字典。根据【字典分类】模块的`分类标识`关键字获取,如:
```vue
<auth-select
:isEdit="true"
@change="handleChange"
:data-source="'SYS_STATUS'"
v-model="value">
</auth-select>
```
### 动态接口模式
动态列表。从指定Url获取value/label结构的数据进行渲染
```vue
<auth-select
:isEdit="true"
@change="handleChange"
:type="'dynamic'"
:data-source="'/CategoryTypes/Load'"
v-model="value">
</auth-select>
```
### 直接传入数组
```vue
<auth-select
:isEdit="true"
@change="handleChange"
:data-source="[{label: '选项1', value: '1'}, {label: '选项2', value: '2'}]"
v-model="value">
</auth-select>
```
## API
### Props
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| type | 下拉框类型,`static`表示从数据字典加载,`dynamic`表示从接口获取 | String | `static` |
| dataSource | 数据源当type=='dynamic'时该值为一个Url地址该地址返回一个id/name结构的数组。当type=='static'或空时,该值为【字典分类】模块的`分类标识` | String/Number/Array | - |
| value / v-model | 绑定值 | String/Number | `''` |
| isEdit | 是否为编辑状态,`false`时显示文本 | Boolean | `true` |
| disabled | 是否禁用 | Boolean | `false` |
| size | 输入框尺寸 | String | `default` |
| searchKey | 搜索参数,当`type``dynamic`时有效 | Object | `{}` |
| defaultProps | 自定义选项的值和标签字段。如果该值为空则v-model需要传字段id/name的数组 | Object | `{label: 'label', value: 'value'}` |
### Events
| 事件名称 | 说明 | 回调参数 |
| --- | --- | --- |
| change | 选中值发生变化时触发 | 选中项的值 |
| update:value | 更新v-model值 | 选中项的值 |
## 示例
### 基础用法 - 字典数据
```vue
<template>
<auth-select
:isEdit="isEdit"
@change="onChange"
:data-source="'SYS_STATUS'"
v-model="status">
</auth-select>
</template>
<script setup>
import { ref } from 'vue'
const isEdit = ref(true)
const status = ref('')
const onChange = (val) => {
console.log('选中值:', val)
}
</script>
```
### 动态接口数据
```vue
<template>
<auth-select
:isEdit="isEdit"
@change="onChange"
:type="'dynamic'"
:data-source="'/CategoryTypes/Load'"
:search-key="searchParams"
v-model="selectedValue">
</auth-select>
</template>
<script setup>
import { ref, reactive } from 'vue'
const isEdit = ref(true)
const selectedValue = ref('')
const searchParams = reactive({
key: 'value',
otherParam: 'test'
})
const onChange = (val) => {
console.log('选中值:', val)
}
</script>
```
### 自定义字段名
```vue
<template>
<auth-select
:isEdit="isEdit"
:type="'dynamic'"
:data-source="'/api/users'"
:default-props="{label: 'username', value: 'id'}"
v-model="userId">
</auth-select>
</template>
<script setup>
import { ref } from 'vue'
const isEdit = ref(true)
const userId = ref('')
</script>
```

View File

@@ -4,9 +4,7 @@ createTime: 2025/05/07 20:24:28
permalink: /pro/columnsetting/
---
## 功能简介
`ColumnSetting` 是一个用于表格列显示与顺序自定义的弹窗组件,支持列的拖拽排序、显示/隐藏切换并将用户设置持久化到本地存储。用于配合框架el-table及[通用表格组件](./table.md)的列设置。
列设置组件`ColumnSetting` 是一个用于表格列显示与顺序自定义的弹窗组件,支持列的拖拽排序、显示/隐藏切换并将用户设置持久化到本地存储。用于配合框架el-table及[通用表格组件](./table.md)的列设置。
![2025-05-07-20-24-28](http://img.openauth.net.cn/2025-05-07-20-24-28.png)

View File

@@ -1,82 +0,0 @@
---
title: 其他组件
createTime: 2025/04/23 23:43:26
permalink: /pro/components/
---
OpenAuth.Pro封装了一些组件方便开发使用组件全部在`src\components`中定义。目前提供的三大核心组件关系如下:
![20211230063538](http://img.openauth.net.cn/20211230063538.png)
## 通用下拉选择 auth-select
通用下拉选择`auth-select`组件是用于渲染和编辑字典结构和动态列表返回key-valuel数据的组件。用法如下
静态字典。根据【字典分类】模块的`分类标识`关键字获取,如:
```html
<auth-select :isEdit="isEdit" @change="change" :data-source="'SYS_STATUS'" v-model="val" size="small"></auth-select>
```
动态列表。从指定Url获取value/label结构的数据进行渲染
```html
<auth-select :default-props="{label:'name', value:'id'}" :isEdit="isEdit" @change="change" :type="'dynamic'" :data-source="'/CategoryTypes/Load'" v-model="val" size="small"></auth-select>
```
该组件有以下几个参数:
isEdit:是否编辑如果为false则展示为label形式如果为true则为编辑状态
type选择类型。'dynamic'表示动态加载,空或'static'则从系统Category表中获取
data-source数据源。当type=='dynamic'时该值为一个Url地址该地址返回一个id/name结构的数组。当type=='static'或空时,该值为【字典分类】模块的`分类标识`
default-props: 数据源提供的数据类型。默认情况下为id/name的数组。如果设置该值则需要提供对应结构的数据:default-props="{label:'tableName', value:'id'}",则提供的数据格式如下:
```javascript
[
{id:'1',tableName:'category'},
{id:'2',tableName:'user'},
{id:'3',tableName:'module'},
....
]
```
## 表单组件 auth-form
根据定义动态渲染表单项,减少表单的开发。用法如下:
```html
<auth-form ref="dataForm" :edit-model="editModel" :form-fields="firstHeaderList" :data="firstTemp" :col-num="3"></auth-form>
```
当父组件需要使用表单里面的数据,进行提交操作时,参考以下代码:
```javascript
submit() {
// 保存提交
this.$refs['dataForm'].validate(() => {
let tempData = Object.assign({}, this.firstTemp)
api.add(tempData).then((resp) => {
...
this.$notify({
title: '成功',
message: '提交成功',
type: 'success'
})
})
})
}
```
该组件有以下几个参数:
form-fields表单属性定义。为一个`ColumnDefine`的数组。典型的值如下:
```javascript
this.firstHeaderList = [
new ColumnDefine('id', 'id', false, false, 'text', '', 'string', 'varchar', ''),
new ColumnDefine('tableName', '表名', true, true, 'text', '', 'string', 'varchar', ''),
new ColumnDefine('parentTableId', '父表', true, true, 'selectDynamic', '/BuilderTables/AllMain', 'string', 'varchar', ''),
]
```

View File

@@ -1,5 +1,5 @@
---
title: vue3代码生成
title: 单表代码生成
createTime: 2025/04/23 23:43:26
permalink: /pro/devnew/
---
@@ -49,7 +49,7 @@ getList() {
#### 固定头部
如果不是上面的情况,则生成前端列为固定的头部。如下:
代码生成的时候,按配置生成前端表头,一旦生成完毕,后台就无法通过授权等方式来控制显示的文字等。如下:
```html
@@ -68,6 +68,8 @@ initCfg() {
```
只能通过修改前端代码或使用[列设置](/pro/columnsetting/)组件来修改。
## 准备工作
* 数据库OpenAuthPro中添加仓储表【Stock】本文以该数据表为例。参考表结构如下
@@ -110,27 +112,10 @@ create table stock
这时,编辑字段具体的属性。如是否【可显示】【可编辑】等。
## 子表添加
::: warning 注意
如果只生成单表结构,可以跳过本节
:::
如果需要添加主/从(父/子)结构,需要先添加子表信息。代码生成界面,点击`添加`按钮,输入想添加的模块信息。
![20211208001551](http://img.openauth.net.cn/20211208001551.png)
::: warning 注意
子表的`动态头部`属性务必与父表保持一致,否则会造成生成的代码不能正常运行
:::
## 生成模块
选中刚刚添加的`Stock`依次点击【生成实体】【生成业务代码】【生成vue页面】;
如果存在子表,也进行相同的操作。即选中刚刚添加的`StockDetail`依次点击【生成实体】【生成业务代码】【生成vue页面】;
成功后生成的后端.Net代码位置如下
OpenAuth.Repository\Domain\Stock.cs
@@ -139,11 +124,13 @@ OpenAuth.App\StockApp\Request\AddOrUpdateStockReq.cs
OpenAuth.App\StockApp\Request\QueryStockListReq.cs
OpenAuth.WebApi\Controllers\StocksController.cs
并且会在OpenAuth.Repository\OpenAuthDBContext.cs中自动添加
::: warning 注意
以前代码生成器生成基于EF的代码时会在OpenAuth.Repository\OpenAuthDBContext.cs中自动添加
```
public virtual DbSet<Stock> Stocks { get; set; }
```
现在基于SqlSugar的代码生成器已不需要。如果有其他开发方面需要可自行手动添加。
:::
前端Vue代码
@@ -151,9 +138,23 @@ src\api\stocks.js
src\views\stocks\index.vue
::: warning 注意
完成以上步骤后请重启OpenAuth.WebApi用来重新加载刚刚生成的后台代码
1. 完成以上步骤后请重启OpenAuth.WebApi用来重新加载刚刚生成的后台代码
子表不需要添加模块
2. 代码生成器生成的应用层XxxxApp.cs里面的编辑功能只修改部分属性且按帕斯卡命名。需要根据业务需要调整要编辑的字段。如
```csharp
public void Update(AddOrUpdateChildtableReq obj)
{
var user = _auth.GetCurrentUser().User;
Repository.Update(u => new Childtable
{
//todo: 根据业务需要调整字段
Updatetime = DateTime.Now,
Updateuserid = user.Id,
Updateusername = user.Name
},u => u.Id == obj.Id);
}
```
:::
## 配置模块地址
@@ -166,13 +167,21 @@ src\views\stocks\index.vue
![20211208011431](http://img.openauth.net.cn/20211208011431.png)
尝试增加一条记录,看看模块是否能正常运行。
## 代码生成功能字段详解
* 模块名称:表示这个生成的模块名称,根据自己需要填写。
* 模块名称:**必填** 表示这个生成的模块名称,根据自己需要填写。
* 模块编码:**必填** 表示生成的模块业务类名称,比如`XxxxxApp`
* 命名空间:**必填** 表示生成的实体类的命名空间。默认`OpenAuth.Repository.Domain`
* 表名:**必填** 表示数据库中对应的表名。
* 父表:表示主/从(父/子)表的父表。如果该项不选,则生成的是单表结构的代码,如果选择了父表,则生成主/从(父/子)表的代码
* 实体类名称:**必填** 表示生成的实体类名称
* 父表:表示主/从表的父表。如果选择了父表,则当前表为子表,与对应的父表自动形成主/从关系。
* 外键:表示与父表关联的外键。如果选择了父表,则该项必填。
@@ -180,13 +189,125 @@ src\views\stocks\index.vue
* 动态头部:如果选中,表示前端渲染列表(或表单)时,列表的列定义是从后端返回。通常用于控制前端字段显示权限。
* 实体类名称:**必填** 表示生成的实体类名称
* Vue目录: 生成vue代码时代码存放的目录一般是vue项目的根目录。比如`D:\openauthvue3`
* 命名空间:**必填** 表示生成的实体类的命名空间。默认`OpenAuth.Repository.Domain`
* 模块编码:**必填** 表示生成的模块业务类名称,比如`XxxxxApp`
## 生成逻辑详解
* Vue目录: 生成vue代码时代码存放的目录。比如`D:\openauthvue3`
::: warning 注意
如果不想了解我们代码生成器生成代码的逻辑,可以跳过以下内容。
:::
如果你觉得框架自带的代码生成器没达到你想要的效果,可以自行修改。以下是代码生成器逻辑概述:
当您在代码生成界面选中一个或多个表并依次点击【生成实体】、【生成业务代码】、【生成vue页面】及Vue API系统会执行一系列后端操作来创建所需的代码文件。以下是这些操作的详细解析
### 1. 生成实体
点击【生成实体】后系统将为选中的数据库表创建对应的C#实体类
* **核心方法**: `BuilderTableApp.CreateEntity(CreateEntityReq req)`
* **主要流程**:
1. **数据准备**: 根据请求中的ID加载表定义信息 (`BuilderTable`) 和所有字段定义信息 (`BuilderTableColumn`)。
2. **重复性检查**: 调用 `CheckExistsModule` 方法,通过反射检查项目中是否已存在同名或已映射到同名数据库表的实体类,防止重复创建。
3. **模型创建 (`CreateEntityModel`)**:
* **模板**: 固定使用 `Template\BuildEntity.html`
* **基类选择**:
* 根据主键的数据库类型 (`ColumnType`) 和是否自增 (`IsIncrement`),实体类会继承自:
* `IntAutoGenEntity`: 若主键为数字类型 (如 `decimal`, `numeric`) 且自增。
* `LongEntity`: 若主键为数字类型 (如 `decimal`, `numeric`) 但不自增 (通常用于雪花ID)。
* `StringEntity`: 若主键为字符串等其他类型。
* **属性生成**:
* 遍历表中的每个字段(主键除外)。
* 为字段添加 `/// <summary>` XML注释源自字段备注`[Description("字段备注")]` 特性。
* 根据字段是否允许为空 (`IsRequired`) 和其数据类型 (`EntityType`),正确生成属性类型(例如,值类型若允许为空,则变为可空类型,如 `int?`)。
* 生成 `public {字段类型} {字段名} { get; set; }`
* **构造函数初始化**:
* 在实体类的构造函数中,为每个非主键属性赋予初始值 (例如 `string` 类型初始化为 `""``DateTime` 初始化为 `DateTime.Now``bool` 初始化为 `false` 等)。
* **表注解**:
* 为实体类添加 `/// <summary>` XML注释源自表备注
* 添加 `[Table("实际数据库表名")]` 特性,将实体类映射到数据库表。
* **文件输出**: 生成的实体类代码最终会写入到 `OpenAuth.Repository\Domain\{实体类名}.cs` 文件。
### 2. 生成业务代码
点击【生成业务代码】后系统会创建应用服务层App、相关的请求对象Request DTOs以及WebAPI控制器Controller
* **核心方法**: `BuilderTableApp.CreateBusiness(CreateBusiReq req)`
* **主要流程**: 此方法会依次调用以下三个内部方法:
* **A. `GenerateApp` (生成应用服务类)**
1. **路径与检查**: 定位 `.App` 项目路径,并检查模块代码 (`ModuleCode`) 是否已存在。
2. **模板选择**:
* **主从表判断**: 根据当前表是否有子表 (`ParentTableId`),区分为单表或主从表生成逻辑。
* **动态/固定头部**: 根据表配置的 `IsDynamicHeader` 属性,选择对应的模板。
* 单表动态头部: `Template\SingleTable\BuildAppWithDynamicHeader.html`
* 单表固定头部: `Template\SingleTable\BuildApp.html`
* 主从表动态头部: `Template\MultiTable\BuildAppWithDynamicHeader.html`
* 主从表固定头部: `Template\MultiTable\BuildApp.html`
3. **基类选择**:
* 应用服务类的基类根据主键类型决定:
* `BaseIntAutoGenApp`: 对应 `IntAutoGenEntity`
* `BaseLongApp`: 对应 `LongEntity`
* `BaseStringApp`: 对应 `StringEntity`
4. **代码生成**:
* 替换模板中的占位符,如 `{ClassName}` (实体类名), `{ModuleCode}` (模块代码), `{ModuleName}` (模块名), `{StartName}` (通常为项目主命名空间前缀如 `OpenAuth`)。
* **外键处理**: 如果当前生成的是子表的App服务会在查询列表中自动加入基于其外键 (`ForeignKey`) 的过滤条件。
* **主从表逻辑**: 如果是主表且有关联子表,模板中会包含对子表操作的逻辑,并替换子表相关占位符如 `{SubClassName}`, `{SubForeignKey}`
5. **文件输出**: 生成的应用服务类写入到 `OpenAuth.App\{ModuleCode}\{ModuleCode}.cs`
* **B. `GenerateAppReq` (生成请求DTO)**
1. **路径**: 定位 `.App` 项目路径。
2. **查询请求对象 (`Query{ClassName}ListReq.cs`)**:
* 模板: `Template\BuildQueryReq.html`
* 内容: 主要包含分页、排序、关键词查询等基础参数。如果当前表是子表,还会自动添加其外键字段作为查询参数。
* 文件输出: `OpenAuth.App\{ModuleCode}\Request\Query{ClassName}ListReq.cs`
3. **添加/更新请求对象 (`AddOrUpdate{ClassName}Req.cs`)**:
* 模板: `Template\BuildUpdateReq.html`
* 内容:
* 包含表中所有可编辑字段对应的属性每个属性都有XML注释。
* 如果当前是主表且有关联子表,会自动添加一个子表请求对象列表属性,如 `public List<AddOrUpdate{SubClassName}Req> {SubClassName}Reqs { get; set; }`,用于一次性提交主从数据。
* 文件输出: `OpenAuth.App\{ModuleCode}\Request\AddOrUpdate{ClassName}Req.cs`
* **C. `GenerateWebApi` (生成控制器)**
1. **路径与检查**: 定位 `.WebApi` 项目路径,控制器名称通常为 `{ClassName}sController` (如 `StocksController`),并检查是否已存在。
2. **模板**: `Template\BuildControllerApi.html`
3. **主键类型处理**: 控制器中的 `Get(id)``Update(id, req)` 等接口的 `id` 参数类型,会根据实体主键的实际类型(`int`, `long`/`decimal`, `string`)进行适配。
4. **代码生成**: 替换模板占位符注入对应的App服务。
5. **文件输出**: 生成的控制器写入到 `OpenAuth.WebApi\Controllers\{ClassName}sController.cs`
### 3. 生成Vue页面与API脚本
点击【生成vue页面】后系统不仅会为选中的主表创建Vue视图文件还会为其以及其子表如果存在生成前端调用API的JavaScript文件。
* **核心方法 (Vue视图)**: `BuilderTableApp.CreateVue(CreateVueReq req)`
* **核心方法 (Vue API脚本)**: `BuilderTableApp.CreateVueApi(CreateVueReq req)`
* **A. `CreateVue` (生成 `.vue` 文件)**:
1. **路径校验**: 确保提供了Vue项目的根目录 (`req.VueProjRootPath`)。
2. **子表跳过**: 如果当前选中的表是子表(即 `ParentTableId` 不为空则不生成独立的Vue页面因为子表通常在主表的页面中以内嵌表格形式管理。
3. **模板选择**:
* **主从表判断**: 区分单表和主从表。
* **Vue版本与动态头部**: 根据请求中的Vue版本 (`req.Version`,如 'vue3') 和表的 `IsDynamicHeader` 配置,选择对应的模板:
* Vue3单表动态头: `Template\SingleTable\BuildVue3WithDynamicHeader.html`
* Vue3单表固定头: `Template\SingleTable\BuildVue3.html`
* (Vue2的模板类似`Template\SingleTable\BuildVue.html`)
* 主从表模板类似 (如 `Template\MultiTable\BuildVue3.html`)
4. **表头生成 (`BuilderHeader` 辅助方法)**:
* 遍历表的字段配置 (`BuilderTableColumn`)。
* 为每个需要在列表或表单中显示的字段,生成 `new ColumnDefine(...)` 的JavaScript代码。`ColumnDefine` 对象包含了字段的驼峰命名、显示名(备注)、是否可编辑、是否在列表显示、编辑控件类型 (`EditType`)、数据源 (`DataSource`,用于下拉框等)、原始实体类型、数据库列类型等信息。
5. **代码生成与占位符替换**:
* **单表**: 替换 `{ClassName}` (主表类名), `{TableName}` (主表类名驼峰式), `{HeaderList}` (主表头定义)。
* **主从表**: 替换 `{FirstTableName}` (主表名驼峰), `{SecondTableName}` (子表名驼峰), `{FirstHeaderList}` (主表头), `{SecondHeaderList}` (子表头), `{ParentTableId}` (子表外键驼峰式,用于关联主从数据)。
6. **文件输出**: 生成的Vue文件写入到 `{Vue项目根路径}\src\views\{主表类名小写}s\index.vue`
* **B. `CreateVueApi` (生成 `apiname.js` 文件)**:
1. **适用对象**: 无论是主表还是子表都会为其生成API脚本。
2. **模板**: `Template\BuildVueApi.html`
3. **代码生成**: 主要替换 `{TableName}` 占位符为表的类名驼峰式。模板中预设了对后端标准CRUD接口的调用函数 (如 `getList`, `add`, `update`, `del` 等)。
4. **文件输出**: 生成的API脚本写入到 `{Vue项目根路径}\src\api\{表类名驼峰式}s.js`
---
## 其他

View File

@@ -0,0 +1,140 @@
---
title: 多表代码生成
createTime: 2025/04/23 23:43:26
permalink: /pro/devnewmulti/
---
本章节视频讲解请参考:[OpenAuth.Net视频合集--多表代码生成](https://www.bilibili.com/video/BV1r4J3zAEsx/)
上节介绍了[单表代码生成](/pro/devnew/),本节介绍多表代码生成。
## 准备工作
本文以mysql数据库为例在openauthpro数据库中添加父表parenttable、子表childtable,参考表结构如下:
```SQL
CREATE TABLE `parenttable` (
`id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '父表ID',
`externalno` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '自定义编号',
`status` int(11) NOT NULL COMMENT '状态',
`name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称',
`createtime` datetime NOT NULL COMMENT '创建时间',
`createuserid` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '创建人ID',
`createusername` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '创建人',
`updatetime` datetime NULL DEFAULT NULL COMMENT '最后更新时间',
`updateuserid` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最后更新人ID',
`updateusername` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最后更新人',
`orgid` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '所属部门',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '父表' ROW_FORMAT = DYNAMIC;
CREATE TABLE `childtable` (
`id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '子表ID',
`parentid` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '父表ID',
`remark` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '备注',
`pricenotax` decimal(18, 6) NULL DEFAULT NULL COMMENT '单价',
`asnstatus` int(11) NOT NULL COMMENT '细单状态',
`name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '商品名称',
`goodssku` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '商品SKU',
`goodsbatch` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '商品批号',
`ordernum` decimal(18, 2) NOT NULL COMMENT '数量',
`proddate` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '生产日期',
`expiredate` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '失效日期',
`createtime` datetime NOT NULL COMMENT '创建时间',
`createuserid` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '创建人ID',
`createusername` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '创建人',
`updatetime` datetime NULL DEFAULT NULL COMMENT '最后更新时间',
`updateuserid` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最后更新人ID',
`updateusername` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最后更新人',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '子表' ROW_FORMAT = DYNAMIC;
```
* 使用Visual Studio 以开发模式启动后端。参考:[编译运行webapi](http://doc.openauth.net.cn/core/start/#%E7%BC%96%E8%AF%91%E8%BF%90%E8%A1%8Cwebapi)
* 启动企业版前端。参考:[启动前端](http://doc.openauth.net.cn/pro/#%E5%90%AF%E5%8A%A8%E5%89%8D%E7%AB%AF)
后续操作全部在系统中完成。
## 添加父表
代码生成界面,点击`添加`按钮,输入想添加的模块信息。相关字段说明可参考[代码生成功能字段详解](/pro/devnew/#代码生成功能字段详解)
![20250518152210](http://img.openauth.net.cn/20250518152210.png)
录入基础信息,点击右下角`确认`保存后,系统会添加一条记录,并自动加载对应的字段信息。
![20250518152235](http://img.openauth.net.cn/20250518152235.png)
这时,编辑字段具体的属性。如是否【可显示】【可编辑】等。
## 添加子表
同添加父表一样,点击`添加`按钮,输入子模块信息。
![20250518152326](http://img.openauth.net.cn/20250518152326.png)
::: warning 注意
* 子表的`父表`为必填字段,选择刚刚添加的父表。
* 子表的`外键`字段为必填字段,选择子表设计的类似`父表ID`字段,从而可以与父表关联。
* 子表的`动态头部`属性务必与父表保持一致,否则会造成生成的代码不能正常运行
:::
## 生成模块
选中刚刚添加的父表依次点击【生成实体】【生成业务代码】【生成vue页面】;
子表虽然不需要生成vue页面但需要生成前端js api;
成功后生成的后端.Net代码位置如下
OpenAuth.Repository\Domain\Parentable.cs
OpenAuth.App\ParentableApp\ParentableApp.cs
OpenAuth.App\ParentableApp\Request\AddOrUpdateParentableReq.cs
OpenAuth.App\ParentableApp\Request\QueryParentableListReq.cs
OpenAuth.WebApi\Controllers\ParentablesController.cs
OpenAuth.Repository\Domain\Childtable.cs
OpenAuth.App\ChildtableApp\ChildtableApp.cs
OpenAuth.App\ChildtableApp\Request\AddOrUpdateChildtableReq.cs
OpenAuth.App\ChildtableApp\Request\QueryChildtableListReq.cs
OpenAuth.WebApi\Controllers\ChildtablesController.cs
前端Vue代码
src\api\parentables.js
src\api\childtables.js
src\views\parentables\index.vue
::: warning 注意
1. 完成以上步骤后请重启OpenAuth.WebApi用来重新加载刚刚生成的后台代码
2. 代码生成器生成的应用层XxxxApp.cs里面的编辑功能只修改部分属性且按帕斯卡命名。需要根据业务需要调整要编辑的字段。如
```csharp
public void Update(AddOrUpdateChildtableReq obj)
{
var user = _auth.GetCurrentUser().User;
Repository.Update(u => new Childtable
{
//todo: 根据业务需要调整字段
Updatetime = DateTime.Now,
Updateuserid = user.Id,
Updateusername = user.Name
},u => u.Id == obj.Id);
}
```
:::
## 配置模块地址
经过以上步骤重启系统后使用System账号重新登录在【模块管理】中添加`测试模块`模块,如下图:
![20250518153337](http://img.openauth.net.cn/20250518153337.png)
这样就可以访问刚刚新加的`测试模块`功能,到此就完成了添加一个新模块功能:
![20250518153406](http://img.openauth.net.cn/20250518153406.png)
尝试增加一条记录,看看模块是否能正常运行。

View File

@@ -4,59 +4,86 @@ createTime: 2025/04/23 23:43:26
permalink: /pro/printerplan/
---
OpenAuth.Pro提供灵活无代码可能需要写SQL)的打印解决方案,无需开发人员参与即可配置自己需要的打印
OpenAuth.Pro提供强大且灵活的低代码打印方案,企业用户仅需进行简单配置(可能需要写SQL查询)即可实现个性化打印需求,无需开发人员参与即可快速部署各类打印模板
打印模板设计界面如下:
## 系统界面展示
### 打印模板设计界面
专业的设计工作台,支持拖拽式操作与精确配置:
![sysprinterplan](http://img.openauth.net.cn/sysprinterplan.png)
挂载到业务系统效果
### 业务系统集成效果
无缝集成到各类业务功能模块中:
![20231002114914](http://img.openauth.net.cn/20231002114914.png)
## 使用指南
## 详细使用指南
### 设计打印模版
### 创建与设计打印模版
在打印中心-打印模版功能新增打印模版。设计界面说明如下:
打印中心】-【打印模版功能新增打印模版。设计界面布局如下:
![20231004165652](http://img.openauth.net.cn/20231004165652.png)
左边为设计工具栏,中间为画布,右边为属性框。可以根据自己需要调整打印界面。
界面分为三大区域:
- 左侧:设计工具栏,提供各类控件与格式设置工具
- 中间:设计画布,实时预览打印效果
- 右侧:属性配置区,精确调整各元素参数与样式
### 设置数据源
用户可根据实际需求,通过拖拽与配置快速构建专业打印模板。
打印模版的核心是设计数据源。用户通过设计数据源,实现无开发打印。如下:
### 配置数据源
数据源是打印模版的核心,通过合理设计数据源,实现真正的低代码打印解决方案:
![20231004165939](http://img.openauth.net.cn/20231004165939.png)
1. 数据源:用于查询打印打印数据。
2. Id:由数据源中的SQL自动生成的参数文本框。
3. 分组字段:如果配置了`分组字段`,则默认对应列表式打印模版。否则,可以看做卡片式。
关键配置项说明:
* 【卡片式】用于打印一页只有一条数据的情况。比如快递的标签、用户名片等。
1. **数据源**用于查询打印数据的SQL语句支持复杂查询与条件筛选
2. **Id参数**系统根据数据源SQL自动生成的参数文本框用于动态传递查询条件
3. **分组字段**:决定打印模版的类型与数据结构处理方式
- 配置分组字段:生成列表式打印模版(一对多数据结构)
- 不配置分组字段:生成卡片式打印模版(单条数据记录)
### 打印模式类型说明
系统支持两种主要打印模式,适应不同业务场景:
* **卡片式打印**:适用于单条记录的打印场景,如快递标签、会员卡、用户名片等
![20231004164125](http://img.openauth.net.cn/20231004164125.png)
* 【列表式】用于打印一页有多条数据的情况,通常是主从结构的表,同时打印主表及从表详情。如客户的发货清单。
![20231004164203](http://img.openauth.net.cn/20231004164203.png)
::: warning 注意
* **列表式打印**:适用于主从结构数据的打印场景,一页包含多条关联数据,如销售订单、发货清单等
![20231004164203](http://img.openauth.net.cn/20231004164203.png)
两种模版类型没有严格的区分。在后端返回相同的数据情况下,如果配置了`分组字段`后端返回的数据会被转化成主从结构。上图即为官网【入库随货同行单列表式【带参数】】按OrderId进行分组后的数据。
::: warning 特别说明
两种模版类型在底层处理上存在灵活性。当配置了`分组字段`后端将自动将数据按该字段进行分组转化为主从结构。例如上图展示的【入库随货同行单列表式【带参数】】正是通过按OrderId分组后生成的数据结构。
:::
### 功能模块挂载打印方案
### 打印方案与功能模块关联
制作完打印方案后,在【模块管理】功能里面,把刚刚打印方案与功能关联
打印方案设计后,在【模块管理】功能中将打印方案与业务功能关联,实现功能集成:
![20231004173811](http://img.openauth.net.cn/20231004173811.png)
### 使用打印
### 实际打印操作
最后就可以在功能里面使用打印了。
完成上述配置后,用户可在相应业务功能中直接调用打印方案,实现一键打印:
![20231002114914](http://img.openauth.net.cn/20231002114914.png)
![20231002114914](http://img.openauth.net.cn/20231002114914.png)
## 最佳实践建议
1. 设计数据源时建议先测试SQL查询结果确保数据结构符合预期
2. 打印模板设计时注意适配不同纸张规格,预留适当边距
3. 对于复杂报表,可考虑使用分组字段实现数据的合理组织
4. 利用系统提供的预览功能,及时调整布局与样式

View File

@@ -4,9 +4,7 @@ createTime: 2025/05/07 19:10:57
permalink: /pro/querybuilder/
---
## 组件简介
自定义查询组件是一个可复用的高级查询条件构建器组件,支持通过配置 columns 自动生成 SQL 查询条件。用于配合框架el-table及[通用表格组件](./table.md)的复杂筛选的场景支持条件的增删、类型自动识别、SQL 片段生成与复制、条件本地缓存等功能。
自定义查询组件`QueryBuilder`是一个可复用的高级查询条件构建器组件,支持通过配置 columns 自动生成 SQL 查询条件。用于配合框架el-table及[通用表格组件](./table.md)的复杂筛选的场景支持条件的增删、类型自动识别、SQL 片段生成与复制、条件本地缓存等功能。
![2025-05-07-19-20-48](http://img.openauth.net.cn/2025-05-07-19-20-48.png)

View File

@@ -4,9 +4,9 @@ createTime: 2025/05/07 19:10:57
permalink: /pro/table/
---
## 组件简介
通用表格组件`AuthTable` 是OpenAuth.Net框架的核心组件之一支持自定义列模板、单选/多选、可编辑、导出 Excel 等功能。
`AuthTable` 是一个通用表格组件,支持自定义列模板、单选/多选、可编辑、导出 Excel 等功能。
![2025-05-12-16-14-13](http://img.openauth.net.cn/2025-05-12-16-14-13.png)
## 使用示例

BIN
snapshot/maintable.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB