😎1、增加单独更新租户种子处理 2、适配人大金仓列排序 3、页面调整

This commit is contained in:
zuohuaijun 2024-08-16 20:05:43 +08:00
parent bb5b8caba2
commit 7525ea2fa0
17 changed files with 149 additions and 28 deletions

View File

@ -39,7 +39,7 @@
<PackageReference Include="SqlSugarCore" Version="5.1.4.166" />
<PackageReference Include="SSH.NET" Version="2024.1.0" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.4.4" />
<PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1069" />
<PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1070" />
<PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup>

View File

@ -133,8 +133,10 @@ public static class RepositoryExtension
/// <returns> </returns>
public static ISugarQueryable<T> OrderBuilder<T>(this ISugarQueryable<T> queryable, BasePageInput pageInput, string prefix = "", string defaultSortField = "Id", bool descSort = true)
{
var iSqlBuilder = InstanceFactory.GetSqlBuilderWithContext(queryable.Context);
// 约定默认每张表都有Id排序
var orderStr = string.IsNullOrWhiteSpace(defaultSortField) ? "" : $"{prefix}{defaultSortField}" + (descSort ? " Desc" : " Asc");
var orderStr = string.IsNullOrWhiteSpace(defaultSortField) ? "" : $"{prefix}{iSqlBuilder.GetTranslationColumnName(defaultSortField)}" + (descSort ? " Desc" : " Asc");
TypeAdapterConfig typeAdapterConfig = new();
typeAdapterConfig.ForType<T, BasePageInput>().IgnoreNullValues(true);
@ -144,9 +146,8 @@ public static class RepositoryExtension
if (!string.IsNullOrEmpty(nowPagerInput.Field) && !string.IsNullOrEmpty(nowPagerInput.Order))
{
var col = queryable.Context.EntityMaintenance.GetEntityInfo<T>().Columns.FirstOrDefault(u => u.PropertyName.Equals(nowPagerInput.Field, StringComparison.CurrentCultureIgnoreCase));
orderStr = col != null
? $"{prefix}{col.DbColumnName} {(nowPagerInput.Order == nowPagerInput.DescStr ? "Desc" : "Asc")}"
: $"{prefix}{nowPagerInput.Field} {(nowPagerInput.Order == nowPagerInput.DescStr ? "Desc" : "Asc")}";
var dbColumnName = col != null ? col.DbColumnName : nowPagerInput.Field;
orderStr = $"{prefix}{iSqlBuilder.GetTranslationColumnName(dbColumnName)} {(nowPagerInput.Order == nowPagerInput.DescStr ? "Desc" : "Asc")}";
}
return queryable.OrderByIF(!string.IsNullOrWhiteSpace(orderStr), orderStr);
}

View File

@ -442,7 +442,6 @@ public class SysTenantService : IDynamicApiController, ITransient
// 默认数据库配置
var defaultConfig = App.GetOptions<DbConnectionOptions>().ConnectionConfigs.FirstOrDefault();
var config = new DbConnectionConfig
{
ConfigId = tenant.Id.ToString(),
@ -455,7 +454,24 @@ public class SysTenantService : IDynamicApiController, ITransient
EnableUnderLine = defaultConfig.DbSettings.EnableUnderLine,
}
};
SqlSugarSetup.InitTenantDatabase(App.GetRequiredService<ISqlSugarClient>().AsTenant(), config);
SqlSugarSetup.InitTenantDatabase(config);
}
/// <summary>
/// 创建租户数据 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("创建租户数据")]
public async Task InitTenantData(TenantInput input)
{
var tenant = await _sysTenantRep.GetSingleAsync(u => u.Id == input.Id);
if (tenant == null) return;
if (string.IsNullOrWhiteSpace(tenant.Connection) || tenant.Connection.Length < 10)
throw Oops.Oh(ErrorCodeEnum.Z1004);
SqlSugarSetup.InitTenantData(_sysTenantRep.AsTenant(), SqlSugarConst.MainConfigId.ToLong());
}
/// <summary>

View File

