😎1、优化自定义查询 2、升级依赖
This commit is contained in:
parent
b39592fdcd
commit
be7cd177d1
@ -21,16 +21,16 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AlibabaCloud.SDK.Dysmsapi20170525" Version="4.0.0" />
|
<PackageReference Include="AlibabaCloud.SDK.Dysmsapi20170525" Version="4.0.1" />
|
||||||
<PackageReference Include="AlipaySDKNet.Standard" Version="4.9.809" />
|
<PackageReference Include="AlipaySDKNet.Standard" Version="4.9.809" />
|
||||||
<PackageReference Include="AngleSharp" Version="1.3.0" />
|
<PackageReference Include="AngleSharp" Version="1.3.0" />
|
||||||
<PackageReference Include="AspectCore.Extensions.Reflection" Version="2.4.0" />
|
<PackageReference Include="AspectCore.Extensions.Reflection" Version="2.4.0" />
|
||||||
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
|
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
|
||||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" Aliases="BouncyCastleV2" />
|
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" Aliases="BouncyCastleV2" />
|
||||||
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="9.1.7" />
|
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="9.1.8" />
|
||||||
<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.7.126" />
|
<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.7.127" />
|
||||||
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.9.7.126" />
|
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.9.7.127" />
|
||||||
<PackageReference Include="Furion.Pure" Version="4.9.7.126" />
|
<PackageReference Include="Furion.Pure" Version="4.9.7.127" />
|
||||||
<PackageReference Include="Hardware.Info" Version="101.1.0" />
|
<PackageReference Include="Hardware.Info" Version="101.1.0" />
|
||||||
<PackageReference Include="Hashids.net" Version="1.7.0" />
|
<PackageReference Include="Hashids.net" Version="1.7.0" />
|
||||||
<PackageReference Include="IPTools.China" Version="1.6.0" />
|
<PackageReference Include="IPTools.China" Version="1.6.0" />
|
||||||
@ -51,9 +51,9 @@
|
|||||||
<PackageReference Include="RabbitMQ.Client" Version="7.1.2" />
|
<PackageReference Include="RabbitMQ.Client" Version="7.1.2" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp.Web" Version="3.2.0" />
|
<PackageReference Include="SixLabors.ImageSharp.Web" Version="3.2.0" />
|
||||||
<PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="3.11.0" />
|
<PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="3.11.0" />
|
||||||
<PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="3.13.0" />
|
<PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="3.14.0" />
|
||||||
<PackageReference Include="SqlSugar.MongoDbCore" Version="5.1.4.264" />
|
<PackageReference Include="SqlSugar.MongoDbCore" Version="5.1.4.264" />
|
||||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.202" />
|
<PackageReference Include="SqlSugarCore" Version="5.1.4.205" />
|
||||||
<PackageReference Include="SSH.NET" Version="2025.0.0" />
|
<PackageReference Include="SSH.NET" Version="2025.0.0" />
|
||||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.7" />
|
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.7" />
|
||||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||||
|
|||||||
@ -78,8 +78,9 @@ public sealed class RequiredIFAttribute(
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 判断是否需要进行验证
|
/// 判断是否需要进行验证
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="targetValue">依赖属性的值</param>
|
/// <param name="targetValue"></param>
|
||||||
/// <returns>是否需要验证</returns>
|
/// <param name="dictAttr"></param>
|
||||||
|
/// <returns></returns>
|
||||||
private bool ShouldValidate(object targetValue, DictAttribute dictAttr)
|
private bool ShouldValidate(object targetValue, DictAttribute dictAttr)
|
||||||
{
|
{
|
||||||
switch (Comparison)
|
switch (Comparison)
|
||||||
|
|||||||
@ -8,21 +8,41 @@ namespace Admin.NET.Core.Service;
|
|||||||
|
|
||||||
public class GenerateSQLInput
|
public class GenerateSQLInput
|
||||||
{
|
{
|
||||||
public List<ColumnsSQL> Columns { get; set; }
|
public List<SelectConfig> SelectConfigs { get; set; }
|
||||||
|
|
||||||
public List<ConditionsSQL> Conditions { get; set; }
|
public List<JoinConfig> JoinConfigs { get; set; }
|
||||||
|
|
||||||
public List<OrdersSQL> Orders { get; set; }
|
public List<WhereConfig> WhereConfigs { get; set; }
|
||||||
|
|
||||||
|
public List<OrderConfig> OrderConfigs { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ColumnsSQL
|
public class SelectConfig
|
||||||
{
|
{
|
||||||
public string Table { get; set; }
|
public string Table { get; set; }
|
||||||
|
|
||||||
public string Column { get; set; }
|
public string Column { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ConditionsSQL
|
public class JoinConfig
|
||||||
|
{
|
||||||
|
public string JoinType { get; set; }
|
||||||
|
|
||||||
|
public string LeftTable { get; set; }
|
||||||
|
|
||||||
|
public string RightTable { get; set; }
|
||||||
|
|
||||||
|
public List<JoinConfig_TableColumn> TableColumns { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class JoinConfig_TableColumn
|
||||||
|
{
|
||||||
|
public string LeftTableColumn { get; set; }
|
||||||
|
|
||||||
|
public string RightTableColumn { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WhereConfig
|
||||||
{
|
{
|
||||||
public string Table { get; set; }
|
public string Table { get; set; }
|
||||||
|
|
||||||
@ -31,9 +51,13 @@ public class ConditionsSQL
|
|||||||
public string Operator { get; set; }
|
public string Operator { get; set; }
|
||||||
|
|
||||||
public string Value { get; set; }
|
public string Value { get; set; }
|
||||||
|
|
||||||
|
public string DataType { get; set; }
|
||||||
|
|
||||||
|
public string JoinType { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OrdersSQL
|
public class OrderConfig
|
||||||
{
|
{
|
||||||
public string Table { get; set; }
|
public string Table { get; set; }
|
||||||
|
|
||||||
|
|||||||
275
Admin.NET/Admin.NET.Core/Service/DataBase/GenerateSQL.cs
Normal file
275
Admin.NET/Admin.NET.Core/Service/DataBase/GenerateSQL.cs
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||||
|
//
|
||||||
|
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||||
|
//
|
||||||
|
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||||
|
|
||||||
|
namespace Admin.NET.Core.Service;
|
||||||
|
|
||||||
|
public static class BuildSQL
|
||||||
|
{
|
||||||
|
public static string Build(GenerateSQLInput config)
|
||||||
|
{
|
||||||
|
if (config.SelectConfigs == null || !config.SelectConfigs.Any())
|
||||||
|
throw new ArgumentException("至少需要一个选择配置");
|
||||||
|
|
||||||
|
StringBuilder sql = new StringBuilder();
|
||||||
|
|
||||||
|
// SELECT clause
|
||||||
|
sql.Append("SELECT ");
|
||||||
|
sql.Append(string.Join(", ", config.SelectConfigs.Select(sc =>
|
||||||
|
$"{sc.Table}.{sc.Column}")));
|
||||||
|
sql.AppendLine();
|
||||||
|
|
||||||
|
// 确定主表(出现频率最高的表)
|
||||||
|
var mainTable = DetermineMainTable(config);
|
||||||
|
sql.AppendLine($"FROM {mainTable}");
|
||||||
|
|
||||||
|
// 收集所有涉及的表
|
||||||
|
var allTables = CollectAllTables(config);
|
||||||
|
var joinedTables = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { mainTable };
|
||||||
|
|
||||||
|
// 处理JOIN关系 - 优化后的逻辑
|
||||||
|
ProcessJoins(sql, config.JoinConfigs, joinedTables, allTables);
|
||||||
|
|
||||||
|
// 添加剩余未连接的表(使用CROSS JOIN)
|
||||||
|
AddRemainingTables(sql, allTables, joinedTables);
|
||||||
|
|
||||||
|
// WHERE子句
|
||||||
|
ProcessWhereClause(sql, config.WhereConfigs);
|
||||||
|
|
||||||
|
// ORDER BY子句
|
||||||
|
ProcessOrderByClause(sql, config.OrderConfigs);
|
||||||
|
|
||||||
|
return sql.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string DetermineMainTable(GenerateSQLInput config)
|
||||||
|
{
|
||||||
|
// 统计所有表出现的频率
|
||||||
|
var tableFrequency = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// 统计SELECT中的表
|
||||||
|
foreach (var sc in config.SelectConfigs)
|
||||||
|
{
|
||||||
|
if (tableFrequency.ContainsKey(sc.Table))
|
||||||
|
tableFrequency[sc.Table]++;
|
||||||
|
else
|
||||||
|
tableFrequency[sc.Table] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计JOIN中的表
|
||||||
|
foreach (var jc in config.JoinConfigs)
|
||||||
|
{
|
||||||
|
if (tableFrequency.ContainsKey(jc.LeftTable))
|
||||||
|
tableFrequency[jc.LeftTable]++;
|
||||||
|
else
|
||||||
|
tableFrequency[jc.LeftTable] = 1;
|
||||||
|
|
||||||
|
if (tableFrequency.ContainsKey(jc.RightTable))
|
||||||
|
tableFrequency[jc.RightTable]++;
|
||||||
|
else
|
||||||
|
tableFrequency[jc.RightTable] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计WHERE中的表
|
||||||
|
foreach (var wc in config.WhereConfigs)
|
||||||
|
{
|
||||||
|
if (tableFrequency.ContainsKey(wc.Table))
|
||||||
|
tableFrequency[wc.Table]++;
|
||||||
|
else
|
||||||
|
tableFrequency[wc.Table] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计ORDER BY中的表
|
||||||
|
foreach (var oc in config.OrderConfigs)
|
||||||
|
{
|
||||||
|
if (tableFrequency.ContainsKey(oc.Table))
|
||||||
|
tableFrequency[oc.Table]++;
|
||||||
|
else
|
||||||
|
tableFrequency[oc.Table] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择出现频率最高的表作为主表
|
||||||
|
return tableFrequency.OrderByDescending(kv => kv.Value)
|
||||||
|
.ThenBy(kv => kv.Key)
|
||||||
|
.FirstOrDefault().Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashSet<string> CollectAllTables(GenerateSQLInput config)
|
||||||
|
{
|
||||||
|
var allTables = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
config.SelectConfigs?.ForEach(sc => allTables.Add(sc.Table));
|
||||||
|
config.JoinConfigs?.ForEach(jc =>
|
||||||
|
{
|
||||||
|
allTables.Add(jc.LeftTable);
|
||||||
|
allTables.Add(jc.RightTable);
|
||||||
|
});
|
||||||
|
config.WhereConfigs?.ForEach(wc => allTables.Add(wc.Table));
|
||||||
|
config.OrderConfigs?.ForEach(oc => allTables.Add(oc.Table));
|
||||||
|
|
||||||
|
return allTables;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ProcessJoins(StringBuilder sql, List<JoinConfig> joinConfigs,
|
||||||
|
HashSet<string> joinedTables, HashSet<string> allTables)
|
||||||
|
{
|
||||||
|
if (joinConfigs == null) return;
|
||||||
|
|
||||||
|
// 使用队列处理所有JOIN关系
|
||||||
|
var joinQueue = new Queue<JoinConfig>(joinConfigs);
|
||||||
|
var processedJoins = new HashSet<JoinConfig>();
|
||||||
|
bool progressMade;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
progressMade = false;
|
||||||
|
var currentQueue = new Queue<JoinConfig>(joinQueue);
|
||||||
|
joinQueue.Clear();
|
||||||
|
|
||||||
|
while (currentQueue.Count > 0)
|
||||||
|
{
|
||||||
|
var join = currentQueue.Dequeue();
|
||||||
|
|
||||||
|
// 如果右表已经连接,跳过
|
||||||
|
if (joinedTables.Contains(join.RightTable))
|
||||||
|
{
|
||||||
|
processedJoins.Add(join);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果左表已连接,可以处理这个JOIN
|
||||||
|
if (joinedTables.Contains(join.LeftTable))
|
||||||
|
{
|
||||||
|
// 构建ON条件
|
||||||
|
var onConditions = join.TableColumns.Select(tc =>
|
||||||
|
$"{join.LeftTable}.{tc.LeftTableColumn} = " +
|
||||||
|
$"{join.RightTable}.{tc.RightTableColumn}");
|
||||||
|
|
||||||
|
sql.AppendLine($"{join.JoinType} {join.RightTable} ON {string.Join(" AND ", onConditions)}");
|
||||||
|
joinedTables.Add(join.RightTable);
|
||||||
|
processedJoins.Add(join);
|
||||||
|
progressMade = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 左表未连接,放回队列稍后处理
|
||||||
|
joinQueue.Enqueue(join);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果还有未处理的JOIN但队列未变化,尝试添加缺失的表
|
||||||
|
if (!progressMade && joinQueue.Count > 0)
|
||||||
|
{
|
||||||
|
var nextJoin = joinQueue.Peek();
|
||||||
|
|
||||||
|
// 如果左表不在已连接表中,尝试添加它
|
||||||
|
if (!joinedTables.Contains(nextJoin.LeftTable) && allTables.Contains(nextJoin.LeftTable))
|
||||||
|
{
|
||||||
|
sql.AppendLine($", {nextJoin.LeftTable}");
|
||||||
|
joinedTables.Add(nextJoin.LeftTable);
|
||||||
|
progressMade = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (progressMade && joinQueue.Count > 0);
|
||||||
|
|
||||||
|
// 处理剩余无法连接的JOIN
|
||||||
|
foreach (var join in joinQueue)
|
||||||
|
{
|
||||||
|
// 如果右表已经连接,跳过
|
||||||
|
if (joinedTables.Contains(join.RightTable)) continue;
|
||||||
|
|
||||||
|
// 尝试添加左表
|
||||||
|
if (!joinedTables.Contains(join.LeftTable))
|
||||||
|
{
|
||||||
|
sql.AppendLine($", {join.LeftTable}");
|
||||||
|
joinedTables.Add(join.LeftTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建ON条件
|
||||||
|
var onConditions = join.TableColumns.Select(tc =>
|
||||||
|
$"{join.LeftTable}.{tc.LeftTableColumn} = " +
|
||||||
|
$"{join.RightTable}.{tc.RightTableColumn}");
|
||||||
|
|
||||||
|
sql.AppendLine($"{join.JoinType} {join.RightTable} ON {string.Join(" AND ", onConditions)}");
|
||||||
|
joinedTables.Add(join.RightTable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddRemainingTables(StringBuilder sql, HashSet<string> allTables, HashSet<string> joinedTables)
|
||||||
|
{
|
||||||
|
var remainingTables = allTables.Except(joinedTables, StringComparer.OrdinalIgnoreCase).ToList();
|
||||||
|
if (!remainingTables.Any()) return;
|
||||||
|
|
||||||
|
foreach (var table in remainingTables)
|
||||||
|
{
|
||||||
|
sql.AppendLine($", {table}");
|
||||||
|
joinedTables.Add(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ProcessWhereClause(StringBuilder sql, List<WhereConfig> whereConfigs)
|
||||||
|
{
|
||||||
|
if (whereConfigs == null || !whereConfigs.Any()) return;
|
||||||
|
|
||||||
|
sql.Append("WHERE ");
|
||||||
|
foreach (var where in whereConfigs)
|
||||||
|
{
|
||||||
|
string formattedValue = FormatValue(where.Value, where.DataType, where.Operator);
|
||||||
|
sql.Append($"{where.Table}.{where.Column} " + $"{where.Operator} {formattedValue} {where.JoinType} ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除最后一个逻辑运算符
|
||||||
|
sql.Length -= 4;
|
||||||
|
sql.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ProcessOrderByClause(StringBuilder sql, List<OrderConfig> orderConfigs)
|
||||||
|
{
|
||||||
|
if (orderConfigs == null || !orderConfigs.Any()) return;
|
||||||
|
|
||||||
|
sql.Append("ORDER BY ");
|
||||||
|
sql.Append(string.Join(", ", orderConfigs.OrderBy(oc => oc.Sort).Select(oc => $"{oc.Table}.{oc.Column}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// WHERE 数值格式化
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="dataType"></param>
|
||||||
|
/// <param name="operatorType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static string FormatValue(object value, string dataType, string operatorType)
|
||||||
|
{
|
||||||
|
if (value == null) return "NULL";
|
||||||
|
|
||||||
|
string valueStr = value.ToString();
|
||||||
|
if (string.IsNullOrWhiteSpace(valueStr)) return "NULL";
|
||||||
|
|
||||||
|
// 根据数据类型和操作符格式化值
|
||||||
|
if (dataType?.ToLower().Contains("varchar") == true ||
|
||||||
|
dataType?.ToLower().Contains("text") == true ||
|
||||||
|
dataType?.ToLower().Contains("bool") == true ||
|
||||||
|
dataType?.ToLower().Contains("datetime") == true ||
|
||||||
|
dataType?.ToLower().Contains("timestamp") == true ||
|
||||||
|
value is string)
|
||||||
|
{
|
||||||
|
// 对于LIKE操作符,添加通配符
|
||||||
|
if (operatorType?.ToUpper() == "LIKE")
|
||||||
|
{
|
||||||
|
return $"'%{valueStr}%'";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"'{valueStr}'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 数字类型直接返回
|
||||||
|
return valueStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -631,54 +631,7 @@ public class SysDatabaseService : IDynamicApiController, ITransient
|
|||||||
[DisplayName("生成SQL语句")]
|
[DisplayName("生成SQL语句")]
|
||||||
public async Task<string> GenerateSQL(GenerateSQLInput input)
|
public async Task<string> GenerateSQL(GenerateSQLInput input)
|
||||||
{
|
{
|
||||||
if (input.Columns == null || input.Columns.Count <= 0) throw Oops.Oh("至少需要设置一个列");
|
return await Task.FromResult(BuildSQL.Build(input));
|
||||||
|
|
||||||
// 收集所有表名(从columns、conditions、orders中)
|
|
||||||
var tableNames = new HashSet<string>();
|
|
||||||
foreach (var col in input.Columns)
|
|
||||||
tableNames.Add(col.Table);
|
|
||||||
foreach (var cond in input.Conditions)
|
|
||||||
tableNames.Add(cond.Table);
|
|
||||||
foreach (var ord in input.Orders)
|
|
||||||
tableNames.Add(ord.Table);
|
|
||||||
if (tableNames == null || tableNames.Count == 0)
|
|
||||||
throw Oops.Oh("至少需要一个表");
|
|
||||||
|
|
||||||
// 构建 SELECT 子句
|
|
||||||
var selectItems = input.Columns.Select(u => $"{u.Table}.{u.Column}");
|
|
||||||
var selectString = "SELECT " + string.Join(", ", selectItems);
|
|
||||||
|
|
||||||
// 构建 FROM 子句
|
|
||||||
var fromString = "FROM " + string.Join(", ", tableNames);
|
|
||||||
|
|
||||||
// 组合SQL语句
|
|
||||||
var sql = new StringBuilder();
|
|
||||||
sql.Append(selectString);
|
|
||||||
sql.Append(" ");
|
|
||||||
sql.Append(fromString);
|
|
||||||
if (input.Conditions != null && input.Conditions.Count > 0)
|
|
||||||
{
|
|
||||||
// 构建WHERE子句
|
|
||||||
var whereConditions = input.Conditions.Where(u => !string.IsNullOrWhiteSpace(u.Table) && !string.IsNullOrWhiteSpace(u.Column)).Select(u =>
|
|
||||||
{
|
|
||||||
var valueStr = u.Value is string ? $"'{u.Value}'" : u.Value.ToString(); // 处理值:如果是字符串则需要加单引号
|
|
||||||
return $"{u.Table}.{u.Column} {u.Operator} {valueStr}";
|
|
||||||
});
|
|
||||||
var whereString = "WHERE " + string.Join(" AND ", whereConditions);
|
|
||||||
|
|
||||||
sql.Append(" ");
|
|
||||||
sql.Append(whereString);
|
|
||||||
}
|
|
||||||
if (input.Orders != null && input.Orders.Count > 0)
|
|
||||||
{
|
|
||||||
// 构建 ORDER BY 子句
|
|
||||||
var orderItems = input.Orders.OrderBy(u => u.Sort).Where(u => !string.IsNullOrWhiteSpace(u.Table) && !string.IsNullOrWhiteSpace(u.Column)).Select(u => $"{u.Table}.{u.Column} ASC"); // 按照sort值从小到大排序,确定列的先后顺序
|
|
||||||
var orderByString = "ORDER BY " + string.Join(", ", orderItems);
|
|
||||||
|
|
||||||
sql.Append(" ");
|
|
||||||
sql.Append(orderByString);
|
|
||||||
}
|
|
||||||
return await Task.FromResult(sql.ToString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -690,7 +643,10 @@ public class SysDatabaseService : IDynamicApiController, ITransient
|
|||||||
public async Task<DataTable> ExecuteSQL(GenerateSQLInput input)
|
public async Task<DataTable> ExecuteSQL(GenerateSQLInput input)
|
||||||
{
|
{
|
||||||
var sql = await GenerateSQL(input);
|
var sql = await GenerateSQL(input);
|
||||||
var dt = _db.Ado.GetDataTable(sql);
|
if (Regex.IsMatch(sql, @"\b(delete|update)\b", RegexOptions.IgnoreCase))
|
||||||
|
throw Oops.Oh("非法SQL语句,已停止执行");
|
||||||
|
|
||||||
|
var dt = await _db.Ado.GetDataTableAsync(sql);
|
||||||
return dt;
|
return dt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -14,8 +14,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
<PackageReference Include="Furion.Xunit" Version="4.9.7.126" />
|
<PackageReference Include="Furion.Xunit" Version="4.9.7.127" />
|
||||||
<PackageReference Include="Furion.Pure" Version="4.9.7.126">
|
<PackageReference Include="Furion.Pure" Version="4.9.7.127">
|
||||||
<ExcludeAssets>compile</ExcludeAssets>
|
<ExcludeAssets>compile</ExcludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="xunit.assert" Version="2.9.3" />
|
<PackageReference Include="xunit.assert" Version="2.9.3" />
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.14.36511.14 d17.14
|
VisualStudioVersion = 17.14.36511.14
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.NET.Application", "Admin.NET.Application\Admin.NET.Application.csproj", "{C3F5AEC5-ACEE-4109-94E3-3F981DC18268}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.NET.Application", "Admin.NET.Application\Admin.NET.Application.csproj", "{C3F5AEC5-ACEE-4109-94E3-3F981DC18268}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "admin.net.pro",
|
"name": "admin.net.pro",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.4.33",
|
"version": "2.4.33",
|
||||||
"lastBuildTime": "2025.09.23",
|
"lastBuildTime": "2025.09.28",
|
||||||
"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",
|
"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",
|
||||||
"author": "zuohuaijun",
|
"author": "zuohuaijun",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -21,7 +21,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.2",
|
"@element-plus/icons-vue": "^2.3.2",
|
||||||
"@logicflow/core": "^2.1.2",
|
"@logicflow/core": "^2.1.2",
|
||||||
"@logicflow/extension": "^2.1.3",
|
"@logicflow/extension": "^2.1.4",
|
||||||
"@microsoft/signalr": "^9.0.6",
|
"@microsoft/signalr": "^9.0.6",
|
||||||
"@vue-office/docx": "^1.6.3",
|
"@vue-office/docx": "^1.6.3",
|
||||||
"@vue-office/excel": "^1.7.14",
|
"@vue-office/excel": "^1.7.14",
|
||||||
@ -39,7 +39,7 @@
|
|||||||
"cropperjs": "^1.6.2",
|
"cropperjs": "^1.6.2",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"echarts": "^6.0.0",
|
"echarts": "^6.0.0",
|
||||||
"element-plus": "^2.11.3",
|
"element-plus": "^2.11.4",
|
||||||
"exceljs": "^4.4.0",
|
"exceljs": "^4.4.0",
|
||||||
"flag-icons": "^7.5.0",
|
"flag-icons": "^7.5.0",
|
||||||
"franc": "^6.2.0",
|
"franc": "^6.2.0",
|
||||||
@ -69,7 +69,7 @@
|
|||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
"vcrontab-3": "^3.3.22",
|
"vcrontab-3": "^3.3.22",
|
||||||
"vform3-builds": "^3.0.10",
|
"vform3-builds": "^3.0.10",
|
||||||
"vue": "^3.5.21",
|
"vue": "^3.5.22",
|
||||||
"vue-clipboard3": "^2.0.0",
|
"vue-clipboard3": "^2.0.0",
|
||||||
"vue-demi": "0.14.10",
|
"vue-demi": "0.14.10",
|
||||||
"vue-draggable-plus": "^0.6.0",
|
"vue-draggable-plus": "^0.6.0",
|
||||||
@ -81,8 +81,8 @@
|
|||||||
"vue-router": "^4.5.1",
|
"vue-router": "^4.5.1",
|
||||||
"vue-signature-pad": "^3.0.2",
|
"vue-signature-pad": "^3.0.2",
|
||||||
"vue3-tree-org": "^4.2.2",
|
"vue3-tree-org": "^4.2.2",
|
||||||
"vxe-pc-ui": "^4.9.34",
|
"vxe-pc-ui": "^4.9.38",
|
||||||
"vxe-table": "^4.16.18",
|
"vxe-table": "^4.16.20",
|
||||||
"xe-utils": "^3.7.9",
|
"xe-utils": "^3.7.9",
|
||||||
"xlsx-js-style": "^1.2.0"
|
"xlsx-js-style": "^1.2.0"
|
||||||
},
|
},
|
||||||
@ -97,7 +97,7 @@
|
|||||||
"@typescript-eslint/parser": "^8.44.1",
|
"@typescript-eslint/parser": "^8.44.1",
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"@vitejs/plugin-vue-jsx": "^5.1.1",
|
"@vitejs/plugin-vue-jsx": "^5.1.1",
|
||||||
"@vue/compiler-sfc": "^3.5.21",
|
"@vue/compiler-sfc": "^3.5.22",
|
||||||
"cli-progress": "^3.12.0",
|
"cli-progress": "^3.12.0",
|
||||||
"code-inspector-plugin": "^1.2.10",
|
"code-inspector-plugin": "^1.2.10",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
@ -108,7 +108,7 @@
|
|||||||
"less": "^4.4.1",
|
"less": "^4.4.1",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"rollup-plugin-visualizer": "^6.0.3",
|
"rollup-plugin-visualizer": "^6.0.3",
|
||||||
"sass": "^1.93.1",
|
"sass": "^1.93.2",
|
||||||
"terser": "^5.44.0",
|
"terser": "^5.44.0",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
"vite": "^7.1.7",
|
"vite": "^7.1.7",
|
||||||
|
|||||||
@ -12,9 +12,10 @@
|
|||||||
* Do not edit the class manually.
|
* Do not edit the class manually.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ColumnsSQL } from './columns-sql';
|
import { JoinConfig } from './join-config';
|
||||||
import { ConditionsSQL } from './conditions-sql';
|
import { OrderConfig } from './order-config';
|
||||||
import { OrdersSQL } from './orders-sql';
|
import { SelectConfig } from './select-config';
|
||||||
|
import { WhereConfig } from './where-config';
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
@ -24,20 +25,26 @@ import { OrdersSQL } from './orders-sql';
|
|||||||
export interface GenerateSQLInput {
|
export interface GenerateSQLInput {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Array<ColumnsSQL>}
|
* @type {Array<SelectConfig>}
|
||||||
* @memberof GenerateSQLInput
|
* @memberof GenerateSQLInput
|
||||||
*/
|
*/
|
||||||
columns?: Array<ColumnsSQL> | null;
|
selectConfigs?: Array<SelectConfig> | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Array<ConditionsSQL>}
|
* @type {Array<JoinConfig>}
|
||||||
* @memberof GenerateSQLInput
|
* @memberof GenerateSQLInput
|
||||||
*/
|
*/
|
||||||
conditions?: Array<ConditionsSQL> | null;
|
joinConfigs?: Array<JoinConfig> | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Array<OrdersSQL>}
|
* @type {Array<WhereConfig>}
|
||||||
* @memberof GenerateSQLInput
|
* @memberof GenerateSQLInput
|
||||||
*/
|
*/
|
||||||
orders?: Array<OrdersSQL> | null;
|
whereConfigs?: Array<WhereConfig> | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<OrderConfig>}
|
||||||
|
* @memberof GenerateSQLInput
|
||||||
|
*/
|
||||||
|
orderConfigs?: Array<OrderConfig> | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -188,9 +188,7 @@ export * from './code-gen-scene-enum';
|
|||||||
export * from './column-custom-output';
|
export * from './column-custom-output';
|
||||||
export * from './column-output';
|
export * from './column-output';
|
||||||
export * from './column-relation';
|
export * from './column-relation';
|
||||||
export * from './columns-sql';
|
|
||||||
export * from './compare-info';
|
export * from './compare-info';
|
||||||
export * from './conditions-sql';
|
|
||||||
export * from './const-output';
|
export * from './const-output';
|
||||||
export * from './constructor-info';
|
export * from './constructor-info';
|
||||||
export * from './copy-role-input';
|
export * from './copy-role-input';
|
||||||
@ -308,6 +306,8 @@ export * from './job-create-type-enum';
|
|||||||
export * from './job-detail-input';
|
export * from './job-detail-input';
|
||||||
export * from './job-detail-output';
|
export * from './job-detail-output';
|
||||||
export * from './job-trigger-input';
|
export * from './job-trigger-input';
|
||||||
|
export * from './join-config';
|
||||||
|
export * from './join-config-table-column';
|
||||||
export * from './key-value-pair-string-string';
|
export * from './key-value-pair-string-string';
|
||||||
export * from './llmmodel-input';
|
export * from './llmmodel-input';
|
||||||
export * from './layout-kind';
|
export * from './layout-kind';
|
||||||
@ -355,7 +355,7 @@ export * from './oauth-user-output';
|
|||||||
export * from './online-user';
|
export * from './online-user';
|
||||||
export * from './open-access-output';
|
export * from './open-access-output';
|
||||||
export * from './order-by-type';
|
export * from './order-by-type';
|
||||||
export * from './orders-sql';
|
export * from './order-config';
|
||||||
export * from './page-code-gen-input';
|
export * from './page-code-gen-input';
|
||||||
export * from './page-config-input';
|
export * from './page-config-input';
|
||||||
export * from './page-dict-data-input';
|
export * from './page-dict-data-input';
|
||||||
@ -427,6 +427,7 @@ export * from './schema-serialization-mode';
|
|||||||
export * from './search';
|
export * from './search';
|
||||||
export * from './security-rule-set';
|
export * from './security-rule-set';
|
||||||
export * from './seed-type';
|
export * from './seed-type';
|
||||||
|
export * from './select-config';
|
||||||
export * from './select-list-group';
|
export * from './select-list-group';
|
||||||
export * from './select-list-item';
|
export * from './select-list-item';
|
||||||
export * from './send-subscribe-message-input';
|
export * from './send-subscribe-message-input';
|
||||||
@ -579,6 +580,7 @@ export * from './wechat-pay-para-input';
|
|||||||
export * from './wechat-pay-para-output';
|
export * from './wechat-pay-para-output';
|
||||||
export * from './wechat-pay-transaction-input';
|
export * from './wechat-pay-transaction-input';
|
||||||
export * from './wechat-user-login';
|
export * from './wechat-user-login';
|
||||||
|
export * from './where-config';
|
||||||
export * from './wx-open-id-login-input';
|
export * from './wx-open-id-login-input';
|
||||||
export * from './wx-open-id-output';
|
export * from './wx-open-id-output';
|
||||||
export * from './wx-phone-output';
|
export * from './wx-phone-output';
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* Admin.NET 通用权限开发平台
|
||||||
|
* 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。<br/><u><b><font color='FF0000'> 👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!</font></b></u>
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 1.0.0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by the swagger code generator program.
|
||||||
|
* https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface JoinConfigTableColumn
|
||||||
|
*/
|
||||||
|
export interface JoinConfigTableColumn {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string}
|
||||||
|
* @memberof JoinConfigTableColumn
|
||||||
|
*/
|
||||||
|
leftTableColumn?: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string}
|
||||||
|
* @memberof JoinConfigTableColumn
|
||||||
|
*/
|
||||||
|
rightTableColumn?: string | null;
|
||||||
|
}
|
||||||
47
Web/src/api-services/system/models/join-config.ts
Normal file
47
Web/src/api-services/system/models/join-config.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* Admin.NET 通用权限开发平台
|
||||||
|
* 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。<br/><u><b><font color='FF0000'> 👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!</font></b></u>
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 1.0.0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by the swagger code generator program.
|
||||||
|
* https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { JoinConfigTableColumn } from './join-config-table-column';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface JoinConfig
|
||||||
|
*/
|
||||||
|
export interface JoinConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string}
|
||||||
|
* @memberof JoinConfig
|
||||||
|
*/
|
||||||
|
joinType?: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string}
|
||||||
|
* @memberof JoinConfig
|
||||||
|
*/
|
||||||
|
leftTable?: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string}
|
||||||
|
* @memberof JoinConfig
|
||||||
|
*/
|
||||||
|
rightTable?: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<JoinConfigTableColumn>}
|
||||||
|
* @memberof JoinConfig
|
||||||
|
*/
|
||||||
|
tableColumns?: Array<JoinConfigTableColumn> | null;
|
||||||
|
}
|
||||||
@ -16,25 +16,25 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
* @interface OrdersSQL
|
* @interface OrderConfig
|
||||||
*/
|
*/
|
||||||
export interface OrdersSQL {
|
export interface OrderConfig {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof OrdersSQL
|
* @memberof OrderConfig
|
||||||
*/
|
*/
|
||||||
table?: string | null;
|
table?: string | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof OrdersSQL
|
* @memberof OrderConfig
|
||||||
*/
|
*/
|
||||||
column?: string | null;
|
column?: string | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {number}
|
* @type {number}
|
||||||
* @memberof OrdersSQL
|
* @memberof OrderConfig
|
||||||
*/
|
*/
|
||||||
sort?: number;
|
sort?: number;
|
||||||
}
|
}
|
||||||
@ -16,19 +16,19 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
* @interface ColumnsSQL
|
* @interface SelectConfig
|
||||||
*/
|
*/
|
||||||
export interface ColumnsSQL {
|
export interface SelectConfig {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof ColumnsSQL
|
* @memberof SelectConfig
|
||||||
*/
|
*/
|
||||||
table?: string | null;
|
table?: string | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof ColumnsSQL
|
* @memberof SelectConfig
|
||||||
*/
|
*/
|
||||||
column?: string | null;
|
column?: string | null;
|
||||||
}
|
}
|
||||||
@ -16,31 +16,43 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
* @interface ConditionsSQL
|
* @interface WhereConfig
|
||||||
*/
|
*/
|
||||||
export interface ConditionsSQL {
|
export interface WhereConfig {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof ConditionsSQL
|
* @memberof WhereConfig
|
||||||
*/
|
*/
|
||||||
table?: string | null;
|
table?: string | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof ConditionsSQL
|
* @memberof WhereConfig
|
||||||
*/
|
*/
|
||||||
column?: string | null;
|
column?: string | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof ConditionsSQL
|
* @memberof WhereConfig
|
||||||
*/
|
*/
|
||||||
operator?: string | null;
|
operator?: string | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof ConditionsSQL
|
* @memberof WhereConfig
|
||||||
*/
|
*/
|
||||||
value?: string | null;
|
value?: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string}
|
||||||
|
* @memberof WhereConfig
|
||||||
|
*/
|
||||||
|
dataType?: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string}
|
||||||
|
* @memberof WhereConfig
|
||||||
|
*/
|
||||||
|
joinType?: string | null;
|
||||||
}
|
}
|
||||||
@ -1,182 +1,351 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="reportCustomQuery-container">
|
<div class="reportCustomQuery-container">
|
||||||
<el-card shadow="hover" style="margin-bottom: 10px">
|
<splitpanes class="default-theme" horizontal>
|
||||||
<!-- <template #header>
|
<pane size="40">
|
||||||
<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-Setting /> </el-icon>
|
<el-card shadow="hover">
|
||||||
<span> 条件配置 </span>
|
<el-row :gutter="10" style="text-align: center">
|
||||||
</template> -->
|
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||||
|
<el-divider>
|
||||||
|
显示列表配置
|
||||||
|
<span style="margin-left: 15px">
|
||||||
|
<el-button type="primary" icon="ele-Plus" circle @click="addDbConfig"></el-button>
|
||||||
|
<el-button type="danger" icon="ele-Delete" circle @click="clearDbConfig"></el-button>
|
||||||
|
</span>
|
||||||
|
</el-divider>
|
||||||
|
<div class="config-container">
|
||||||
|
<div v-for="(selectConfig, index) in state.dbConfigs" :key="index" class="config-item-db">
|
||||||
|
<div class="config-item-left" style="flex: 1">
|
||||||
|
<span>表名</span>
|
||||||
|
<el-select v-model="selectConfig.selectedTables" @change="(val: string) => onTableChange(val, selectConfig)">
|
||||||
|
<el-option v-for="item in state.dbTables" :key="item.name" :label="`${item.description ? item.description : ''}[${item.name}]`" :value="item.name" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="config-item-right" style="flex: 1">
|
||||||
|
<span>列名</span>
|
||||||
|
<el-select v-model="selectConfig.selectedColumns" multiple filterable @change="(val: string[]) => onColumnChange(val, selectConfig)">
|
||||||
|
<el-option
|
||||||
|
v-for="column in selectConfig.dbColumns"
|
||||||
|
:key="column.dbColumnName"
|
||||||
|
:label="`${column.tableDescription ? column.tableDescription : ''}.${column.columnDescription ? column.columnDescription : ''}[${column.tableName}.${column.dbColumnName}]`"
|
||||||
|
:value="`${column.tableName}.${column.dbColumnName}`"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<!-- 删除按钮 -->
|
||||||
|
<div class="config-item-button">
|
||||||
|
<el-button type="danger" icon="ele-CloseBold" plain circle @click="removeDbConfig(index)"></el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
<el-divider>数据显示列配置</el-divider>
|
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||||
<el-row :gutter="10">
|
<el-row :gutter="10" style="text-align: center">
|
||||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
<!-- 连接关系配置 -->
|
||||||
<el-form-item label="表名">
|
<el-divider>
|
||||||
<el-select v-model="state.selectedTables" multiple filterable @change="onTableChange">
|
连接关系配置
|
||||||
<el-option v-for="item in state.dbTables" :key="item.name" :label="`${item.description ? item.description : ''}[${item.name}]`" :value="item.name" />
|
<span style="margin-left: 15px">
|
||||||
</el-select>
|
<el-button type="primary" icon="ele-Plus" circle @click="addJoinConfig"></el-button>
|
||||||
</el-form-item>
|
<el-button type="danger" icon="ele-Delete" circle @click="clearJoinConfig"></el-button>
|
||||||
</el-col>
|
</span>
|
||||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
</el-divider>
|
||||||
<el-form-item label="列名">
|
<div class="config-container">
|
||||||
<el-select v-model="state.selectedColumns" multiple filterable @change="onColumnsChange">
|
<div v-for="(joinConfig, index) in state.joinConfigs" :key="index" class="config-item-join">
|
||||||
<el-option
|
<div class="config-item-left">
|
||||||
v-for="column in state.dbColumns"
|
<!-- 连接关系选择项 -->
|
||||||
:key="column.dbColumnName"
|
<el-select v-model="joinConfig.joinType" style="width: 100px">
|
||||||
:label="`${column.tableDescription ? column.tableDescription : ''}.${column.columnDescription ? column.columnDescription : ''}[${column.tableName}.${column.dbColumnName}]`"
|
<el-option label="左连接" value="LEFT JOIN" />
|
||||||
:value="`${column.tableName}.${column.dbColumnName}`"
|
<el-option label="右连接" value="RIGHT JOIN" />
|
||||||
/>
|
<el-option label="内连接" value="INNER JOIN" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
<!-- 左表选择项 -->
|
||||||
</el-col>
|
<el-select v-model="joinConfig.leftTable" style="flex: 1; margin-left: 10px">
|
||||||
</el-row>
|
<el-option v-for="item in state.dbTables" :key="item.name" :label="`${item.description ? item.description : ''}[${item.name}]`" :value="item.name" />
|
||||||
|
</el-select>
|
||||||
|
<!-- 右表选择项 -->
|
||||||
|
<el-select v-model="joinConfig.rightTable" style="flex: 1; margin-left: 10px">
|
||||||
|
<el-option v-for="item in state.dbTables" :key="item.name" :label="`${item.description ? item.description : ''}[${item.name}]`" :value="item.name" />
|
||||||
|
</el-select>
|
||||||
|
<!-- 操作按钮:增加和删除 -->
|
||||||
|
<el-button-group style="margin-left: 10px">
|
||||||
|
<el-button type="primary" icon="ele-Plus" @click="addJoinTable(index)"></el-button>
|
||||||
|
<el-button type="danger" icon="ele-Close" @click="delJoinTable(index)"></el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</div>
|
||||||
|
<div class="config-item-right" v-if="joinConfig.tableColumns.length > 0">
|
||||||
|
<div v-for="(item, kindex) in joinConfig.tableColumns" :key="kindex" class="config-item-right-item">
|
||||||
|
<!-- 左表字段下拉框 -->
|
||||||
|
<el-select v-model="item.leftTableColumn" @visible-change="(visible: boolean) => visible && getJoinTableColumns(joinConfig.leftTable, true, index)">
|
||||||
|
<el-option
|
||||||
|
v-for="column in joinConfig.leftTableColumns"
|
||||||
|
:key="column.dbColumnName"
|
||||||
|
:label="`${column.columnDescription ? column.columnDescription : ''}[${column.tableName}.${column.dbColumnName}]`"
|
||||||
|
:value="`${column.dbColumnName}`"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<!-- 右表字段下拉框 -->
|
||||||
|
<el-select
|
||||||
|
v-model="item.rightTableColumn"
|
||||||
|
style="margin-left: 10px"
|
||||||
|
@visible-change="(visible: boolean) => visible && getJoinTableColumns(joinConfig.rightTable, false, index)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="column in joinConfig.rightTableColumns"
|
||||||
|
:key="column.dbColumnName"
|
||||||
|
:label="`${column.columnDescription ? column.columnDescription : ''}[${column.tableName}.${column.dbColumnName}]`"
|
||||||
|
:value="`${column.dbColumnName}`"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-button type="danger" icon="ele-CloseBold" plain circle @click="removeJoinColumn(index, kindex)" style="margin-left: 10px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-row>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
<el-row :gutter="10" style="text-align: center">
|
<el-row :gutter="10" style="text-align: center">
|
||||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||||
<!-- 查询条件配置 -->
|
<!-- 查询条件配置 -->
|
||||||
<el-divider>
|
<el-divider>
|
||||||
查询条件配置
|
查询条件配置
|
||||||
<el-button type="primary" icon="ele-Plus" plain circle @click="addCondition"></el-button>
|
<span style="margin-left: 15px">
|
||||||
<el-button type="danger" icon="ele-Delete" plain circle @click="clearCondition"></el-button>
|
<el-button type="primary" icon="ele-Plus" circle @click="addWhereConfig"></el-button>
|
||||||
</el-divider>
|
<el-button type="danger" icon="ele-Delete" circle @click="clearWhereConfig"></el-button>
|
||||||
<div class="conditions-container">
|
</span>
|
||||||
<div v-for="(condition, index) in state.queryConditions" :key="index" class="condition-item">
|
</el-divider>
|
||||||
<!-- 表选择项 -->
|
<div class="config-container">
|
||||||
<el-select v-model="condition.table" @change="changeConditionTable(condition)" style="width: 400px">
|
<div v-for="(condition, index) in state.whereConfigs" :key="index" class="config-item">
|
||||||
<el-option v-for="item in state.dbTables" :key="item.name" :label="`${item.description ? item.description : ''}[${item.name}]`" :value="item.name" />
|
<!-- 逻辑操作符选择项 (隐藏第一个条件的逻辑操作符) -->
|
||||||
</el-select>
|
<el-select v-model="condition.joinType" style="width: 200px">
|
||||||
<!-- 列选择项 -->
|
<el-option label="且" value="AND" />
|
||||||
<el-select v-model="condition.column" style="width: 400px" @change="changeConditionColumn(condition)">
|
<el-option label="或" value="OR" />
|
||||||
<el-option
|
</el-select>
|
||||||
v-for="column in condition.columns"
|
<!-- 表选择项 -->
|
||||||
:key="column.dbColumnName"
|
<el-select v-model="condition.table" @change="changeWhereTable(condition)" style="width: 400px">
|
||||||
:label="`${column.columnDescription ? column.columnDescription : ''}[${column.dbColumnName}]`"
|
<el-option v-for="item in state.dbTables" :key="item.name" :label="`${item.description ? item.description : ''}[${item.name}]`" :value="item.name" />
|
||||||
:value="`${column.tableName}.${column.dbColumnName}`"
|
</el-select>
|
||||||
/>
|
<!-- 列选择项 -->
|
||||||
</el-select>
|
<el-select v-model="condition.column" style="width: 400px" @change="changeWhereColumn(condition)">
|
||||||
<!-- 操作符选择项 -->
|
<el-option
|
||||||
<el-select v-model="condition.operator" style="width: 250px">
|
v-for="column in condition.columns"
|
||||||
<el-option label="等于" value="=" />
|
:key="column.dbColumnName"
|
||||||
<el-option label="不等于" value="!=" />
|
:label="`${column.columnDescription ? column.columnDescription : ''}[${column.dbColumnName}]`"
|
||||||
<el-option label="大于" value=">" />
|
:value="`${column.tableName}.${column.dbColumnName}`"
|
||||||
<el-option label="小于" value="<" />
|
/>
|
||||||
<el-option label="大于等于" value=">=" />
|
</el-select>
|
||||||
<el-option label="小于等于" value="<=" />
|
<!-- 操作符选择项 -->
|
||||||
<el-option label="包含" value="LIKE" />
|
<el-select v-model="condition.operator" style="width: 250px">
|
||||||
</el-select>
|
<el-option label="等于" value="=" />
|
||||||
<!-- 值填写项 -->
|
<el-option label="不等于" value="!=" />
|
||||||
<el-input-number v-if="condition.dataType == 'bigint' || condition.dataType == 'int8'" v-model="condition.value" controls-position="right" class="w100" />
|
<el-option label="大于" value=">" />
|
||||||
<el-date-picker v-else-if="condition.dataType == 'datetime' || condition.dataType == 'timestamp'" v-model="condition.value" type="datetime" class="w100" />
|
<el-option label="小于" value="<" />
|
||||||
<el-input v-else v-model="condition.value" />
|
<el-option label="大于等于" value=">=" />
|
||||||
<!-- 删除按钮 -->
|
<el-option label="小于等于" value="<=" />
|
||||||
<el-button type="danger" icon="ele-CloseBold" plain circle @click="removeCondition(index)"></el-button>
|
<el-option label="包含" value="LIKE" />
|
||||||
</div>
|
</el-select>
|
||||||
</div>
|
<!-- 值填写项 -->
|
||||||
</el-col>
|
<el-input-number v-if="condition.dataType == 'bigint' || condition.dataType == 'int8'" v-model="condition.value" controls-position="right" class="w100" />
|
||||||
|
<el-date-picker
|
||||||
|
v-else-if="condition.dataType == 'datetime' || condition.dataType == 'timestamp'"
|
||||||
|
v-model="condition.value"
|
||||||
|
type="datetime"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
class="w100"
|
||||||
|
/>
|
||||||
|
<el-input v-else v-model="condition.value" />
|
||||||
|
<!-- 删除按钮 -->
|
||||||
|
<el-button type="danger" icon="ele-CloseBold" plain circle @click="removeWhereConfig(index)"></el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||||
<!-- 数据排序配置 -->
|
<!-- 数据排序配置 -->
|
||||||
<el-divider>
|
<el-divider>
|
||||||
数据排序配置
|
数据排序配置
|
||||||
<el-button type="primary" icon="ele-Plus" plain circle @click="addSortCondition"></el-button>
|
<span style="margin-left: 15px">
|
||||||
<el-button type="danger" icon="ele-Delete" plain circle @click="clearSortCondition"></el-button>
|
<el-button type="primary" icon="ele-Plus" circle @click="addSortConfig"></el-button>
|
||||||
</el-divider>
|
<el-button type="danger" icon="ele-Delete" circle @click="clearSortConfig"></el-button>
|
||||||
<div class="conditions-container">
|
</span>
|
||||||
<div v-for="(sortCondition, index) in state.sortConditions" :key="index" class="condition-item">
|
</el-divider>
|
||||||
<!-- 表选择项 -->
|
<div class="config-container">
|
||||||
<el-select v-model="sortCondition.table" @change="changeConditionTable(sortCondition)">
|
<div v-for="(sortCondition, index) in state.orderConfigs" :key="index" class="config-item">
|
||||||
<el-option v-for="item in state.dbTables" :key="item.name" :label="`${item.description ? item.description : ''}[${item.name}]`" :value="item.name" />
|
<!-- 表选择项 -->
|
||||||
</el-select>
|
<el-select v-model="sortCondition.table" @change="changeWhereTable(sortCondition)">
|
||||||
<!-- 列选择项 -->
|
<el-option v-for="item in state.dbTables" :key="item.name" :label="`${item.description ? item.description : ''}[${item.name}]`" :value="item.name" />
|
||||||
<el-select v-model="sortCondition.column">
|
</el-select>
|
||||||
<el-option
|
<!-- 列选择项 -->
|
||||||
v-for="column in sortCondition.columns"
|
<el-select v-model="sortCondition.column">
|
||||||
:key="column.dbColumnName"
|
<el-option
|
||||||
:label="`${column.columnDescription ? column.columnDescription : ''}[${column.dbColumnName}]`"
|
v-for="column in sortCondition.columns"
|
||||||
:value="`${column.tableName}.${column.dbColumnName}`"
|
:key="column.dbColumnName"
|
||||||
/>
|
:label="`${column.columnDescription ? column.columnDescription : ''}[${column.dbColumnName}]`"
|
||||||
</el-select>
|
:value="`${column.tableName}.${column.dbColumnName}`"
|
||||||
<!-- 排序序号 -->
|
/>
|
||||||
<el-input-number v-model="sortCondition.sortOrder" style="width: 180px" controls-position="right" />
|
</el-select>
|
||||||
<!-- 删除按钮 -->
|
<!-- 排序序号 -->
|
||||||
<el-button type="danger" icon="ele-CloseBold" plain circle @click="removeSortCondition(index)"></el-button>
|
<el-input-number v-model="sortCondition.sort" style="width: 180px" controls-position="right" />
|
||||||
</div>
|
<!-- 删除按钮 -->
|
||||||
</div>
|
<el-button type="danger" icon="ele-CloseBold" plain circle @click="removeSortConfig(index)"></el-button>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
<el-row :gutter="10" style="text-align: center">
|
<el-row :gutter="10" style="text-align: center">
|
||||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
|
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
|
||||||
<el-button icon="ele-View" type="primary" plain @click="generateSQL">预览SQL</el-button>
|
<el-button icon="ele-View" type="primary" plain @click="generateSQL">预览SQL</el-button>
|
||||||
<el-button icon="ele-Position" type="primary" @click="executeSQL">执行查询</el-button>
|
<el-button icon="ele-Position" type="primary" @click="executeSQL">执行查询</el-button>
|
||||||
<el-button icon="ele-FolderOpened" @click="openSchema">打开方案</el-button>
|
<el-button icon="ele-FolderOpened" @click="openSchema">打开方案</el-button>
|
||||||
<el-button icon="ele-DocumentCopy" @click="saveSchema">保存方案</el-button>
|
<el-button icon="ele-DocumentCopy" @click="saveSchema">保存方案</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<!-- SQL语句预览 -->
|
<!-- SQL语句预览 -->
|
||||||
<el-row :gutter="10">
|
<el-row :gutter="10">
|
||||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="">
|
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="">
|
||||||
<el-input v-model="state.sqlString" type="textarea" :rows="5" disabled style="color: red" />
|
<el-input v-model="state.sqlString" type="textarea" :rows="5" disabled style="color: red" />
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
</pane>
|
||||||
|
|
||||||
<!-- 查询结果展示 - 直接显示表格 -->
|
<pane size="60">
|
||||||
<el-card shadow="hover">
|
<!-- 查询结果表格显示 -->
|
||||||
<template #header>
|
<vxe-grid ref="xGrid" class="xGrid-style" v-bind="options">
|
||||||
<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-Tickets /> </el-icon>
|
<template #toolbar_buttons>
|
||||||
<span> 查询结果 </span>
|
<el-icon size="20" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-Tickets /> </el-icon>
|
||||||
</template>
|
<span> 查询结果 </span>
|
||||||
<el-table :data="formattedTableData" style="width: 100%" height="calc(100% - 30px)" v-if="state.selectedColumnDetails.length > 0">
|
</template>
|
||||||
<el-table-column v-for="(column, index) in state.selectedColumnDetails" :key="index" :label="`${column.tableDescription}.${column.columnDescription}`" :prop="`col${index}`"> </el-table-column>
|
<template #toolbar_tools> </template>
|
||||||
</el-table>
|
<template #empty>
|
||||||
</el-card>
|
<el-empty :image-size="200" />
|
||||||
|
</template>
|
||||||
|
</vxe-grid>
|
||||||
|
</pane>
|
||||||
|
</splitpanes>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 报表自定义查询 -->
|
<!-- 报表自定义查询 -->
|
||||||
<script lang="ts" setup name="sysReportCustomQuery">
|
<script lang="ts" setup name="sysReportCustomQuery">
|
||||||
import { reactive, onMounted, computed } from 'vue';
|
import { reactive, onMounted, computed, ref, watch } from 'vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
// import { format } from 'sql-formatter';
|
// import { format } from 'sql-formatter';
|
||||||
|
import { Splitpanes, Pane } from 'splitpanes';
|
||||||
|
import 'splitpanes/dist/splitpanes.css';
|
||||||
|
import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook';
|
||||||
|
import { VxeGridInstance } from 'vxe-table';
|
||||||
|
|
||||||
import { getAPI } from '/@/utils/axios-utils';
|
import { getAPI } from '/@/utils/axios-utils';
|
||||||
import { SysDatabaseApi } from '/@/api-services/system/api';
|
import { SysDatabaseApi } from '/@/api-services/system/api';
|
||||||
import { DbTableInfo } from '/@/api-services/system/models';
|
import { DbColumnOutput, DbTableInfo } from '/@/api-services/system/models';
|
||||||
|
|
||||||
// 定义状态
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
// 数据库表和列集合
|
// 数据库表和列集合
|
||||||
dbTables: [] as Array<DbTableInfo>,
|
dbTables: [] as Array<DbTableInfo>,
|
||||||
dbColumns: [] as any,
|
// 数据显示列配置
|
||||||
selectedTables: [] as any,
|
dbConfigs: [] as Array<{
|
||||||
selectedColumns: [] as any,
|
selectedTables: string;
|
||||||
selectedColumnDetails: [] as any,
|
selectedColumns: any[];
|
||||||
|
dbColumns: any[];
|
||||||
|
}>,
|
||||||
|
|
||||||
|
selectConfigs: [] as any,
|
||||||
|
// 连接关系配置
|
||||||
|
joinConfigs: [] as Array<{
|
||||||
|
joinType: 'LEFT JOIN'; // 左连接LEFTJOIN、右连接RIGHTJOIN、内连接INNERJOIN
|
||||||
|
leftTable: ''; // 左表名称
|
||||||
|
rightTable: ''; // 右表名称
|
||||||
|
tableColumns: Array<{ leftTableColumn: string; rightTableColumn: string }>; // 左右表列集合(一一对应)
|
||||||
|
leftTableColumns: any[];
|
||||||
|
rightTableColumns: any[];
|
||||||
|
}>,
|
||||||
// 查询条件配置
|
// 查询条件配置
|
||||||
queryConditions: [] as Array<{
|
whereConfigs: [] as Array<{
|
||||||
table: string;
|
table: string;
|
||||||
column: string;
|
column: string;
|
||||||
operator: string; // 操作符
|
operator: string; // 操作符
|
||||||
value: string; // 值
|
value: string; // 值
|
||||||
columns: any;
|
columns: any;
|
||||||
dataType: string; // 列数据类型
|
dataType: string; // 列数据类型
|
||||||
|
joinType: string; // 连接方式
|
||||||
}>,
|
}>,
|
||||||
// 数据排序配置
|
// 数据排序配置
|
||||||
sortConditions: [] as Array<{
|
orderConfigs: [] as Array<{
|
||||||
table: string;
|
table: string;
|
||||||
column: string;
|
column: string;
|
||||||
sortOrder: number;
|
sort: number; // 排序(从小到大)
|
||||||
columns: any;
|
columns: any;
|
||||||
}>,
|
}>,
|
||||||
// SQL语句
|
// SQL语句
|
||||||
sqlString: 'select * from xxx',
|
sqlString: 'select * from xxx',
|
||||||
// 查询结果
|
// 查询结果
|
||||||
queryResults: [] as Array<{
|
queryResult: [] as any,
|
||||||
columns: string[];
|
|
||||||
data: any[][];
|
|
||||||
}>,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 获取xGrid实例引用
|
||||||
|
const xGrid = ref<VxeGridInstance>();
|
||||||
|
|
||||||
|
// 创建响应式的列配置
|
||||||
|
const dynamicColumns = computed(() => {
|
||||||
|
// 基础列 - 序号
|
||||||
|
const columns: any = [
|
||||||
|
{
|
||||||
|
field: 'seq',
|
||||||
|
type: 'seq',
|
||||||
|
title: '序号',
|
||||||
|
width: 60,
|
||||||
|
fixed: 'left',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
// 根据selectConfigs动态添加列
|
||||||
|
if (state.selectConfigs && state.selectConfigs.length > 0) {
|
||||||
|
state.selectConfigs.forEach((config: any) => {
|
||||||
|
// 确保配置中包含必要的字段
|
||||||
|
if (config.dbColumnName && config.tableDescription && config.columnDescription) {
|
||||||
|
columns.push({
|
||||||
|
field: config.dbColumnName,
|
||||||
|
title: `${config.tableDescription}.${config.columnDescription}`,
|
||||||
|
showOverflow: 'tooltip',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return columns;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听selectConfigs变化,更新表格列配置
|
||||||
|
watch(
|
||||||
|
() => state.selectConfigs,
|
||||||
|
() => {
|
||||||
|
// 更新列配置
|
||||||
|
if (xGrid.value) {
|
||||||
|
xGrid.value.loadColumn(dynamicColumns.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 表格参数配置
|
||||||
|
const options = useVxeTable<DbColumnOutput>(
|
||||||
|
{
|
||||||
|
id: 'sysCustomQuery',
|
||||||
|
name: '自定义查询',
|
||||||
|
columns: dynamicColumns.value as any,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 分页配置
|
||||||
|
pagerConfig: { enabled: false },
|
||||||
|
// 工具栏配置
|
||||||
|
toolbarConfig: { export: true },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// 页面初始化
|
// 页面初始化
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 查询表列表
|
// 查询表列表
|
||||||
@ -187,39 +356,150 @@ onMounted(async () => {
|
|||||||
state.dbTables.push(element);
|
state.dbTables.push(element);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
updateSelectConfigs();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 当选择表时,加载对应的列
|
// 当选择表变化时,加载对应的列
|
||||||
const onTableChange = async () => {
|
const onTableChange = async (value: string, selectConfig: { selectedTables: string; selectedColumns: any[]; dbColumns: any[] }) => {
|
||||||
state.dbColumns = [];
|
selectConfig.dbColumns = [];
|
||||||
if (!state.selectedTables) return;
|
if (!selectConfig.selectedTables) return;
|
||||||
|
|
||||||
state.selectedTables.forEach(async (element: string) => {
|
// 获取当前选择表的详细项
|
||||||
// 获取当前选择表的详细项
|
const tableInfo = state.dbTables.find((table) => table.name === value);
|
||||||
const tableInfo = state.dbTables.find((table) => table.name === element);
|
const res: any = await getAPI(SysDatabaseApi).apiSysDatabaseColumnListTableNameConfigIdGet(value, '1300000000001');
|
||||||
const res: any = await getAPI(SysDatabaseApi).apiSysDatabaseColumnListTableNameConfigIdGet(element, '1300000000001');
|
if (res.data.result && Array.isArray(res.data.result)) {
|
||||||
if (res.data.result && Array.isArray(res.data.result)) {
|
res.data.result.forEach((newColumn: any) => {
|
||||||
res.data.result.forEach((newColumn: any) => {
|
const columnWithTableDesc = {
|
||||||
const columnWithTableDesc = {
|
...newColumn,
|
||||||
...newColumn,
|
tableDescription: tableInfo ? tableInfo.description : '', // 添加表描述
|
||||||
tableDescription: tableInfo ? tableInfo.description : '', // 添加表描述
|
};
|
||||||
};
|
selectConfig.dbColumns.push(columnWithTableDesc);
|
||||||
state.dbColumns.push(columnWithTableDesc);
|
});
|
||||||
});
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// 当列选择变化时,实现勾选与列表的关联
|
||||||
|
const onColumnChange = (val: string[], selectConfig: { selectedTables: string; selectedColumns: any[]; dbColumns: any[] }) => {
|
||||||
|
state.sqlString = '';
|
||||||
|
updateSelectConfigs();
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSelectConfigs = () => {
|
||||||
|
// 先清空现有数据,避免重复添加
|
||||||
|
state.selectConfigs = [];
|
||||||
|
|
||||||
|
// 遍历所有数据库配置
|
||||||
|
state.dbConfigs.forEach((config) => {
|
||||||
|
config.selectedColumns.forEach((colFullName: string) => {
|
||||||
|
// 解析列名,格式为"表名.列名"
|
||||||
|
const [tableName, dbColumnName] = colFullName.split('.');
|
||||||
|
// 如果解析成功,在当前config的dbColumns中查找匹配的项
|
||||||
|
if (tableName && dbColumnName) {
|
||||||
|
const matchedColumn = config.dbColumns.find((column: any) => column.tableName === tableName && column.dbColumnName === dbColumnName);
|
||||||
|
if (matchedColumn) {
|
||||||
|
state.selectConfigs.push(matchedColumn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 查询条件表改变时
|
// 添加数据显示列配置
|
||||||
const changeConditionTable = async (condition: any) => {
|
const addDbConfig = () => {
|
||||||
|
state.dbConfigs.push({
|
||||||
|
selectedTables: '',
|
||||||
|
selectedColumns: [],
|
||||||
|
dbColumns: [],
|
||||||
|
});
|
||||||
|
updateSelectConfigs();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除数据显示列配置
|
||||||
|
const removeDbConfig = (index: number) => {
|
||||||
|
state.dbConfigs.splice(index, 1);
|
||||||
|
updateSelectConfigs();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清除数据显示列配置
|
||||||
|
const clearDbConfig = () => {
|
||||||
|
state.dbConfigs = [];
|
||||||
|
state.selectConfigs = [];
|
||||||
|
updateSelectConfigs();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加连接关系配置
|
||||||
|
const addJoinConfig = () => {
|
||||||
|
state.joinConfigs.push({
|
||||||
|
joinType: 'LEFT JOIN',
|
||||||
|
leftTable: '',
|
||||||
|
rightTable: '',
|
||||||
|
tableColumns: [
|
||||||
|
{
|
||||||
|
leftTableColumn: '',
|
||||||
|
rightTableColumn: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
leftTableColumns: [],
|
||||||
|
rightTableColumns: [],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清除连接关系配置
|
||||||
|
const clearJoinConfig = () => {
|
||||||
|
state.joinConfigs = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加连接关系配置对应表
|
||||||
|
const addJoinTable = (index: number) => {
|
||||||
|
state.joinConfigs[index].tableColumns.push({
|
||||||
|
leftTableColumn: '',
|
||||||
|
rightTableColumn: '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除连接关系配置对应表
|
||||||
|
const delJoinTable = (index: number) => {
|
||||||
|
state.joinConfigs.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除连接关系配置对应列
|
||||||
|
const removeJoinColumn = (index: number, keyIndex: number) => {
|
||||||
|
state.joinConfigs[index].tableColumns.splice(keyIndex, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取连接关系配置表列数据
|
||||||
|
const getJoinTableColumns = async (tableName: string, isLeftTable: boolean = true, joinIndex: number) => {
|
||||||
|
if (tableName) {
|
||||||
|
try {
|
||||||
|
const res: any = await getAPI(SysDatabaseApi).apiSysDatabaseColumnListTableNameConfigIdGet(tableName, '1300000000001');
|
||||||
|
const columns = res?.data?.result || [];
|
||||||
|
|
||||||
|
// 根据isLeftTable参数决定更新哪个表的字段
|
||||||
|
if (isLeftTable) {
|
||||||
|
state.joinConfigs[joinIndex].leftTableColumns = columns;
|
||||||
|
} else {
|
||||||
|
state.joinConfigs[joinIndex].rightTableColumns = columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取表字段失败:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 改变查询条件表时
|
||||||
|
const changeWhereTable = async (condition: any) => {
|
||||||
const res: any = await getAPI(SysDatabaseApi).apiSysDatabaseColumnListTableNameConfigIdGet(condition.table, '1300000000001');
|
const res: any = await getAPI(SysDatabaseApi).apiSysDatabaseColumnListTableNameConfigIdGet(condition.table, '1300000000001');
|
||||||
condition.columns = res.data.result;
|
condition.columns = res.data.result;
|
||||||
condition.column = undefined;
|
condition.column = undefined;
|
||||||
condition.value = undefined;
|
condition.value = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 查询条件列改变时
|
// 改变查询条件列时
|
||||||
const changeConditionColumn = async (condition: any) => {
|
const changeWhereColumn = async (condition: any) => {
|
||||||
// 获取当前列数据类型
|
// 获取当前列数据类型
|
||||||
var column = condition.columns.find((column: any) => column.tableName + '.' + column.dbColumnName == condition.column);
|
var column = condition.columns.find((column: any) => column.tableName + '.' + column.dbColumnName == condition.column);
|
||||||
if (column) {
|
if (column) {
|
||||||
@ -228,95 +508,89 @@ const changeConditionColumn = async (condition: any) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 当列选择变化时触发,实现勾选与列表的关联
|
// 添加查询条件配置
|
||||||
const onColumnsChange = () => {
|
const addWhereConfig = () => {
|
||||||
// 清除之前的查询结果,确保下一次查询基于最新选择的列
|
state.whereConfigs.push({
|
||||||
state.sqlString = '';
|
|
||||||
state.selectedColumnDetails = [];
|
|
||||||
|
|
||||||
// 更新已选择列的完整信息
|
|
||||||
if (state.selectedColumns.length <= 0 || state.dbColumns.length <= 0) return;
|
|
||||||
state.selectedColumns.forEach((colFullName: string) => {
|
|
||||||
// 解析列名,格式为"表名.列名"
|
|
||||||
const [tableName, dbColumnName] = colFullName.split('.');
|
|
||||||
// 如果解析成功,在columns中查找匹配的项
|
|
||||||
if (tableName && dbColumnName) {
|
|
||||||
const matchedColumn = state.dbColumns.find((column: any) => column.tableName === tableName && column.dbColumnName === dbColumnName);
|
|
||||||
// 如果找到匹配项,添加到selectedColumnDetails
|
|
||||||
if (matchedColumn) {
|
|
||||||
state.selectedColumnDetails.push(matchedColumn);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 添加查询条件
|
|
||||||
const addCondition = () => {
|
|
||||||
state.queryConditions.push({
|
|
||||||
table: '',
|
table: '',
|
||||||
column: '',
|
column: '',
|
||||||
operator: '=',
|
operator: '=',
|
||||||
value: '',
|
value: '',
|
||||||
columns: [],
|
columns: [],
|
||||||
dataType: '',
|
dataType: '',
|
||||||
|
joinType: 'AND',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 移除查询条件
|
// 移除查询条件配置
|
||||||
const removeCondition = (index: number) => {
|
const removeWhereConfig = (index: number) => {
|
||||||
state.queryConditions.splice(index, 1);
|
state.whereConfigs.splice(index, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 清除所有查询条件
|
// 清除所有查询条件配置
|
||||||
const clearCondition = () => {
|
const clearWhereConfig = () => {
|
||||||
state.queryConditions = [];
|
state.whereConfigs = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加排序条件
|
// 添加排序配置
|
||||||
const addSortCondition = () => {
|
const addSortConfig = () => {
|
||||||
state.sortConditions.push({
|
state.orderConfigs.push({
|
||||||
table: '',
|
table: '',
|
||||||
column: '',
|
column: '',
|
||||||
sortOrder: 10,
|
sort: 10,
|
||||||
columns: [],
|
columns: [],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 移除排序条件
|
// 移除排序配置
|
||||||
const removeSortCondition = (index: number) => {
|
const removeSortConfig = (index: number) => {
|
||||||
state.sortConditions.splice(index, 1);
|
state.orderConfigs.splice(index, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 清除所有排序条件
|
// 清除所有排序配置
|
||||||
const clearSortCondition = () => {
|
const clearSortConfig = () => {
|
||||||
state.sortConditions = [];
|
state.orderConfigs = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 预览SQL
|
// 预览SQL语句
|
||||||
const generateSQL = async () => {
|
const generateSQL = async () => {
|
||||||
var queryJson = {
|
var queryJson = {
|
||||||
columns: state.selectedColumnDetails
|
selectConfigs: state.selectConfigs
|
||||||
.filter((col: any) => state.selectedTables.includes(col.tableName)) // 只包含选中表的列
|
.filter((config: any) => state.dbConfigs.some((dbConfig: any) => dbConfig.selectedTables.includes(config.tableName))) // 只包含选中表的列
|
||||||
.map((col: any) => ({
|
.map((config: any) => ({
|
||||||
table: col.tableName,
|
table: config.tableName,
|
||||||
column: col.dbColumnName,
|
column: config.dbColumnName,
|
||||||
})),
|
})),
|
||||||
conditions: state.queryConditions.map((cond) => {
|
joinConfigs: state.joinConfigs
|
||||||
return {
|
.filter((config: any) => config.leftTable != undefined && config.rightTable != undefined && config.tableColumns != undefined)
|
||||||
table: cond.table,
|
.map((config) => {
|
||||||
column: cond.column.includes('.') ? cond.column.split('.')[1] : cond.column,
|
return {
|
||||||
operator: cond.operator,
|
joinType: config.joinType,
|
||||||
value: cond.value,
|
leftTable: config.leftTable,
|
||||||
};
|
rightTable: config.rightTable,
|
||||||
}),
|
tableColumns: config.tableColumns,
|
||||||
orders: state.sortConditions.map((sortCond) => {
|
};
|
||||||
return {
|
}),
|
||||||
table: sortCond.table,
|
whereConfigs: state.whereConfigs
|
||||||
column: sortCond.column.includes('.') ? sortCond.column.split('.')[1] : sortCond.column,
|
.filter((config: any) => config.table != undefined && config.column != undefined && config.value != undefined)
|
||||||
sort: sortCond.sortOrder,
|
.map((config) => {
|
||||||
};
|
return {
|
||||||
}),
|
table: config.table,
|
||||||
|
column: config.column.includes('.') ? config.column.split('.')[1] : config.column,
|
||||||
|
operator: config.operator,
|
||||||
|
value: config.value,
|
||||||
|
dataType: config.dataType,
|
||||||
|
joinType: config.joinType,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
orderConfigs: state.orderConfigs
|
||||||
|
.filter((config: any) => config.table != undefined && config.column != undefined)
|
||||||
|
.map((config) => {
|
||||||
|
return {
|
||||||
|
table: config.table,
|
||||||
|
column: config.column.includes('.') ? config.column.split('.')[1] : config.column,
|
||||||
|
sort: config.sort,
|
||||||
|
};
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
var res = await getAPI(SysDatabaseApi).apiSysDatabaseGenerateSQLPost(queryJson);
|
var res = await getAPI(SysDatabaseApi).apiSysDatabaseGenerateSQLPost(queryJson);
|
||||||
state.sqlString = res.data.result ?? '';
|
state.sqlString = res.data.result ?? '';
|
||||||
@ -338,26 +612,12 @@ const generateSQL = async () => {
|
|||||||
const executeSQL = async () => {
|
const executeSQL = async () => {
|
||||||
var queryJson = await generateSQL();
|
var queryJson = await generateSQL();
|
||||||
var res = await getAPI(SysDatabaseApi).apiSysDatabaseExecuteSQLPost(queryJson);
|
var res = await getAPI(SysDatabaseApi).apiSysDatabaseExecuteSQLPost(queryJson);
|
||||||
console.log(res.data.result);
|
state.queryResult = res.data.result ?? [];
|
||||||
state.queryResults[0].data = res.data.result ?? [];
|
|
||||||
|
// 更新表格数据
|
||||||
|
xGrid.value?.loadData(state.queryResult);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 将二维数组数据格式化为对象数组,以便 Element Plus 的表格组件使用
|
|
||||||
const formattedTableData = computed(() => {
|
|
||||||
if (!state.queryResults || !state.queryResults[0] || !state.queryResults[0].data) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回所有数据
|
|
||||||
return state.queryResults[0].data.map((row, rowIndex) => {
|
|
||||||
const formattedRow: Record<string, any> = {};
|
|
||||||
row.forEach((cell, cellIndex) => {
|
|
||||||
formattedRow[`col${cellIndex}`] = cell;
|
|
||||||
});
|
|
||||||
return formattedRow;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 打开方案
|
// 打开方案
|
||||||
const openSchema = () => {
|
const openSchema = () => {
|
||||||
// 创建文件选择器
|
// 创建文件选择器
|
||||||
@ -374,18 +634,22 @@ const openSchema = () => {
|
|||||||
try {
|
try {
|
||||||
// 解析JSON文件内容
|
// 解析JSON文件内容
|
||||||
const schemaData = JSON.parse(e.target?.result as string);
|
const schemaData = JSON.parse(e.target?.result as string);
|
||||||
if (schemaData.selectedTable) {
|
if (schemaData.dbConfigs) {
|
||||||
state.selectedTables = schemaData.selectedTable;
|
state.dbConfigs = schemaData.dbConfigs;
|
||||||
}
|
}
|
||||||
if (schemaData.selectedColumns) {
|
if (schemaData.selectConfigs) {
|
||||||
state.selectedColumns = schemaData.selectedColumns;
|
state.selectConfigs = schemaData.selectConfigs;
|
||||||
}
|
}
|
||||||
if (schemaData.queryConditions) {
|
if (schemaData.whereConfigs) {
|
||||||
state.queryConditions = schemaData.queryConditions;
|
state.whereConfigs = schemaData.whereConfigs;
|
||||||
}
|
}
|
||||||
if (schemaData.sortConditions) {
|
if (schemaData.joinConfigs) {
|
||||||
state.sortConditions = schemaData.sortConditions;
|
state.joinConfigs = schemaData.joinConfigs;
|
||||||
}
|
}
|
||||||
|
if (schemaData.orderConfigs) {
|
||||||
|
state.orderConfigs = schemaData.orderConfigs;
|
||||||
|
}
|
||||||
|
|
||||||
ElMessage.success('自定义查询方案加载成功');
|
ElMessage.success('自定义查询方案加载成功');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('自定义查询方案文件格式错误');
|
ElMessage.error('自定义查询方案文件格式错误');
|
||||||
@ -401,16 +665,17 @@ const openSchema = () => {
|
|||||||
|
|
||||||
// 保存方案
|
// 保存方案
|
||||||
const saveSchema = () => {
|
const saveSchema = () => {
|
||||||
if (state.queryConditions.length === 0) {
|
if (state.whereConfigs.length === 0) {
|
||||||
ElMessage.warning('请先进行配置');
|
ElMessage.warning('请先进行配置');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 创建要保存的查询配置对象
|
// 创建要保存的查询配置对象
|
||||||
const schemaData = {
|
const schemaData = {
|
||||||
selectedTable: state.selectedTables,
|
dbConfigs: state.dbConfigs, // 数据库配置
|
||||||
selectedColumns: state.selectedColumns,
|
selectConfigs: state.selectConfigs, // 选中的列的详细信息
|
||||||
queryConditions: state.queryConditions,
|
whereConfigs: state.whereConfigs, // 查询条件
|
||||||
sortConditions: state.sortConditions,
|
orderConfigs: state.orderConfigs, // 排序条件
|
||||||
|
joinConfigs: state.joinConfigs, // 关联键配置
|
||||||
};
|
};
|
||||||
|
|
||||||
// 转换为JSON字符串
|
// 转换为JSON字符串
|
||||||
@ -438,14 +703,74 @@ const saveSchema = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.conditions-container {
|
.config-container {
|
||||||
|
width: 100%;
|
||||||
padding: 0 10px 10px 10px;
|
padding: 0 10px 10px 10px;
|
||||||
}
|
}
|
||||||
|
:deep(.el-card) {
|
||||||
.condition-item {
|
height: 100%;
|
||||||
|
.el-card__body {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.config-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
.config-item-join {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-bottom: 1px dashed #c0c4cc;
|
||||||
|
padding: 0 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
.config-item-left {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
// .config-item-right {
|
||||||
|
// width: 100%;
|
||||||
|
// }
|
||||||
|
.config-item-right-item {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.config-item-db {
|
||||||
|
display: flex;
|
||||||
|
// align-items: center;
|
||||||
|
.config-item-left {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
align-items: center;
|
||||||
|
:deep(.el-select) {
|
||||||
|
width: calc(100% - 45px);
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.config-item-right {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
align-items: center;
|
||||||
|
:deep(.el-select) {
|
||||||
|
width: calc(100% - 45px);
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.config-item-button {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.config-item-db:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.config-item-join:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user