增加:开发工具->数据重置(手动操作生成表和种子数据)
This commit is contained in:
parent
d99c6f1f46
commit
e2daef423e
@ -205,10 +205,11 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
|
||||
|
||||
new SysMenu{ Id=1310000000601, Pid=0, Title="开发工具", Path="/develop", Name="develop", Component="Layout", Icon="ele-Cpu", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=13000 },
|
||||
new SysMenu{ Id=1310000000611, Pid=1310000000601, Title="库表管理", Path="/develop/database", Name="sysDatabase", Component="/system/database/index",Icon="ele-Coin", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
|
||||
new SysMenu{ Id=1310000000621, Pid=1310000000601, Title="代码生成", Path="/develop/codeGen", Name="sysCodeGen", Component="/system/codeGen/index", Icon="ele-Crop", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=110 },
|
||||
new SysMenu{ Id=1310000000631, Pid=1310000000601, Title="表单设计", Path="/develop/formDes", Name="sysFormDes", Component="/system/formDes/index", Icon="ele-Edit", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=120 },
|
||||
new SysMenu{ Id=1310000000641, Pid=1310000000601, Title="系统接口", Path="/develop/api", Name="sysApi", Component="layout/routerView/iframe", IsIframe=true, OutLink="http://localhost:5005", Icon="ele-Help", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=130 },
|
||||
new SysMenu{ Id=1310000000651, Pid=1310000000601, Title="接口压测", Path="/develop/stressTest", Name="sysStressTest", Component="/system/stressTest/index", Icon="ele-Compass", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2024-12-28 00:00:00"), OrderNo=140 },
|
||||
new SysMenu{ Id=1310000000621, Pid=1310000000601, Title="数据重置", Path="/develop/dataInit", Name="sysDataInit", Component="/system/dataInit/index",Icon="ele-TakeawayBox", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
|
||||
new SysMenu{ Id=1310000000631, Pid=1310000000601, Title="代码生成", Path="/develop/codeGen", Name="sysCodeGen", Component="/system/codeGen/index", Icon="ele-Crop", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=110 },
|
||||
new SysMenu{ Id=1310000000641, Pid=1310000000601, Title="表单设计", Path="/develop/formDes", Name="sysFormDes", Component="/system/formDes/index", Icon="ele-Edit", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=120 },
|
||||
new SysMenu{ Id=1310000000651, Pid=1310000000601, Title="系统接口", Path="/develop/api", Name="sysApi", Component="layout/routerView/iframe", IsIframe=true, OutLink="http://localhost:5005", Icon="ele-Help", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=130 },
|
||||
new SysMenu{ Id=1310000000661, Pid=1310000000601, Title="接口压测", Path="/develop/stressTest", Name="sysStressTest", Component="/system/stressTest/index", Icon="ele-Compass", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2024-12-28 00:00:00"), OrderNo=140 },
|
||||
|
||||
new SysMenu{ Id=1310000000701, Pid=0, Title="帮助文档", Path="/doc", Name="doc", Component="Layout", Icon="ele-Notebook", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=14000 },
|
||||
new SysMenu{ Id=1310000000711, Pid=1310000000701, Title="框架教程", Path="/doc/admin", Name="sysAdmin", Component="layout/routerView/link", IsIframe=false, IsKeepAlive=false, OutLink="https://adminnet.top/", Icon="ele-Sunny", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class DataInitInput
|
||||
{
|
||||
public string ConfigId { get; set; }
|
||||
|
||||
public List<string> EntityNames { get; set; }
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class DataInitItemOutput
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string AssemblyName { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
}
|
||||
103
Admin.NET/Admin.NET.Core/Service/DataInit/SysDataInitService.cs
Normal file
103
Admin.NET/Admin.NET.Core/Service/DataInit/SysDataInitService.cs
Normal file
@ -0,0 +1,103 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using DocumentFormat.OpenXml.Office2016.Drawing.ChartDrawing;
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 系统表和种子数据初始化服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 250, Description = "系统表和种子数据初始化服务")]
|
||||
public class SysDataInitService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly UserManager userManager;
|
||||
|
||||
public SysDataInitService(UserManager userManager)
|
||||
{
|
||||
this.userManager = userManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实体类列表 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取实体类列表")]
|
||||
public List<DataInitItemOutput> GetClassList(string configId)
|
||||
{
|
||||
var totalWatch = Stopwatch.StartNew(); // 开始总计时
|
||||
Log.Information($"获取实体类列表");
|
||||
var entityTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false))
|
||||
.Where(u => !u.GetCustomAttributes<IgnoreTableAttribute>().Any())
|
||||
//.WhereIF(config.TableSettings.EnableIncreTable, u => u.IsDefined(typeof(IncreTableAttribute), false))
|
||||
.ToList();
|
||||
|
||||
if (configId == SqlSugarConst.MainConfigId) // 默认库(有系统表特性、没有日志表和租户表特性)
|
||||
entityTypes = entityTypes.Where(u => u.GetCustomAttributes<SysTableAttribute>().Any() || (!u.GetCustomAttributes<LogTableAttribute>().Any() && !u.GetCustomAttributes<TenantAttribute>().Any())).ToList();
|
||||
else if (configId == SqlSugarConst.LogConfigId) // 日志库
|
||||
entityTypes = entityTypes.Where(u => u.GetCustomAttributes<LogTableAttribute>().Any()).ToList();
|
||||
else
|
||||
entityTypes = entityTypes.Where(u => u.GetCustomAttribute<TenantAttribute>()?.configId.ToString() == configId).ToList(); // 自定义的库
|
||||
|
||||
List<DataInitItemOutput> outputList = new List<DataInitItemOutput>();
|
||||
foreach (var entityType in entityTypes)
|
||||
{
|
||||
outputList.Add(new DataInitItemOutput() { Name = entityType.Name, AssemblyName = entityType.Assembly.ManifestModule.Name, Description = entityType.GetCustomAttribute<SugarTable>().TableDescription });
|
||||
}
|
||||
return outputList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取种子数据列表 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取种子数据列表")]
|
||||
public List<DataInitItemOutput> GetSeedDataList(string configId)
|
||||
{
|
||||
bool enableIncreSeed = false; // 是否启用增量种子数据TODO
|
||||
Log.Information($"初始化种子数据");
|
||||
var seedDataTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>))))
|
||||
.Where(u => !u.IsDefined(typeof(TenantSeedAttribute), false))
|
||||
.WhereIF(enableIncreSeed, u => u.IsDefined(typeof(IncreSeedAttribute), false))
|
||||
.OrderBy(u => u.GetCustomAttributes(typeof(SeedDataAttribute), false).Length > 0 ? ((SeedDataAttribute)u.GetCustomAttributes(typeof(SeedDataAttribute), false)[0]).Order : 0).ToList();
|
||||
|
||||
List<DataInitItemOutput> outputList = new List<DataInitItemOutput>();
|
||||
foreach (var seedDataType in seedDataTypes)
|
||||
{
|
||||
var entityType = seedDataType.GetInterfaces().First().GetGenericArguments().First();
|
||||
outputList.Add(new DataInitItemOutput() { Name = seedDataType.Name, AssemblyName = seedDataType.Assembly.ManifestModule.Name, Description = entityType.GetCustomAttribute<SugarTable>().TableDescription + "种子数据" });
|
||||
}
|
||||
return outputList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化表结构 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
[ApiDescriptionSettings(Name = "InitTable"), HttpPost]
|
||||
[DisplayName("初始化表结构")]
|
||||
public void InitializeTable(DataInitInput input)
|
||||
{
|
||||
if (!userManager.SuperAdmin)
|
||||
throw Oops.Oh("无权限操作!");
|
||||
|
||||
SqlSugarSetup.InitTables(input.ConfigId, input.EntityNames);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化种子数据 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
[ApiDescriptionSettings(Name = "InitSeedData"), HttpPost]
|
||||
[DisplayName("初始化种子数据")]
|
||||
public void InitializeSeedData(DataInitInput input)
|
||||
{
|
||||
if (!userManager.SuperAdmin)
|
||||
throw Oops.Oh("无权限操作!");
|
||||
|
||||
SqlSugarSetup.InitSeedData(input.ConfigId, input.EntityNames);
|
||||
}
|
||||
}
|
||||
@ -703,4 +703,96 @@ public static class SqlSugarSetup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化表结构
|
||||
/// </summary>
|
||||
/// <param name="entityNames">实体名称列表</param>
|
||||
public static void InitTables(string configId, List<string> entityNames)
|
||||
{
|
||||
var dbOptions = App.GetConfig<DbConnectionOptions>("DbConnection", true);
|
||||
var config = dbOptions.ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == configId);
|
||||
var dbProvider = ITenant.GetConnectionScope(config.ConfigId);
|
||||
|
||||
// 初始化表结构之前——系统版本号
|
||||
var (startups, oldVerion, currentVersion) = BeforeInitTable(dbProvider);
|
||||
|
||||
// 初始化表结构
|
||||
var totalWatch = Stopwatch.StartNew(); // 开始总计时
|
||||
Log.Information($"初始化表结构 {config.DbType} - {config.ConfigId}");
|
||||
var entityTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false))
|
||||
.Where(u => !u.GetCustomAttributes<IgnoreTableAttribute>().Any())
|
||||
.WhereIF(config.TableSettings.EnableIncreTable, u => u.IsDefined(typeof(IncreTableAttribute), false)).ToList();
|
||||
|
||||
entityTypes = entityTypes.Where(u => entityNames.Contains(u.Name)).ToList();
|
||||
|
||||
// 删除视图再初始化表结构,防止因为视图导致无法同步表结构
|
||||
var viewTypeList = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarView)))).ToList();
|
||||
foreach (var viewType in viewTypeList)
|
||||
{
|
||||
var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(viewType) ?? throw new Exception("获取视图实体配置有误");
|
||||
if (dbProvider.DbMaintenance.GetViewInfoList(false).Any(it => it.Name.EqualIgnoreCase(entityInfo.DbTableName)))
|
||||
dbProvider.DbMaintenance.DropView(entityInfo.DbTableName);
|
||||
}
|
||||
|
||||
int taskIndex = 0, size = entityTypes.Count;
|
||||
var taskList = entityTypes.Select(entityType => Task.Run(() =>
|
||||
{
|
||||
var stopWatch = Stopwatch.StartNew(); // 开始计时
|
||||
|
||||
dbProvider.InitTable(entityType); // 初始化表结构
|
||||
|
||||
stopWatch.Stop(); // 停止计时
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"初始化表 {entityType,-64} ({config.ConfigId} - {Interlocked.Increment(ref taskIndex):D003}/{size:D003}) 耗时:{stopWatch.ElapsedMilliseconds} ms");
|
||||
}));
|
||||
Task.WaitAll(taskList.ToArray());
|
||||
|
||||
totalWatch.Stop(); // 停止总计时
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"初始化表结构 {config.DbType} - {config.ConfigId} 总耗时:{totalWatch.ElapsedMilliseconds} ms");
|
||||
|
||||
|
||||
// 初始化种子数据之后——系统版本号
|
||||
AfterInitSeed(dbProvider, startups, oldVerion, currentVersion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化种子数据
|
||||
/// </summary>
|
||||
/// <param name="entityNames">实体名称列表</param>
|
||||
public static void InitSeedData(string configId, List<string> entityNames)
|
||||
{
|
||||
var dbOptions = App.GetConfig<DbConnectionOptions>("DbConnection", true);
|
||||
var config = dbOptions.ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == configId);
|
||||
var dbProvider = ITenant.GetConnectionScope(config.ConfigId);
|
||||
|
||||
var totalWatch = Stopwatch.StartNew(); // 开始总计时
|
||||
Log.Information($"初始化种子数据 {dbProvider.CurrentConnectionConfig.DbType} - {dbProvider.CurrentConnectionConfig.ConfigId}");
|
||||
var seedDataTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>))))
|
||||
.Where(u => !u.IsDefined(typeof(TenantSeedAttribute), false))
|
||||
.OrderBy(u => u.GetCustomAttributes(typeof(SeedDataAttribute), false).Length > 0 ? ((SeedDataAttribute)u.GetCustomAttributes(typeof(SeedDataAttribute), false)[0]).Order : 0).ToList();
|
||||
|
||||
seedDataTypes = seedDataTypes.Where(u => entityNames.Contains(u.Name)).ToList();
|
||||
|
||||
int taskIndex = 0, size = seedDataTypes.Count;
|
||||
foreach (var seedType in seedDataTypes)
|
||||
{
|
||||
var stopWatch = Stopwatch.StartNew(); // 开始计时
|
||||
|
||||
// 初始化种子数据
|
||||
var tuple = dbProvider.InitTableSeedData(seedType);
|
||||
if (tuple == null) continue;
|
||||
|
||||
stopWatch.Stop(); // 停止计时
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"初始化种子数据 {seedType.FullName,-58} ({dbProvider.CurrentConnectionConfig.ConfigId} - {Interlocked.Increment(ref taskIndex):D003}/{size:D003},数据量:{tuple.Value.Item1:D003},新增 {tuple.Value.Item2:D003} 条记录,更新 {tuple.Value.Item3:D003} 条记录,耗时:{stopWatch.ElapsedMilliseconds:N0} ms)");
|
||||
}
|
||||
|
||||
totalWatch.Stop(); // 停止总计时
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"初始化种子数据 {dbProvider.CurrentConnectionConfig.DbType} - {dbProvider.CurrentConnectionConfig.ConfigId} 总耗时:{totalWatch.ElapsedMilliseconds:N0} ms");
|
||||
}
|
||||
}
|
||||
37
Web/src/api/system/dataInit.ts
Normal file
37
Web/src/api/system/dataInit.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import request from '/@/utils/request';
|
||||
enum Api {
|
||||
EntityClassList = '/api/sysDataInit/classList',
|
||||
SeedDataList = '/api/sysDataInit/seedDataList',
|
||||
InitTable = '/api/sysDataInit/initTable',
|
||||
InitSeedData = '/api/sysDataInit/initSeedData',
|
||||
}
|
||||
|
||||
// 获取所有实体类
|
||||
export const getEntityClassList = (params?: any) =>
|
||||
request({
|
||||
url: `${Api.EntityClassList}/${params}`,
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
// 获取所有种子数据
|
||||
export const getSeedDataList = (params?: any) =>
|
||||
request({
|
||||
url: `${Api.SeedDataList}/${params}`,
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
// 初始化表
|
||||
export const initTable = (params?: any) =>
|
||||
request({
|
||||
url: `${Api.InitTable}`,
|
||||
method: 'post',
|
||||
data: params,
|
||||
});
|
||||
|
||||
// 初始化种子数据
|
||||
export const initSeedData = (params?: any) =>
|
||||
request({
|
||||
url: `${Api.InitSeedData}`,
|
||||
method: 'post',
|
||||
data: params,
|
||||
});
|
||||
284
Web/src/views/system/dataInit/component/entityList.vue
Normal file
284
Web/src/views/system/dataInit/component/entityList.vue
Normal file
@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<el-card shadow="hover" :body-style="{ padding: '20px 20px 16px 10px', display: 'flex', width: '100%', height: '100%', alignItems: 'start' }">
|
||||
<el-form :model="state.queryParams" ref="queryForm" :show-message="false" :inlineMessage="true" label-width="auto" style="flex: 1 1 0%" @submit.prevent="handleQuery" >
|
||||
<el-row :gutter="10">
|
||||
<el-col class="mb5" :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
|
||||
<el-form-item label="库名" prop="configId">
|
||||
<el-select v-model="state.configId" placeholder="库名" filterable @change="handleQueryTable">
|
||||
<el-option v-for="item in state.dbData" :key="item.configId" :label="`${item.dbName}(${item.configId})`" :value="item.configId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<el-divider style="height: calc(100% - 5px); margin: 0 10px" direction="vertical" />
|
||||
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-button-group>
|
||||
<el-button type="primary" icon="ele-Search" @click="handleInitTable" :loading="options.loading">
|
||||
生成表<span v-if="state.selectedRows.length > 0">({{ state.selectedRows.length }})</span>
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<el-card class="full-table" shadow="hover" style="margin-top: 5px">
|
||||
<vxe-grid ref="xGrid" class="xGrid-style" v-bind="options" v-on="gridEvents">
|
||||
<template #row_record="{ row }">
|
||||
<ModifyRecord :data="row" />
|
||||
</template>
|
||||
</vxe-grid>
|
||||
<div style="text-align: right; margin: 7px 8px 7px 0px; color: #666; font-size: 12px;">
|
||||
共 {{ xGrid?.getTableData ? xGrid.getTableData().fullData.length : 0 }} 条记录
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="entityList">
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { ElMessageBox, ElMessage } from "element-plus";
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
|
||||
import { VxeGridInstance, VxeGridListeners, VxeGridPropTypes } from 'vxe-table';
|
||||
import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook';
|
||||
import { Local } from '/@/utils/storage';
|
||||
|
||||
import { formatDate } from '/@/utils/formatTime';
|
||||
|
||||
import { getAPI } from '/@/utils/axios-utils';
|
||||
import { SysDatabaseApi } from '/@/api-services/api';
|
||||
import { getEntityClassList, initTable } from '/@/api/system/dataInit';
|
||||
|
||||
// 子窗口对象
|
||||
const xGrid = ref<VxeGridInstance>();
|
||||
const userStore = useUserInfo();
|
||||
|
||||
const dc = userStore.getDictItemByValue;
|
||||
const dv = userStore.getDictItemByLabel;
|
||||
const dl = userStore.getDictDataByCode;
|
||||
|
||||
// 变量
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
dbData: [] as any,
|
||||
configId: '',
|
||||
showAdvanceQueryUI: false,
|
||||
queryParams: {
|
||||
searchKey: undefined,
|
||||
GroupBy: [] as any,
|
||||
projectName: undefined,
|
||||
financingProjectName: undefined,
|
||||
projectType: undefined,
|
||||
constructionStatus: undefined,
|
||||
approvalNumber: undefined,
|
||||
approvalDate: undefined,
|
||||
approvalDateRange: undefined,
|
||||
constructionAddress: undefined,
|
||||
},
|
||||
localPageParam: {
|
||||
pageSize: 50 as number,
|
||||
defaultSort: { field: 'Id', order: 'asc', descStr: 'desc' },
|
||||
},
|
||||
totalSum:[] as any,
|
||||
visible: false,
|
||||
title: '',
|
||||
selectedRows: [] as any[],
|
||||
});
|
||||
|
||||
// 本地存储参数
|
||||
const localPageParamKey = 'localPageParam:entityList';
|
||||
|
||||
// 改变高级查询的控件显示状态
|
||||
const changeAdvanceQueryUI = () => {
|
||||
state.showAdvanceQueryUI = !state.showAdvanceQueryUI;
|
||||
};
|
||||
|
||||
// 校验表格字段权限
|
||||
const checkTableColumnVisible = (tableColumnName: any) => {
|
||||
return !userStore.userTableList.includes(tableColumnName);
|
||||
};
|
||||
|
||||
// 表格参数配置
|
||||
const options = useVxeTable(
|
||||
{
|
||||
id: 'entityList',
|
||||
name: '项目信息',
|
||||
columns: [
|
||||
{ type: 'checkbox', width: 40, fixed: 'left' },
|
||||
{ type: 'seq', title: '序号', width: 50, fixed: 'left' },
|
||||
{ field: 'assemblyName', title: '程序集', minWidth: 120, showOverflow: 'tooltip', sortable: false},
|
||||
{ field: 'name', title: '名称', minWidth: 200, showOverflow: 'tooltip', sortable: false},
|
||||
{ field: 'description', title: '描述', minWidth: 200, showOverflow: 'tooltip', sortable: false},
|
||||
// { title: '操作', fixed: 'right', width: 120, showOverflow: true, slots: { default: 'row_buttons' } },
|
||||
],
|
||||
footerMethod: ({ columns, data }) => {
|
||||
const totalSum=state.totalSum[0];
|
||||
return [
|
||||
// columns.map((column, colIndex) => {
|
||||
// if (colIndex === 0) {
|
||||
// return `合计:`
|
||||
// }
|
||||
// if (column.field === 'estimatedTotalInvestment') {
|
||||
// // 计算表格内总和
|
||||
// return `${data.reduce((sum, row) => sum + (row.estimatedTotalInvestment || 0), 0)}/总计:${totalSum?.estimatedTotalInvestment||0}`
|
||||
// }
|
||||
// })
|
||||
]
|
||||
},
|
||||
},
|
||||
// vxeGrid配置参数(此处可覆写任何参数),参考vxe-table官方文档
|
||||
{
|
||||
// 代理配置//加载时不立刻获取
|
||||
proxyConfig: { autoLoad: false, ajax: { query: ({ page, sort }) => handleQueryApi(page, sort) } },
|
||||
// 排序配置
|
||||
sortConfig: { defaultSort: Local.get(localPageParamKey)?.defaultSort || state.localPageParam.defaultSort },
|
||||
// 关闭分页
|
||||
pagerConfig: { enabled: false },
|
||||
// 工具栏配置
|
||||
toolbarConfig: { export: false },
|
||||
// 行设置
|
||||
// rowConfig: { height: 80 },
|
||||
// 设置列显隐
|
||||
customConfig: {
|
||||
visibleMethod({ column }) {
|
||||
return checkTableColumnVisible(`entityList:${column.field}`);
|
||||
},
|
||||
},
|
||||
showFooter: true, // ✅ 正确控制表尾显示
|
||||
}
|
||||
);
|
||||
|
||||
// 页面初始化
|
||||
onMounted(async () => {
|
||||
options.loading = true;
|
||||
let res = await getAPI(SysDatabaseApi).apiSysDatabaseListGet();
|
||||
state.dbData = res.data.result;
|
||||
|
||||
// let appNamesRes = await getAPI(SysCodeGenApi).apiSysCodeGenApplicationNamespacesGet();
|
||||
// state.appNamespaces = appNamesRes.data.result as Array<string>;
|
||||
options.loading = false;
|
||||
});
|
||||
|
||||
// 查询api
|
||||
const handleQueryApi = async (page: VxeGridPropTypes.ProxyAjaxQueryPageParams, sort: VxeGridPropTypes.ProxyAjaxQuerySortCheckedParams) => {
|
||||
if (state.configId) {
|
||||
return getEntityClassList(state.configId);
|
||||
}
|
||||
};
|
||||
|
||||
// 查询操作
|
||||
const handleQuery = async (reset = false) => {
|
||||
options.loading = true;
|
||||
reset ? await xGrid.value?.commitProxy('reload') : await xGrid.value?.commitProxy('query');
|
||||
options.loading = false;
|
||||
};
|
||||
const listhandleQuery= async (qparams: any) => {
|
||||
await handleQuery(true);
|
||||
};
|
||||
// 重置操作
|
||||
const resetQuery = async () => {
|
||||
state.queryParams.searchKey = undefined;
|
||||
state.queryParams.projectName = undefined;
|
||||
state.queryParams.financingProjectName = undefined;
|
||||
state.queryParams.projectType = undefined;
|
||||
state.queryParams.constructionStatus = undefined;
|
||||
state.queryParams.approvalNumber = undefined;
|
||||
state.queryParams.approvalDate = undefined;
|
||||
state.queryParams.constructionAddress = undefined;
|
||||
await xGrid.value?.commitProxy('reload');
|
||||
};
|
||||
|
||||
|
||||
// 表格事件
|
||||
const gridEvents: VxeGridListeners = {
|
||||
//行单击事件
|
||||
async cellClick({ row, column }) {
|
||||
listClick(row,column);
|
||||
},
|
||||
// 多选变更事件
|
||||
checkboxChange({ records }) {
|
||||
state.selectedRows = records;
|
||||
},
|
||||
// 全选/全不选事件,确保取消全选时 selectedRows 及时清空
|
||||
checkboxAll({ records }) {
|
||||
state.selectedRows = records;
|
||||
},
|
||||
// 只对 pager-config 配置时有效,分页发生改变时会触发该事件
|
||||
async pageChange({ pageSize }) {
|
||||
state.localPageParam.pageSize = pageSize;
|
||||
Local.set(localPageParamKey, state.localPageParam);
|
||||
},
|
||||
// 当排序条件发生变化时会触发该事件
|
||||
async sortChange({ field, order }) {
|
||||
state.localPageParam.defaultSort = { field: field, order: order!, descStr: 'desc' };
|
||||
Local.set(localPageParamKey, state.localPageParam);
|
||||
},
|
||||
};
|
||||
|
||||
// 表查询操作
|
||||
const handleQueryTable = async () => {
|
||||
if (!state.configId) {
|
||||
xGrid.value?.loadData([]);
|
||||
return;
|
||||
}
|
||||
options.loading = true;
|
||||
try {
|
||||
const res = await getEntityClassList(state.configId);
|
||||
const tableData = res?.data?.result ?? [];
|
||||
xGrid.value?.loadData(tableData);
|
||||
} catch (error) {
|
||||
ElMessage.error('加载实体类列表失败');
|
||||
xGrid.value?.loadData([]);
|
||||
} finally {
|
||||
options.loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 与父组件的交互逻辑
|
||||
const emits = defineEmits(['list-click']);
|
||||
const listClick = (row: any,column:any) => {
|
||||
emits('list-click', row,column);
|
||||
};
|
||||
|
||||
// 生成表操作
|
||||
const handleInitTable = async () => {
|
||||
if (!state.configId) {
|
||||
ElMessage.warning('请先选择库名');
|
||||
return;
|
||||
}
|
||||
if (!state.selectedRows.length) {
|
||||
ElMessage.warning('请至少选择一个实体');
|
||||
return;
|
||||
}
|
||||
options.loading = true;
|
||||
try {
|
||||
const params = {
|
||||
configId: state.configId,
|
||||
entityNames: state.selectedRows.map(row => row.name)
|
||||
};
|
||||
await initTable(params);
|
||||
ElMessage.success('生成表操作成功');
|
||||
} catch (error) {
|
||||
ElMessage.error('生成表操作失败');
|
||||
} finally {
|
||||
options.loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 将属性或者函数暴露给父组件
|
||||
defineExpose({ listhandleQuery });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-input),
|
||||
:deep(.el-select),
|
||||
:deep(.el-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
:deep(.el-slider .el-input-number){
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
284
Web/src/views/system/dataInit/component/seedDataList.vue
Normal file
284
Web/src/views/system/dataInit/component/seedDataList.vue
Normal file
@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<el-card shadow="hover" :body-style="{ padding: '20px 20px 16px 10px', display: 'flex', width: '100%', height: '100%', alignItems: 'start' }">
|
||||
<el-form :model="state.queryParams" ref="queryForm" :show-message="false" :inlineMessage="true" label-width="auto" style="flex: 1 1 0%" @submit.prevent="handleQuery" >
|
||||
<el-row :gutter="10">
|
||||
<el-col class="mb5" :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
|
||||
<el-form-item label="库名" prop="configId">
|
||||
<el-select v-model="state.configId" placeholder="库名" filterable @change="handleQueryTable">
|
||||
<el-option v-for="item in state.dbData" :key="item.configId" :label="`${item.dbName}(${item.configId})`" :value="item.configId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<el-divider style="height: calc(100% - 5px); margin: 0 10px" direction="vertical" />
|
||||
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-button-group>
|
||||
<el-button type="primary" icon="ele-Search" @click="handleinitSeedData" :loading="options.loading">
|
||||
生成数据<span v-if="state.selectedRows.length > 0">({{ state.selectedRows.length }})</span>
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<el-card class="full-table" shadow="hover" style="margin-top: 5px">
|
||||
<vxe-grid ref="xGrid" class="xGrid-style" v-bind="options" v-on="gridEvents">
|
||||
<template #row_record="{ row }">
|
||||
<ModifyRecord :data="row" />
|
||||
</template>
|
||||
</vxe-grid>
|
||||
<div style="text-align: right; margin: 7px 8px 7px 0px; color: #666; font-size: 12px;">
|
||||
共 {{ xGrid?.getTableData ? xGrid.getTableData().fullData.length : 0 }} 条记录
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="entityList">
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { ElMessageBox, ElMessage } from "element-plus";
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
|
||||
import { VxeGridInstance, VxeGridListeners, VxeGridPropTypes } from 'vxe-table';
|
||||
import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook';
|
||||
import { Local } from '/@/utils/storage';
|
||||
|
||||
import { formatDate } from '/@/utils/formatTime';
|
||||
|
||||
import { getAPI } from '/@/utils/axios-utils';
|
||||
import { SysDatabaseApi } from '/@/api-services/api';
|
||||
import { getSeedDataList, initSeedData } from '/@/api/system/dataInit';
|
||||
|
||||
// 子窗口对象
|
||||
const xGrid = ref<VxeGridInstance>();
|
||||
const userStore = useUserInfo();
|
||||
|
||||
const dc = userStore.getDictItemByValue;
|
||||
const dv = userStore.getDictItemByLabel;
|
||||
const dl = userStore.getDictDataByCode;
|
||||
|
||||
// 变量
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
dbData: [] as any,
|
||||
configId: '',
|
||||
showAdvanceQueryUI: false,
|
||||
queryParams: {
|
||||
searchKey: undefined,
|
||||
GroupBy: [] as any,
|
||||
projectName: undefined,
|
||||
financingProjectName: undefined,
|
||||
projectType: undefined,
|
||||
constructionStatus: undefined,
|
||||
approvalNumber: undefined,
|
||||
approvalDate: undefined,
|
||||
approvalDateRange: undefined,
|
||||
constructionAddress: undefined,
|
||||
},
|
||||
localPageParam: {
|
||||
pageSize: 50 as number,
|
||||
defaultSort: { field: 'Id', order: 'asc', descStr: 'desc' },
|
||||
},
|
||||
totalSum:[] as any,
|
||||
visible: false,
|
||||
title: '',
|
||||
selectedRows: [] as any[],
|
||||
});
|
||||
|
||||
// 本地存储参数
|
||||
const localPageParamKey = 'localPageParam:entityList';
|
||||
|
||||
// 改变高级查询的控件显示状态
|
||||
const changeAdvanceQueryUI = () => {
|
||||
state.showAdvanceQueryUI = !state.showAdvanceQueryUI;
|
||||
};
|
||||
|
||||
// 校验表格字段权限
|
||||
const checkTableColumnVisible = (tableColumnName: any) => {
|
||||
return !userStore.userTableList.includes(tableColumnName);
|
||||
};
|
||||
|
||||
// 表格参数配置
|
||||
const options = useVxeTable(
|
||||
{
|
||||
id: 'entityList',
|
||||
name: '项目信息',
|
||||
columns: [
|
||||
{ type: 'checkbox', width: 40, fixed: 'left' },
|
||||
{ type: 'seq', title: '序号', width: 50, fixed: 'left' },
|
||||
{ field: 'assemblyName', title: '程序集', minWidth: 120, showOverflow: 'tooltip', sortable: false},
|
||||
{ field: 'name', title: '名称', minWidth: 200, showOverflow: 'tooltip', sortable: false},
|
||||
{ field: 'description', title: '描述', minWidth: 200, showOverflow: 'tooltip', sortable: false},
|
||||
// { title: '操作', fixed: 'right', width: 120, showOverflow: true, slots: { default: 'row_buttons' } },
|
||||
],
|
||||
footerMethod: ({ columns, data }) => {
|
||||
const totalSum=state.totalSum[0];
|
||||
return [
|
||||
// columns.map((column, colIndex) => {
|
||||
// if (colIndex === 0) {
|
||||
// return `合计:`
|
||||
// }
|
||||
// if (column.field === 'estimatedTotalInvestment') {
|
||||
// // 计算表格内总和
|
||||
// return `${data.reduce((sum, row) => sum + (row.estimatedTotalInvestment || 0), 0)}/总计:${totalSum?.estimatedTotalInvestment||0}`
|
||||
// }
|
||||
// })
|
||||
]
|
||||
},
|
||||
},
|
||||
// vxeGrid配置参数(此处可覆写任何参数),参考vxe-table官方文档
|
||||
{
|
||||
// 代理配置//加载时不立刻获取
|
||||
proxyConfig: { autoLoad: false, ajax: { query: ({ page, sort }) => handleQueryApi(page, sort) } },
|
||||
// 排序配置
|
||||
sortConfig: { defaultSort: Local.get(localPageParamKey)?.defaultSort || state.localPageParam.defaultSort },
|
||||
// 关闭分页
|
||||
pagerConfig: { enabled: false },
|
||||
// 工具栏配置
|
||||
toolbarConfig: { export: false },
|
||||
// 行设置
|
||||
// rowConfig: { height: 80 },
|
||||
// 设置列显隐
|
||||
customConfig: {
|
||||
visibleMethod({ column }) {
|
||||
return checkTableColumnVisible(`entityList:${column.field}`);
|
||||
},
|
||||
},
|
||||
showFooter: true, // ✅ 正确控制表尾显示
|
||||
}
|
||||
);
|
||||
|
||||
// 页面初始化
|
||||
onMounted(async () => {
|
||||
options.loading = true;
|
||||
let res = await getAPI(SysDatabaseApi).apiSysDatabaseListGet();
|
||||
state.dbData = res.data.result;
|
||||
|
||||
// let appNamesRes = await getAPI(SysCodeGenApi).apiSysCodeGenApplicationNamespacesGet();
|
||||
// state.appNamespaces = appNamesRes.data.result as Array<string>;
|
||||
options.loading = false;
|
||||
});
|
||||
|
||||
// 查询api
|
||||
const handleQueryApi = async (page: VxeGridPropTypes.ProxyAjaxQueryPageParams, sort: VxeGridPropTypes.ProxyAjaxQuerySortCheckedParams) => {
|
||||
if (state.configId) {
|
||||
return getSeedDataList(state.configId);
|
||||
}
|
||||
};
|
||||
|
||||
// 查询操作
|
||||
const handleQuery = async (reset = false) => {
|
||||
options.loading = true;
|
||||
reset ? await xGrid.value?.commitProxy('reload') : await xGrid.value?.commitProxy('query');
|
||||
options.loading = false;
|
||||
};
|
||||
const listhandleQuery= async (qparams: any) => {
|
||||
await handleQuery(true);
|
||||
};
|
||||
// 重置操作
|
||||
const resetQuery = async () => {
|
||||
state.queryParams.searchKey = undefined;
|
||||
state.queryParams.projectName = undefined;
|
||||
state.queryParams.financingProjectName = undefined;
|
||||
state.queryParams.projectType = undefined;
|
||||
state.queryParams.constructionStatus = undefined;
|
||||
state.queryParams.approvalNumber = undefined;
|
||||
state.queryParams.approvalDate = undefined;
|
||||
state.queryParams.constructionAddress = undefined;
|
||||
await xGrid.value?.commitProxy('reload');
|
||||
};
|
||||
|
||||
|
||||
// 表格事件
|
||||
const gridEvents: VxeGridListeners = {
|
||||
//行单击事件
|
||||
async cellClick({ row, column }) {
|
||||
listClick(row,column);
|
||||
},
|
||||
// 多选变更事件
|
||||
checkboxChange({ records }) {
|
||||
state.selectedRows = records;
|
||||
},
|
||||
// 全选/全不选事件,确保取消全选时 selectedRows 及时清空
|
||||
checkboxAll({ records }) {
|
||||
state.selectedRows = records;
|
||||
},
|
||||
// 只对 pager-config 配置时有效,分页发生改变时会触发该事件
|
||||
async pageChange({ pageSize }) {
|
||||
state.localPageParam.pageSize = pageSize;
|
||||
Local.set(localPageParamKey, state.localPageParam);
|
||||
},
|
||||
// 当排序条件发生变化时会触发该事件
|
||||
async sortChange({ field, order }) {
|
||||
state.localPageParam.defaultSort = { field: field, order: order!, descStr: 'desc' };
|
||||
Local.set(localPageParamKey, state.localPageParam);
|
||||
},
|
||||
};
|
||||
|
||||
// 表查询操作
|
||||
const handleQueryTable = async () => {
|
||||
if (!state.configId) {
|
||||
xGrid.value?.loadData([]);
|
||||
return;
|
||||
}
|
||||
options.loading = true;
|
||||
try {
|
||||
const res = await getSeedDataList(state.configId);
|
||||
const tableData = res?.data?.result ?? [];
|
||||
xGrid.value?.loadData(tableData);
|
||||
} catch (error) {
|
||||
ElMessage.error('加载实体类列表失败');
|
||||
xGrid.value?.loadData([]);
|
||||
} finally {
|
||||
options.loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 与父组件的交互逻辑
|
||||
const emits = defineEmits(['list-click']);
|
||||
const listClick = (row: any,column:any) => {
|
||||
emits('list-click', row,column);
|
||||
};
|
||||
|
||||
// 生成表操作
|
||||
const handleinitSeedData = async () => {
|
||||
if (!state.configId) {
|
||||
ElMessage.warning('请先选择库名');
|
||||
return;
|
||||
}
|
||||
if (!state.selectedRows.length) {
|
||||
ElMessage.warning('请至少选择一个实体');
|
||||
return;
|
||||
}
|
||||
options.loading = true;
|
||||
try {
|
||||
const params = {
|
||||
configId: state.configId,
|
||||
entityNames: state.selectedRows.map(row => row.name)
|
||||
};
|
||||
await initSeedData(params);
|
||||
ElMessage.success('生成种子数据操作成功');
|
||||
} catch (error) {
|
||||
ElMessage.error('生成种子数据操作失败');
|
||||
} finally {
|
||||
options.loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 将属性或者函数暴露给父组件
|
||||
defineExpose({ listhandleQuery });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-input),
|
||||
:deep(.el-select),
|
||||
:deep(.el-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
:deep(.el-slider .el-input-number){
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
62
Web/src/views/system/dataInit/index.vue
Normal file
62
Web/src/views/system/dataInit/index.vue
Normal file
@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="prj-entry-container full-height">
|
||||
<el-row :gutter="8" style="width: 100%; height: 100%; flex: 1">
|
||||
<el-col :span="12" :xs="24" class="full-height">
|
||||
<EntityList ref="EntityListRef"/>
|
||||
</el-col>
|
||||
<el-col :span="12" :xs="24" class="full-height">
|
||||
<SeedDataList ref="SeedDataListRef"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="baseInfo">
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { ElMessageBox, ElMessage } from "element-plus";
|
||||
import EntityList from '/@/views/system/dataInit/component/entityList.vue';
|
||||
import SeedDataList from '/@/views/system/dataInit/component/seedDataList.vue';
|
||||
const EntityListRef = ref<InstanceType<typeof EntityList>>();
|
||||
const SeedDataListRef = ref<InstanceType<typeof SeedDataList>>();
|
||||
// 变量
|
||||
const state = reactive({
|
||||
queryParams: {
|
||||
searchKey: undefined,
|
||||
projectName: undefined,
|
||||
financingProjectName: undefined,
|
||||
projectType: undefined,
|
||||
constructionStatus: undefined,
|
||||
approvalNumber: undefined,
|
||||
approvalDate: undefined,
|
||||
constructionAddress: undefined,
|
||||
}
|
||||
});
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
EntityListRef.value?.listhandleQuery(state.queryParams);//列表控件初始化不请求数据,这里要请求一下
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.prj-entry-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-input),
|
||||
:deep(.el-select),
|
||||
:deep(.el-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
:deep(.el-slider .el-input-number){
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user