@ -403,12 +403,12 @@ public static class SqlSugarSetup
/// <summary>
/// 初始化租户业务数据库
/// </summary>
/// <param name="iTenant"></param>
/// <param name="config"></param>
public static void InitTenantDatabase(ITenant iTenant, DbConnectionConfig config)
public static void InitTenantDatabase(DbConnectionConfig config)
{
SetDbConfig(config);
var iTenant = App.GetRequiredService<ISqlSugarClient>().AsTenant();
if (!iTenant.IsAnyConnection(config.ConfigId.ToString()))
iTenant.AddConnection(config);
var db = iTenant.GetConnectionScope(config.ConfigId.ToString());
@ -430,11 +430,24 @@ public static class SqlSugarSetup
db.CodeFirst.SplitTables().InitTables(entityType);
}
// 初始化业务应用种子数据
InitTenantData(iTenant, config.ConfigId.ToLong());
}
/// <summary>
/// 初始化租户业务数据
/// </summary>
/// <param name="iTenant"></param>
/// <param name="dbConfigId">租户Id</param>
public static void InitTenantData(ITenant iTenant, long dbConfigId)
{
// 初始化业务应用种子数据
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(AppSeedAttribute), false))
.OrderBy(u => u.GetCustomAttributes(typeof(SeedDataAttribute), false).Length > 0 ? ((SeedDataAttribute)u.GetCustomAttributes(typeof(SeedDataAttribute), false)[0]).Order : 0).ToList();
if (seedDataTypes == null || seedDataTypes.Count < 1) return;
var db = iTenant.GetConnectionScope(dbConfigId);
foreach (var seedType in seedDataTypes)
{
var instance = Activator.CreateInstance(seedType);
@ -444,7 +457,7 @@ public static class SqlSugarSetup
var entityType = seedType.GetInterfaces().First().GetGenericArguments().First();
var entityInfo = db.EntityMaintenance.GetEntityInfo(entityType);
var dbConfigId = config.ConfigId.ToLong();
// var dbConfigId = config.ConfigId.ToLong();
// 若实体包含租户Id字段则设置为当前租户Id
if (entityInfo.Columns.Any(u => u.PropertyName == nameof(EntityTenantId.TenantId)))
{

View File

@ -54,7 +54,7 @@
"qs": "^6.13.0",
"relation-graph": "^2.2.3",
"screenfull": "^6.0.2",
"sm-crypto-v2": "^1.9.1",
"sm-crypto-v2": "^1.9.2",
"sortablejs": "^1.15.2",
"splitpanes": "^3.1.5",
"vcrontab-3": "^3.3.22",

View File

@ -180,6 +180,54 @@ export const SysTenantApiAxiosParamCreator = function (configuration?: Configura
options: localVarRequestOptions,
};
},
/**
*
* @summary 🔖
* @param {TenantInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
apiSysTenantInitTenantDataPost: async (body?: TenantInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/sysTenant/initTenantData`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, 'https://example.com');
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication Bearer required
// http bearer authentication required
if (configuration && configuration.accessToken) {
const accessToken = typeof configuration.accessToken === 'function'
? await configuration.accessToken()
: await configuration.accessToken;
localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
}
localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
const query = new URLSearchParams(localVarUrlObj.search);
for (const key in localVarQueryParameter) {
query.set(key, localVarQueryParameter[key]);
}
for (const key in options.params) {
query.set(key, options.params[key]);
}
localVarUrlObj.search = (new URLSearchParams(query)).toString();
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
localVarRequestOptions.data = needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
return {
url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
options: localVarRequestOptions,
};
},
/**
*
* @summary 🔖
@ -610,6 +658,20 @@ export const SysTenantApiFp = function(configuration?: Configuration) {
return axios.request(axiosRequestArgs);
};
},
/**
*
* @summary 🔖
* @param {TenantInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysTenantInitTenantDataPost(body?: TenantInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
const localVarAxiosArgs = await SysTenantApiAxiosParamCreator(configuration).apiSysTenantInitTenantDataPost(body, options);
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
return axios.request(axiosRequestArgs);
};
},
/**
*
* @summary 🔖
@ -760,6 +822,16 @@ export const SysTenantApiFactory = function (configuration?: Configuration, base
async apiSysTenantGrantMenuPost(body?: RoleMenuInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
return SysTenantApiFp(configuration).apiSysTenantGrantMenuPost(body, options).then((request) => request(axios, basePath));
},
/**
*
* @summary 🔖
* @param {TenantInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysTenantInitTenantDataPost(body?: TenantInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
return SysTenantApiFp(configuration).apiSysTenantInitTenantDataPost(body, options).then((request) => request(axios, basePath));
},
/**
*
* @summary 🔖
@ -882,6 +954,17 @@ export class SysTenantApi extends BaseAPI {
public async apiSysTenantGrantMenuPost(body?: RoleMenuInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
return SysTenantApiFp(this.configuration).apiSysTenantGrantMenuPost(body, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary 🔖
* @param {TenantInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof SysTenantApi
*/
public async apiSysTenantInitTenantDataPost(body?: TenantInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
return SysTenantApiFp(this.configuration).apiSysTenantInitTenantDataPost(body, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary 🔖

View File

@ -74,7 +74,6 @@
<script lang="ts" setup name="sysCodeGen">
import { onMounted, reactive, ref, defineAsyncComponent } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import { auth } from '/@/utils/authFunction';
import { VxeGridInstance, VxeGridListeners, VxeGridPropTypes } from 'vxe-table';
import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook';
import { Local } from '/@/utils/storage';

View File

@ -129,7 +129,6 @@
<script lang="ts" setup name="sysDict">
import { onMounted, reactive, ref } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import { auth } from '/@/utils/authFunction';
import { VxeGridInstance, VxeGridListeners, VxeGridPropTypes } from 'vxe-table';
import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook';
import { Local } from '/@/utils/storage';

View File

@ -86,7 +86,7 @@
<script lang="ts" setup name="sysLdap">
import { defineAsyncComponent, ref, reactive, onMounted } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import { auth, auths } from '/@/utils/authFunction';
import { auths } from '/@/utils/authFunction';
import { VxeGridInstance, VxeGridListeners, VxeGridPropTypes } from 'vxe-table';
import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook';
import { Local } from '/@/utils/storage';

View File

@ -199,9 +199,8 @@ const options = useVxeTable<SysLogOp>(
);
//
onMounted(async () => {
onMounted(() => {
state.localPageParam = Local.get(localPageParamKey) || state.localPageParam;
await handleQuery();
});
// api

View File

@ -71,7 +71,6 @@
import { onMounted, reactive, ref } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import commonFunction from '/@/utils/commonFunction';
import { auth } from '/@/utils/authFunction';
import { VxeGridInstance, VxeGridListeners, VxeGridPropTypes } from 'vxe-table';
import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook';
import { Local } from '/@/utils/storage';

View File

@ -71,7 +71,6 @@
<script lang="ts" setup name="sysOAuthUser">
import { onMounted, reactive, ref } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import { auth } from '/@/utils/authFunction';
import { VxeGridInstance, VxeGridListeners, VxeGridPropTypes } from 'vxe-table';
import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook';
import { Local } from '/@/utils/storage';

View File

@ -59,7 +59,6 @@
<script lang="ts" setup name="sysOpenAccess">
import { onMounted, reactive, ref } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import { auth } from '/@/utils/authFunction';
import { VxeGridInstance, VxeGridListeners, VxeGridPropTypes } from 'vxe-table';
import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook';
import { Local } from '/@/utils/storage';

View File

@ -57,7 +57,6 @@
<script lang="ts" setup name="sysPlugin">
import { onMounted, reactive, ref } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import { auth } from '/@/utils/authFunction';
import { VxeGridInstance, VxeGridListeners, VxeGridPropTypes } from 'vxe-table';
import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook';
import { Local } from '/@/utils/storage';

View File

@ -57,7 +57,6 @@
<script lang="ts" setup name="sysPrint">
import { onMounted, reactive, ref } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import { auth } from '/@/utils/authFunction';
import { VxeGridInstance, VxeGridListeners, VxeGridPropTypes } from 'vxe-table';
import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook';
import { Local } from '/@/utils/storage';

View File

@ -72,7 +72,7 @@
<script lang="ts" setup name="sysRegion">
import { nextTick, onMounted, reactive, ref } from 'vue';
import { ElMessageBox, ElMessage, ElNotification } from 'element-plus';
import { ElMessageBox, ElMessage } from 'element-plus';
import { VxeGridInstance, VxeGridListeners, VxeGridPropTypes } from 'vxe-table';
import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook';
import { Local } from '/@/utils/storage';

View File

@ -80,13 +80,15 @@
<el-button icon="ele-Edit" size="small" text type="primary" @click="handleEdit(row)" v-auth="'sysTenant/update'" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button icon="ele-Delete" text type="danger" @click="handleDelete(row)" :v-auth="'sysTenant/delete'" :disabled="row.id == 1300000000001"> </el-button>
<el-button icon="ele-Delete" text type="danger" @click="handleDelete(row)" v-auth="'sysTenant/delete'" :disabled="row.id == 1300000000001"> </el-button>
</el-tooltip>
<el-tooltip content="重置密码" placement="top">
<el-button icon="ele-RefreshLeft" text type="danger" @click="resetTenantPwd(row)" :v-auth="'sysTenant/resetPwd'"> </el-button>
<el-button icon="ele-RefreshLeft" text type="danger" @click="resetTenantPwd(row)" v-auth="'sysTenant/resetPwd'"> </el-button>
</el-tooltip>
<el-button icon="ele-Coin" size="small" text type="danger" @click="createTenant(row)" v-auth="'sysTenant/createDb'" :disabled="row.tenantType == 0"> 创建库 </el-button>
<el-button icon="ele-Menu" size="small" text type="primary" @click="openGrantMenu(row)" :v-auth="'sysTenant/grantMenu'"> 授权菜单 </el-button>
<el-button icon="ele-Coin" size="small" text type="danger" @click="createTenantData(row)" :v-auth="'sysTenant/createDb'" v-if="row.tenantType === 0"> 创建租户数据 </el-button>
<el-button icon="ele-Coin" size="small" text type="danger" @click="createTenantDb(row)" :v-auth="'sysTenant/createDb'" v-else> 创建租户库表 </el-button>
<el-button icon="ele-Menu" size="small" text type="primary" @click="openGrantMenu(row)" v-auth="'sysTenant/grantMenu'"> 授权菜单 </el-button>
<el-button icon="ele-Link" size="small" text type="primary" @click="openGrantApi(row)"> 接口黑名单 </el-button>
</template>
</vxe-grid>
@ -153,7 +155,7 @@ const options = useVxeTable<TenantOutput>(
{ field: 'slaveConnections', title: '从库连接', showOverflow: 'tooltip' },
{ field: 'orderNo', title: '排序', width: 80, showOverflow: 'tooltip' },
{ field: '', title: '修改记录', width: 100, showOverflow: 'tooltip', slots: { default: 'row_record' } },
{ title: '操作', fixed: 'right', width: 300, showOverflow: true, slots: { default: 'row_buttons' } },
{ title: '操作', fixed: 'right', width: 430, showOverflow: true, slots: { default: 'row_buttons' } },
],
},
// vxeGrid()vxe-table
@ -246,7 +248,7 @@ const openGrantApi = async (row: any) => {
};
//
const createTenant = (row: any) => {
const createTenantDb = (row: any) => {
ElMessageBox.confirm(`确定创建/更新租户数据库:【${row.name}】?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@ -259,6 +261,20 @@ const createTenant = (row: any) => {
.catch(() => {});
};
//
const createTenantData = (row: any) => {
ElMessageBox.confirm(`确定创建/更新租户数据:【${row.name}】?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
await getAPI(SysTenantApi).apiSysTenantInitTenantDataPost({ id: row.id });
ElMessage.success('创建/更新租户数据库成功');
})
.catch(() => {});
};
//
const changeStatus = async (scope: any) => {
getAPI(SysTenantApi)