Browse Source

添加数据字典UI

pull/27/head
王军 4 years ago
parent
commit
05110338c3
  1. 17
      aspnet-core/modules/DataDictionaryManagement/src/Lion.AbpPro.DataDictionaryManagement.Application.Contracts/DataDictionaries/Dtos/UpdateDetailInput.cs
  2. 8
      aspnet-core/modules/DataDictionaryManagement/src/Lion.AbpPro.DataDictionaryManagement.Application.Contracts/DataDictionaries/IDataDictionaryAppService.cs
  3. 36
      aspnet-core/modules/DataDictionaryManagement/src/Lion.AbpPro.DataDictionaryManagement.Application/DataDictionaries/DataDictionaryAppService.cs
  4. 23
      aspnet-core/modules/DataDictionaryManagement/src/Lion.AbpPro.DataDictionaryManagement.Domain/DataDictionaries/Aggregates/DataDictionaryDetail.cs
  5. 27
      aspnet-core/modules/DataDictionaryManagement/src/Lion.AbpPro.DataDictionaryManagement.Domain/DataDictionaries/DataDictionaryManager.cs
  6. 11
      aspnet-core/modules/DataDictionaryManagement/src/Lion.AbpPro.DataDictionaryManagement.HttpApi/DataDictionaries/DataDictionaryController.cs
  7. 8
      vben271/src/locales/lang/en/routes/admin.ts
  8. 8
      vben271/src/locales/lang/zh-CN/routes/admin.ts
  9. 10
      vben271/src/router/routes/modules/admin.ts
  10. 254
      vben271/src/services/ServiceProxies.ts
  11. 335
      vben271/src/views/admin/dictionary/AbpDictionary.ts
  12. 185
      vben271/src/views/admin/dictionary/AbpDictionary.vue
  13. 77
      vben271/src/views/admin/dictionary/CreateAbpDictionary.vue
  14. 69
      vben271/src/views/admin/dictionary/CreateAbpDictionaryType.vue
  15. 74
      vben271/src/views/admin/dictionary/EditAbpDictionary.vue

17
aspnet-core/modules/DataDictionaryManagement/src/Lion.AbpPro.DataDictionaryManagement.Application.Contracts/DataDictionaries/Dtos/UpdateDetailInput.cs

@ -0,0 +1,17 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries.Dtos;
public class UpdateDetailInput
{
[Required] public Guid DataDictionaryId { get; set; }
[Required] public Guid Id { get; set; }
[Required] public string DisplayText { get; set; }
public string Description { get; set; }
public int Order { get; set; }
}

8
aspnet-core/modules/DataDictionaryManagement/src/Lion.AbpPro.DataDictionaryManagement.Application.Contracts/DataDictionaries/IDataDictionaryAppService.cs

@ -15,8 +15,7 @@ namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PagedResultDto<PagingDataDictionaryOutput>> GetPagingListAsync(
PagingDataDictionaryInput input,
CancellationToken cancellationToken = default);
PagingDataDictionaryInput input);
/// <summary>
/// 分页查询字典项明细
@ -25,8 +24,7 @@ namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PagedResultDto<PagingDataDictionaryDetailOutput>> GetPagingDetailListAsync(
PagingDataDictionaryDetailInput input,
CancellationToken cancellationToken = default);
PagingDataDictionaryDetailInput input);
/// <summary>
/// 创建字典类型
@ -43,5 +41,7 @@ namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
/// 设置字典明细状态
/// </summary>
Task SetStatus(SetDataDictinaryDetailInput input);
Task UpdateDetailAsync(UpdateDetailInput input);
}
}

36
aspnet-core/modules/DataDictionaryManagement/src/Lion.AbpPro.DataDictionaryManagement.Application/DataDictionaries/DataDictionaryAppService.cs

@ -4,6 +4,7 @@ using System.Threading;
using System.Threading.Tasks;
using Lion.AbpPro.DataDictionaryManagement.DataDictionaries.Aggregates;
using Lion.AbpPro.DataDictionaryManagement.DataDictionaries.Dtos;
using Lion.AbpPro.Extension.System;
using Volo.Abp.Application.Dtos;
namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
@ -16,8 +17,9 @@ namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
/// 如果是其他的操作全部通过对应manger进行操作
/// </summary>
private readonly IDataDictionaryRepository _dataDictionaryRepository;
private readonly DataDictionaryManager _dataDictionaryManager;
public DataDictionaryAppService(
IDataDictionaryRepository dataDictionaryRepository,
DataDictionaryManager dataDictionaryManager)
@ -33,21 +35,21 @@ namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<PagedResultDto<PagingDataDictionaryOutput>> GetPagingListAsync(
PagingDataDictionaryInput input,
CancellationToken cancellationToken = default)
PagingDataDictionaryInput input)
{
var result = new PagedResultDto<PagingDataDictionaryOutput>();
var totalCount = await _dataDictionaryRepository.GetPagingCountAsync(input.Filter, cancellationToken);
var totalCount = await _dataDictionaryRepository.GetPagingCountAsync(input.Filter);
result.TotalCount = totalCount;
if (totalCount <= 0) return result;
var entities = await _dataDictionaryRepository.GetPagingListAsync(input.Filter, input.PageSize,
input.SkipCount, false, cancellationToken);
input.SkipCount, false);
result.Items = ObjectMapper.Map<List<DataDictionary>, List<PagingDataDictionaryOutput>>(entities);
return result;
}
/// <summary>
/// 分页查询字典项明细
/// </summary>
@ -55,17 +57,19 @@ namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<PagedResultDto<PagingDataDictionaryDetailOutput>> GetPagingDetailListAsync(
PagingDataDictionaryDetailInput input,
CancellationToken cancellationToken = default)
PagingDataDictionaryDetailInput input)
{
var entity = await _dataDictionaryRepository.FindByIdAsync(input.DataDictionaryId, true, cancellationToken);
var details = entity.Details.Take(input.PageSize).Skip(input.SkipCount).ToList();
var entity = await _dataDictionaryRepository.FindByIdAsync(input.DataDictionaryId, true);
var details = entity.Details
.WhereIf(input.Filter.IsNotNullOrWhiteSpace(), e => (e.Code.Contains(input.Filter) || e.DisplayText.Contains(input.Filter)))
.OrderBy(e => e.Order)
.Take(input.PageSize).Skip(input.SkipCount).ToList();
return new PagedResultDto<PagingDataDictionaryDetailOutput>(
entity.Details.Count,
ObjectMapper.Map<List<DataDictionaryDetail>, List<PagingDataDictionaryDetailOutput>>(details));
}
/// <summary>
/// 创建字典类型
/// </summary>
@ -78,7 +82,7 @@ namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
/// <summary>
/// 新增字典明细
/// </summary>
public Task CreateDetailAsync(CreateDataDictinaryDetailInput input)
public Task CreateDetailAsync(CreateDataDictinaryDetailInput input)
{
return _dataDictionaryManager.CreateDetailAsync(input.Id, input.Code, input.DisplayText, input.Description,
input.Order);
@ -87,10 +91,16 @@ namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
/// <summary>
/// 设置字典明细状态
/// </summary>
public Task SetStatus(SetDataDictinaryDetailInput input)
public Task SetStatus(SetDataDictinaryDetailInput input)
{
return _dataDictionaryManager.SetStatus(input.DataDictionaryId, input.DataDictionayDetailId,
input.IsEnabled);
}
public Task UpdateDetailAsync(UpdateDetailInput input)
{
return _dataDictionaryManager.UpdateDetailAsync(input.DataDictionaryId, input.Id, input.DisplayText, input.Description,
input.Order);
}
}
}

23
aspnet-core/modules/DataDictionaryManagement/src/Lion.AbpPro.DataDictionaryManagement.Domain/DataDictionaries/Aggregates/DataDictionaryDetail.cs

@ -61,32 +61,43 @@ namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries.Aggregates
SetDescription(description);
}
public void SetCode(string code)
private void SetCode(string code)
{
Guard.NotNullOrWhiteSpace(code, nameof(code), DataDictionaryMaxLengths.Code);
Code = code;
}
public void SetOrder(int order)
private void SetOrder(int order)
{
Order = order;
}
public void SetDisplayText(string displayText)
private void SetDisplayText(string displayText)
{
Guard.NotNullOrWhiteSpace(displayText, nameof(displayText), DataDictionaryMaxLengths.DisplayText);
DisplayText = displayText;
}
private void SetDescription(string description)
{
Guard.Length(description, nameof(description), DataDictionaryMaxLengths.Description);
Description = Description = description ?? string.Empty;
}
public void SetIsEnabled(bool isEnabled)
{
IsEnabled = isEnabled;
}
public void SetDescription(string description)
public void UpdateDetail(
Guid dataDictionayDetailId,
string displayText,
string description,
int order)
{
Guard.Length(description, nameof(description), DataDictionaryMaxLengths.Description);
Description = Description = description ?? string.Empty;
SetDescription(description);
SetDisplayText(displayText);
SetOrder(order);
}
}
}

27
aspnet-core/modules/DataDictionaryManagement/src/Lion.AbpPro.DataDictionaryManagement.Domain/DataDictionaries/DataDictionaryManager.cs

@ -10,7 +10,7 @@ using Volo.Abp.Domain.Services;
namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
{
public class DataDictionaryManager : DataDictionaryDomainService
public class DataDictionaryManager : DataDictionaryDomainService
{
private readonly IDataDictionaryRepository _dataDictionaryRepository;
private readonly IDistributedCache<DataDictionaryDto> _cache;
@ -89,7 +89,7 @@ namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
}
entity.AddDetail(GuidGenerator.Create(), code, displayText, order, description);
return await _dataDictionaryRepository.InsertAsync(entity);
return await _dataDictionaryRepository.UpdateAsync(entity);
}
/// <summary>
@ -110,5 +110,28 @@ namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
detail.SetIsEnabled(isEnabled);
return await _dataDictionaryRepository.UpdateAsync(entity);
}
/// <summary>
/// 更新数据字典明细
/// </summary>
public async Task<DataDictionary> UpdateDetailAsync(
Guid dataDictionaryId,
Guid dataDictionayDetailId,
string displayText,
string description,
int order)
{
var entity = await _dataDictionaryRepository.FindByIdAsync(dataDictionaryId);
if (entity == null)
throw new DataDictionaryDomainException(message: "数据字典不存在");
var detail = entity.Details.FirstOrDefault(e => e.Id == dataDictionayDetailId);
if (null == detail)
{
throw new DataDictionaryDomainException(message: $"字典项不存在");
}
detail.UpdateDetail(dataDictionayDetailId,displayText,description,order);
return await _dataDictionaryRepository.UpdateAsync(entity);
}
}
}

11
aspnet-core/modules/DataDictionaryManagement/src/Lion.AbpPro.DataDictionaryManagement.HttpApi/DataDictionaries/DataDictionaryController.cs

@ -8,7 +8,7 @@ using Volo.Abp.Application.Services;
namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
{
[Route("DataDictionary")]
public class DataDictionaryController : DataDictionaryManagementController, IApplicationService
public class DataDictionaryController : DataDictionaryManagementController, IDataDictionaryAppService
{
private readonly IDataDictionaryAppService _dataDictionaryAppService;
@ -25,7 +25,7 @@ namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
return _dataDictionaryAppService.GetPagingListAsync(input);
}
[HttpPost("page/detail")]
[HttpPost("pageDetail")]
[SwaggerOperation(summary: "分页字典明细", Tags = new[] { "DataDictionary" })]
public Task<PagedResultDto<PagingDataDictionaryDetailOutput>> GetPagingDetailListAsync(
PagingDataDictionaryDetailInput input)
@ -53,5 +53,12 @@ namespace Lion.AbpPro.DataDictionaryManagement.DataDictionaries
{
return _dataDictionaryAppService.SetStatus(input);
}
[HttpPost("updateDetail")]
[SwaggerOperation(summary: "更新字典明细", Tags = new[] { "DataDictionary" })]
public Task UpdateDetailAsync(UpdateDetailInput input)
{
return _dataDictionaryAppService.UpdateDetailAsync(input);
}
}
}

8
vben271/src/locales/lang/en/routes/admin.ts

@ -52,4 +52,12 @@ export default {
logLevel: 'Level',
logContent: 'Content',
settingManagement: 'SettingManagement',
dictionaryManagement: 'DataDictionary',
dictionaryTypeName: 'Type',
dictionaryCode: 'Code',
dictionaryDisplayText: 'Name',
dictionaryDescription: 'Description',
dictionaryOrder: 'Order',
chooseDictionary: 'Please Choose DataDictionary Type',
nonZeroMessage: 'Please enter a non-zero positive integer',
};

8
vben271/src/locales/lang/zh-CN/routes/admin.ts

@ -51,4 +51,12 @@ export default {
logContent: '内容',
detail: '详情',
settingManagement: '设置管理',
dictionaryManagement: '数据字典',
dictionaryTypeName: '字典类型',
dictionaryCode: '编码',
dictionaryDisplayText: '名称',
dictionaryDescription: '描述',
dictionaryOrder: '排序',
chooseDictionary: '请选择字典类型',
nonZeroMessage: '请输入非零的正整数',
};

10
vben271/src/router/routes/modules/admin.ts

@ -63,6 +63,16 @@ const admin: AppRouteModule = {
icon: 'ant-design:snippets-twotone',
},
},
{
path: 'dataDictionary',
name: 'dataDictionary',
component: () => import('/@/views/admin/dictionary/AbpDictionary.vue'),
meta: {
title: t('routes.admin.esLogs'),
icon: 'ant-design:snippets-twotone',
},
},
],
};

254
vben271/src/services/ServiceProxies.ts

@ -2563,7 +2563,7 @@ export class ClientServiceProxy extends ServiceProxyBase {
}
}
export class EsLogServiceProxy extends ServiceProxyBase {
export class DataDictionaryServiceProxy extends ServiceProxyBase {
private instance: AxiosInstance;
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
@ -2575,12 +2575,12 @@ export class EsLogServiceProxy extends ServiceProxyBase {
}
/**
* Es日志
*
* @param body (optional)
* @return Success
*/
page(body: PagingElasticSearchLogInput | undefined , cancelToken?: CancelToken | undefined): Promise<PagingElasticSearchLogOutputCustomePagedResultDto> {
let url_ = this.baseUrl + "/EsLog/page";
page(body: PagingDataDictionaryInput | undefined , cancelToken?: CancelToken | undefined): Promise<PagingDataDictionaryOutputPagedResultDto> {
let url_ = this.baseUrl + "/DataDictionary/page";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(body);
@ -2609,7 +2609,7 @@ export class EsLogServiceProxy extends ServiceProxyBase {
});
}
protected processPage(response: AxiosResponse): Promise<PagingElasticSearchLogOutputCustomePagedResultDto> {
protected processPage(response: AxiosResponse): Promise<PagingDataDictionaryOutputPagedResultDto> {
const status = response.status;
let _headers: any = {};
if (response.headers && typeof response.headers === "object") {
@ -2623,7 +2623,7 @@ export class EsLogServiceProxy extends ServiceProxyBase {
const _responseText = response.data;
let result200: any = null;
let resultData200 = _responseText;
result200 = PagingElasticSearchLogOutputCustomePagedResultDto.fromJS(resultData200);
result200 = PagingDataDictionaryOutputPagedResultDto.fromJS(resultData200);
return result200;
} else if (status === 403) {
const _responseText = response.data;
@ -2665,28 +2665,16 @@ export class EsLogServiceProxy extends ServiceProxyBase {
const _responseText = response.data;
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
}
return Promise.resolve<PagingElasticSearchLogOutputCustomePagedResultDto>(<any>null);
}
}
export class DataDictionaryServiceProxy extends ServiceProxyBase {
private instance: AxiosInstance;
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(baseUrl?: string, instance?: AxiosInstance) {
super();
this.instance = instance ? instance : axios.create();
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "";
return Promise.resolve<PagingDataDictionaryOutputPagedResultDto>(<any>null);
}
/**
*
*
* @param body (optional)
* @return Success
*/
page(body: PagingDataDictionaryInput | undefined , cancelToken?: CancelToken | undefined): Promise<PagingDataDictionaryOutputPagedResultDto> {
let url_ = this.baseUrl + "/DataDictionary/page";
pageDetail(body: PagingDataDictionaryDetailInput | undefined , cancelToken?: CancelToken | undefined): Promise<PagingDataDictionaryDetailOutputPagedResultDto> {
let url_ = this.baseUrl + "/DataDictionary/pageDetail";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(body);
@ -2711,11 +2699,11 @@ export class DataDictionaryServiceProxy extends ServiceProxyBase {
throw _error;
}
}).then((_response: AxiosResponse) => {
return this.transformResult(url_, _response, (_response: AxiosResponse) => this.processPage(_response));
return this.transformResult(url_, _response, (_response: AxiosResponse) => this.processPageDetail(_response));
});
}
protected processPage(response: AxiosResponse): Promise<PagingDataDictionaryOutputPagedResultDto> {
protected processPageDetail(response: AxiosResponse): Promise<PagingDataDictionaryDetailOutputPagedResultDto> {
const status = response.status;
let _headers: any = {};
if (response.headers && typeof response.headers === "object") {
@ -2729,7 +2717,7 @@ export class DataDictionaryServiceProxy extends ServiceProxyBase {
const _responseText = response.data;
let result200: any = null;
let resultData200 = _responseText;
result200 = PagingDataDictionaryOutputPagedResultDto.fromJS(resultData200);
result200 = PagingDataDictionaryDetailOutputPagedResultDto.fromJS(resultData200);
return result200;
} else if (status === 403) {
const _responseText = response.data;
@ -2771,16 +2759,16 @@ export class DataDictionaryServiceProxy extends ServiceProxyBase {
const _responseText = response.data;
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
}
return Promise.resolve<PagingDataDictionaryOutputPagedResultDto>(<any>null);
return Promise.resolve<PagingDataDictionaryDetailOutputPagedResultDto>(<any>null);
}
/**
*
*
* @param body (optional)
* @return Success
*/
detail(body: PagingDataDictionaryDetailInput | undefined , cancelToken?: CancelToken | undefined): Promise<PagingDataDictionaryDetailOutputPagedResultDto> {
let url_ = this.baseUrl + "/DataDictionary/page/detail";
create(body: CreateDataDictinaryInput | undefined , cancelToken?: CancelToken | undefined): Promise<void> {
let url_ = this.baseUrl + "/DataDictionary/create";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(body);
@ -2791,7 +2779,6 @@ export class DataDictionaryServiceProxy extends ServiceProxyBase {
url: url_,
headers: {
"Content-Type": "application/json",
"Accept": "text/plain"
},
cancelToken
};
@ -2805,11 +2792,11 @@ export class DataDictionaryServiceProxy extends ServiceProxyBase {
throw _error;
}
}).then((_response: AxiosResponse) => {
return this.transformResult(url_, _response, (_response: AxiosResponse) => this.processDetail(_response));
return this.transformResult(url_, _response, (_response: AxiosResponse) => this.processCreate(_response));
});
}
protected processDetail(response: AxiosResponse): Promise<PagingDataDictionaryDetailOutputPagedResultDto> {
protected processCreate(response: AxiosResponse): Promise<void> {
const status = response.status;
let _headers: any = {};
if (response.headers && typeof response.headers === "object") {
@ -2821,10 +2808,7 @@ export class DataDictionaryServiceProxy extends ServiceProxyBase {
}
if (status === 200) {
const _responseText = response.data;
let result200: any = null;
let resultData200 = _responseText;
result200 = PagingDataDictionaryDetailOutputPagedResultDto.fromJS(resultData200);
return result200;
return Promise.resolve<void>(<any>null);
} else if (status === 403) {
const _responseText = response.data;
let result403: any = null;
@ -2865,16 +2849,16 @@ export class DataDictionaryServiceProxy extends ServiceProxyBase {
const _responseText = response.data;
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
}
return Promise.resolve<PagingDataDictionaryDetailOutputPagedResultDto>(<any>null);
return Promise.resolve<void>(<any>null);
}
/**
*
*
* @param body (optional)
* @return Success
*/
create(body: CreateDataDictinaryInput | undefined , cancelToken?: CancelToken | undefined): Promise<void> {
let url_ = this.baseUrl + "/DataDictionary/create";
createDetail(body: CreateDataDictinaryDetailInput | undefined , cancelToken?: CancelToken | undefined): Promise<void> {
let url_ = this.baseUrl + "/DataDictionary/createDetail";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(body);
@ -2898,11 +2882,11 @@ export class DataDictionaryServiceProxy extends ServiceProxyBase {
throw _error;
}
}).then((_response: AxiosResponse) => {
return this.transformResult(url_, _response, (_response: AxiosResponse) => this.processCreate(_response));
return this.transformResult(url_, _response, (_response: AxiosResponse) => this.processCreateDetail(_response));
});
}
protected processCreate(response: AxiosResponse): Promise<void> {
protected processCreateDetail(response: AxiosResponse): Promise<void> {
const status = response.status;
let _headers: any = {};
if (response.headers && typeof response.headers === "object") {
@ -2959,12 +2943,12 @@ export class DataDictionaryServiceProxy extends ServiceProxyBase {
}
/**
*
*
* @param body (optional)
* @return Success
*/
createDetail(body: CreateDataDictinaryDetailInput | undefined , cancelToken?: CancelToken | undefined): Promise<void> {
let url_ = this.baseUrl + "/DataDictionary/createDetail";
status(body: SetDataDictinaryDetailInput | undefined , cancelToken?: CancelToken | undefined): Promise<void> {
let url_ = this.baseUrl + "/DataDictionary/status";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(body);
@ -2988,11 +2972,11 @@ export class DataDictionaryServiceProxy extends ServiceProxyBase {
throw _error;
}
}).then((_response: AxiosResponse) => {
return this.transformResult(url_, _response, (_response: AxiosResponse) => this.processCreateDetail(_response));
return this.transformResult(url_, _response, (_response: AxiosResponse) => this.processStatus(_response));
});
}
protected processCreateDetail(response: AxiosResponse): Promise<void> {
protected processStatus(response: AxiosResponse): Promise<void> {
const status = response.status;
let _headers: any = {};
if (response.headers && typeof response.headers === "object") {
@ -3049,12 +3033,12 @@ export class DataDictionaryServiceProxy extends ServiceProxyBase {
}
/**
*
*
* @param body (optional)
* @return Success
*/
status(body: SetDataDictinaryDetailInput | undefined , cancelToken?: CancelToken | undefined): Promise<void> {
let url_ = this.baseUrl + "/DataDictionary/status";
updateDetail(body: UpdateDetailInput | undefined , cancelToken?: CancelToken | undefined): Promise<void> {
let url_ = this.baseUrl + "/DataDictionary/updateDetail";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(body);
@ -3078,11 +3062,11 @@ export class DataDictionaryServiceProxy extends ServiceProxyBase {
throw _error;
}
}).then((_response: AxiosResponse) => {
return this.transformResult(url_, _response, (_response: AxiosResponse) => this.processStatus(_response));
return this.transformResult(url_, _response, (_response: AxiosResponse) => this.processUpdateDetail(_response));
});
}
protected processStatus(response: AxiosResponse): Promise<void> {
protected processUpdateDetail(response: AxiosResponse): Promise<void> {
const status = response.status;
let _headers: any = {};
if (response.headers && typeof response.headers === "object") {
@ -3724,6 +3708,112 @@ export class IdentityResourceServiceProxy extends ServiceProxyBase {
}
}
export class EsLogServiceProxy extends ServiceProxyBase {
private instance: AxiosInstance;
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(baseUrl?: string, instance?: AxiosInstance) {
super();
this.instance = instance ? instance : axios.create();
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "";
}
/**
* Es日志
* @param body (optional)
* @return Success
*/
page(body: PagingElasticSearchLogInput | undefined , cancelToken?: CancelToken | undefined): Promise<PagingElasticSearchLogOutputCustomePagedResultDto> {
let url_ = this.baseUrl + "/EsLog/page";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(body);
let options_ = <AxiosRequestConfig>{
data: content_,
method: "POST",
url: url_,
headers: {
"Content-Type": "application/json",
"Accept": "text/plain"
},
cancelToken
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.instance.request(transformedOptions_);
}).catch((_error: any) => {
if (isAxiosError(_error) && _error.response) {
return _error.response;
} else {
throw _error;
}
}).then((_response: AxiosResponse) => {
return this.transformResult(url_, _response, (_response: AxiosResponse) => this.processPage(_response));
});
}
protected processPage(response: AxiosResponse): Promise<PagingElasticSearchLogOutputCustomePagedResultDto> {
const status = response.status;
let _headers: any = {};
if (response.headers && typeof response.headers === "object") {
for (let k in response.headers) {
if (response.headers.hasOwnProperty(k)) {
_headers[k] = response.headers[k];
}
}
}
if (status === 200) {
const _responseText = response.data;
let result200: any = null;
let resultData200 = _responseText;
result200 = PagingElasticSearchLogOutputCustomePagedResultDto.fromJS(resultData200);
return result200;
} else if (status === 403) {
const _responseText = response.data;
let result403: any = null;
let resultData403 = _responseText;
result403 = RemoteServiceErrorResponse.fromJS(resultData403);
return throwException("Forbidden", status, _responseText, _headers, result403);
} else if (status === 401) {
const _responseText = response.data;
let result401: any = null;
let resultData401 = _responseText;
result401 = RemoteServiceErrorResponse.fromJS(resultData401);
return throwException("Unauthorized", status, _responseText, _headers, result401);
} else if (status === 400) {
const _responseText = response.data;
let result400: any = null;
let resultData400 = _responseText;
result400 = RemoteServiceErrorResponse.fromJS(resultData400);
return throwException("Bad Request", status, _responseText, _headers, result400);
} else if (status === 404) {
const _responseText = response.data;
let result404: any = null;
let resultData404 = _responseText;
result404 = RemoteServiceErrorResponse.fromJS(resultData404);
return throwException("Not Found", status, _responseText, _headers, result404);
} else if (status === 501) {
const _responseText = response.data;
let result501: any = null;
let resultData501 = _responseText;
result501 = RemoteServiceErrorResponse.fromJS(resultData501);
return throwException("Server Error", status, _responseText, _headers, result501);
} else if (status === 500) {
const _responseText = response.data;
let result500: any = null;
let resultData500 = _responseText;
result500 = RemoteServiceErrorResponse.fromJS(resultData500);
return throwException("Server Error", status, _responseText, _headers, result500);
} else if (status !== 200 && status !== 204) {
const _responseText = response.data;
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
}
return Promise.resolve<PagingElasticSearchLogOutputCustomePagedResultDto>(<any>null);
}
}
export class NotificationServiceProxy extends ServiceProxyBase {
private instance: AxiosInstance;
private baseUrl: string;
@ -8607,6 +8697,8 @@ export class CurrentUserDto implements ICurrentUserDto {
tenantId!: string | undefined;
impersonatorUserId!: string | undefined;
impersonatorTenantId!: string | undefined;
impersonatorUserName!: string | undefined;
impersonatorTenantName!: string | undefined;
userName!: string | undefined;
name!: string | undefined;
surName!: string | undefined;
@ -8632,6 +8724,8 @@ export class CurrentUserDto implements ICurrentUserDto {
this.tenantId = _data["tenantId"];
this.impersonatorUserId = _data["impersonatorUserId"];
this.impersonatorTenantId = _data["impersonatorTenantId"];
this.impersonatorUserName = _data["impersonatorUserName"];
this.impersonatorTenantName = _data["impersonatorTenantName"];
this.userName = _data["userName"];
this.name = _data["name"];
this.surName = _data["surName"];
@ -8661,6 +8755,8 @@ export class CurrentUserDto implements ICurrentUserDto {
data["tenantId"] = this.tenantId;
data["impersonatorUserId"] = this.impersonatorUserId;
data["impersonatorTenantId"] = this.impersonatorTenantId;
data["impersonatorUserName"] = this.impersonatorUserName;
data["impersonatorTenantName"] = this.impersonatorTenantName;
data["userName"] = this.userName;
data["name"] = this.name;
data["surName"] = this.surName;
@ -8683,6 +8779,8 @@ export interface ICurrentUserDto {
tenantId: string | undefined;
impersonatorUserId: string | undefined;
impersonatorTenantId: string | undefined;
impersonatorUserName: string | undefined;
impersonatorTenantName: string | undefined;
userName: string | undefined;
name: string | undefined;
surName: string | undefined;
@ -16172,6 +16270,58 @@ export interface IUpdateCreateApiScopeInput {
showInDiscoveryDocument: boolean;
}
export class UpdateDetailInput implements IUpdateDetailInput {
dataDictionaryId!: string;
id!: string;
displayText!: string;
description!: string | undefined;
order!: number;
constructor(data?: IUpdateDetailInput) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
(<any>this)[property] = (<any>data)[property];
}
}
}
init(_data?: any) {
if (_data) {
this.dataDictionaryId = _data["dataDictionaryId"];
this.id = _data["id"];
this.displayText = _data["displayText"];
this.description = _data["description"];
this.order = _data["order"];
}
}
static fromJS(data: any): UpdateDetailInput {
data = typeof data === 'object' ? data : {};
let result = new UpdateDetailInput();
result.init(data);
return result;
}
toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
data["dataDictionaryId"] = this.dataDictionaryId;
data["id"] = this.id;
data["displayText"] = this.displayText;
data["description"] = this.description;
data["order"] = this.order;
return data;
}
}
export interface IUpdateDetailInput {
dataDictionaryId: string;
id: string;
displayText: string;
description: string | undefined;
order: number;
}
export class UpdateEmailSettingsDto implements IUpdateEmailSettingsDto {
smtpHost!: string | undefined;
smtpPort!: number;

335
vben271/src/views/admin/dictionary/AbpDictionary.ts

@ -0,0 +1,335 @@
import { FormSchema } from '/@/components/Table';
import { BasicColumn } from '/@/components/Table';
import { message } from 'ant-design-vue';
import { useI18n } from '/@/hooks/web/useI18n';
import {
PagingDataDictionaryInput,
DataDictionaryServiceProxy,
SetDataDictinaryDetailInput,
} from '/@/services/ServiceProxies';
import { h } from 'vue';
import { Switch } from 'ant-design-vue';
const { t } = useI18n();
export const tableColumns: BasicColumn[] = [
{
title: t('routes.admin.dictionaryCode'),
dataIndex: 'code',
},
{
title: t('routes.admin.dictionaryDisplayText'),
dataIndex: 'displayText',
},
{
title: t('routes.admin.dictionaryOrder'),
dataIndex: 'order',
},
{
title: t('common.status'),
dataIndex: 'isEnabled',
customRender: ({ record }) => {
return h(Switch, {
checked: record.isEnabled,
checkedChildren: '是',
unCheckedChildren: '否',
onChange(checked: boolean) {
const request = new SetDataDictinaryDetailInput();
request.dataDictionaryId = record.dataDictionaryId;
(request.dataDictionayDetailId = record.id), (request.isEnabled = checked);
enableDictionaryAsync(request)
.then(() => {
record.isEnabled = checked;
message.success(t('common.operationSuccess'));
})
.catch(() => {
message.error(t('common.operationFail'));
});
},
});
},
},
{
dataIndex: 'description',
title: t('routes.admin.dictionaryDescription'),
},
];
//字典类型表格
export const dictionaryTypeTableColumns: BasicColumn[] = [
{
title: t('routes.admin.dictionaryTypeName'),
dataIndex: 'displayText',
},
];
//字典项查询
export const searchFormSchema: FormSchema[] = [
{
field: 'filter',
label: '',
component: 'Input',
colProps: {
span: 6,
},
},
];
//字典类型查询
export const searchDictionaryFormSchema: FormSchema[] = [
{
field: 'filter',
label: '',
component: 'Input',
colProps: {
span: 18,
},
},
];
//新增字典项
export const createFormSchema: FormSchema[] = [
{
field: 'id',
label: '',
ifShow: false,
component: 'Input',
colProps: {
span: 18,
},
},
{
field: 'typeDisplayText',
label: t('routes.admin.dictionaryTypeName'),
component: 'Input',
colProps: {
span: 18,
},
componentProps: {
disabled: true,
},
},
{
field: 'code',
label: t('routes.admin.dictionaryCode'),
required: true,
component: 'Input',
colProps: {
span: 18,
},
},
{
field: 'displayText',
label: t('routes.admin.dictionaryDisplayText'),
component: 'Input',
required: true,
colProps: {
span: 18,
},
},
{
field: 'order',
label: t('routes.admin.dictionaryOrder'),
required: true,
component: 'InputNumber',
colProps: {
span: 18,
},
dynamicRules: () => {
return [
{
required: true,
validator: (_, value) => {
const regNull = /^[1-9]\d*$/;
if (regNull.test(value)) {
return Promise.resolve();
}
return Promise.reject(t('routes.admin.nonZeroMessage'));
},
},
];
},
},
{
field: 'description',
label: t('routes.admin.dictionaryDescription'),
component: 'InputTextArea',
colProps: {
span: 18,
},
},
];
//编辑字典项
export const editFormSchema: FormSchema[] = [
{
field: 'dataDictionaryId',
label: '',
ifShow: false,
component: 'Input',
colProps: {
span: 18,
},
},
{
field: 'id',
label: '',
ifShow: false,
component: 'Input',
colProps: {
span: 18,
},
},
{
field: 'code',
label: t('routes.admin.dictionaryCode'),
required: true,
component: 'Input',
colProps: {
span: 18,
},
componentProps: {
disabled: true,
},
},
{
field: 'displayText',
label: t('routes.admin.dictionaryDisplayText'),
component: 'Input',
required: true,
colProps: {
span: 18,
},
},
{
field: 'order',
label: t('routes.admin.dictionaryOrder'),
required: true,
component: 'InputNumber',
colProps: {
span: 18,
},
dynamicRules: () => {
return [
{
required: true,
validator: (_, value) => {
const regNull = /^[1-9]\d*$/;
if (regNull.test(value)) {
return Promise.resolve();
}
return Promise.reject(t('routes.admin.nonZeroMessage'));
},
},
];
},
},
{
field: 'description',
label: t('routes.admin.dictionaryDescription'),
component: 'InputTextArea',
colProps: {
span: 18,
},
},
];
//新增字典类型
export const createDictionaryTypeFormSchema: FormSchema[] = [
{
field: 'code',
label: t('routes.admin.dictionaryCode'),
component: 'Input',
required: true,
colProps: {
span: 22,
},
},
{
field: 'displayText',
label: t('routes.admin.dictionaryDisplayText'),
component: 'Input',
required: true,
colProps: {
span: 22,
},
},
{
field: 'description',
label: t('routes.admin.dictionaryDescription'),
component: 'InputTextArea',
colProps: {
span: 22,
},
},
];
/**
*
*
* @export
* @return {*}
*/
export async function getDictionaryTypeAsync(params: PagingDataDictionaryInput) {
const _dataDictionaryServiceProxy = new DataDictionaryServiceProxy();
return await _dataDictionaryServiceProxy.page(params);
}
//新建字典类型
export async function createDictionaryTypeAsync({
request,
changeOkLoading,
closeModal,
validate,
resetFields,
}) {
changeOkLoading(true);
await validate();
const _dataDictionaryServiceProxy = new DataDictionaryServiceProxy();
await _dataDictionaryServiceProxy.create(request);
message.success(t('common.operationSuccess'));
resetFields();
changeOkLoading(false);
closeModal();
}
//启用|禁用详情字典
export async function enableDictionaryAsync(input: SetDataDictinaryDetailInput) {
const _dataDictionaryServiceProxy = new DataDictionaryServiceProxy();
await _dataDictionaryServiceProxy.status(input);
}
//创建数据详情字典
export async function createDetailsDictionaryAsync({
request,
changeOkLoading,
validate,
resetFields,
closeModal,
}) {
changeOkLoading(true);
await validate();
const _dataDictionaryServiceProxy = new DataDictionaryServiceProxy();
await _dataDictionaryServiceProxy.createDetail(request);
message.success(t('common.operationSuccess'));
resetFields();
changeOkLoading(false);
closeModal();
}
//分页获取数据字典详情
export async function getDictionaryDetailsAsync({ params }) {
const _dataDictionaryServiceProxy = new DataDictionaryServiceProxy();
return await _dataDictionaryServiceProxy.pageDetail(params);
}
//编辑数据字典
export async function editDetailsDictionaryAsync({
request,
changeOkLoading,
validate,
closeModal,
}) {
changeOkLoading(true);
await validate();
const _dataDictionaryServiceProxy = new DataDictionaryServiceProxy();
await _dataDictionaryServiceProxy.updateDetail(request);
message.success(t('common.operationSuccess'));
changeOkLoading(false);
closeModal();
}

185
vben271/src/views/admin/dictionary/AbpDictionary.vue

@ -0,0 +1,185 @@
<template>
<div>
<PageWrapper dense contentFullHeight fixedHeight contentClass="flex">
<BasicTable
@register="registerTypeTable"
class="w-1/4 xl:w-1/5"
size="small"
@selection-change="onSelectChange"
:clickToRowSelect="false"
>
<template #toolbar>
<a-button
type="primary"
preIcon="ant-design:plus-circle-outlined"
@click="handleCreateType"
>
{{ t('common.createText') }}</a-button
>
</template>
</BasicTable>
<BasicTable @register="registerTable" class="w-3/4 xl:w-4/5" size="small">
<template #toolbar>
<a-button preIcon="ant-design:plus-circle-outlined" type="primary" @click="handleCreate">
{{ t('common.createText') }}</a-button
>
</template>
<template #action="{ record }">
<TableAction
:actions="[
{
icon: 'ant-design:edit-outlined',
label: t('common.editText'),
onClick: handleEdit.bind(null, record),
},
]"
/>
</template>
</BasicTable>
<CreateAbpDictionaryType
@reloadType="reloadType"
@register="registerCreateType"
></CreateAbpDictionaryType>
<CreateAbpDictionary @register="registerCreateModal" @reload="reload" />
<EditAbpDictionary @register="registerEditModal" @reload="reload" />
</PageWrapper>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { PageWrapper } from '/@/components/Page';
import { BasicModal, useModal } from '/@/components/Modal';
import CreateAbpDictionary from './CreateAbpDictionary.vue';
import EditAbpDictionary from './EditAbpDictionary.vue';
import CreateAbpDictionaryType from './CreateAbpDictionaryType.vue';
import { useI18n } from '/@/hooks/web/useI18n';
import {
tableColumns,
searchFormSchema,
getDictionaryTypeAsync,
dictionaryTypeTableColumns,
searchDictionaryFormSchema,
getDictionaryDetailsAsync,
} from './AbpDictionary';
import { Tag, message } from 'ant-design-vue';
export default defineComponent({
name: 'AbpDictionary',
components: {
BasicTable,
BasicModal,
PageWrapper,
TableAction,
Tag,
CreateAbpDictionaryType,
CreateAbpDictionary,
EditAbpDictionary,
},
setup() {
const { t } = useI18n();
const [registerCreateModal, { openModal: createModal }] = useModal();
const [registerEditModal, { openModal: editModal }] = useModal();
const [registerCreateType, { openModal: createTypeModal }] = useModal();
const selectedDataDictionaryIdRef = ref('');
const selectedDataDictionaryDisplayTextRef = ref('');
//
const [registerTypeTable, { reload: reloadType, clearSelectedRowKeys }] = useTable({
columns: dictionaryTypeTableColumns,
formConfig: {
labelWidth: 0,
schemas: searchDictionaryFormSchema,
showResetButton: false,
},
api: getDictionaryTypeAsync,
useSearchForm: true,
showTableSetting: false,
showIndexColumn: false,
bordered: true,
canResize: true,
rowSelection: { type: 'radio' },
pagination: false,
});
//
const onSelectChange = async ({ rows }) => {
selectedDataDictionaryIdRef.value = rows[0].id;
selectedDataDictionaryDisplayTextRef.value = rows[0].displayText;
reload();
};
const handleCreate = () => {
if (selectedDataDictionaryIdRef.value == '') {
message.error(t('routes.admin.chooseDictionary'));
return;
} else {
let dictionaryCreate = {
id: selectedDataDictionaryIdRef.value,
displayText: selectedDataDictionaryDisplayTextRef.value,
};
createModal(true, { dictionaryCreate: dictionaryCreate });
}
};
const [registerTable, { reload }] = useTable({
columns: tableColumns,
formConfig: {
labelWidth: 120,
schemas: searchFormSchema,
},
api: getDictionaryPageDetailsAsync,
useSearchForm: true,
showTableSetting: true,
showIndexColumn: true,
bordered: true,
canResize: true,
actionColumn: {
width: 150,
title: t('common.action'),
dataIndex: 'action',
slots: { customRender: 'action' },
},
});
async function getDictionaryPageDetailsAsync(params) {
if (selectedDataDictionaryIdRef.value == '') {
return [];
}
params.dataDictionaryId = selectedDataDictionaryIdRef.value;
return await getDictionaryDetailsAsync({ params });
}
const handleEdit = (record: Recordable) => {
editModal(true, {
record: record,
});
};
const handleCreateType = () => {
createTypeModal(true);
};
return {
registerTable,
registerCreateModal,
registerEditModal,
handleCreate,
handleEdit,
reload,
registerTypeTable,
registerCreateType,
handleCreateType,
reloadType,
onSelectChange,
clearSelectedRowKeys,
t,
};
},
});
</script>
<style lang="less" scoped></style>

77
vben271/src/views/admin/dictionary/CreateAbpDictionary.vue

@ -0,0 +1,77 @@
<template>
<BasicModal
:title="t('common.createText')"
:canFullscreen="false"
@ok="submit"
:maskClosable="false"
@cancel="cancel"
@register="registerModal"
:height="380"
:destroyOnClose="true"
>
<BasicForm @register="registeDictionaryForm" />
</BasicModal>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { useI18n } from '/@/hooks/web/useI18n';
import { createFormSchema, createDetailsDictionaryAsync } from './AbpDictionary';
export default defineComponent({
name: 'CreateDictionary',
components: {
BasicModal,
BasicForm,
},
setup(_, { emit }) {
const { t } = useI18n();
const [registerModal, { closeModal, changeOkLoading }] = useModalInner((data) => {
setFieldsValue({
id: data.dictionaryCreate.id,
typeDisplayText: data.dictionaryCreate.displayText,
});
});
const [registeDictionaryForm, { resetFields, getFieldsValue, validate, setFieldsValue }] =
useForm({
labelWidth: 120,
schemas: createFormSchema,
showActionButtonGroup: false,
});
const submit = async () => {
try {
let request = getFieldsValue();
await createDetailsDictionaryAsync({
request,
changeOkLoading,
validate,
resetFields,
closeModal,
});
emit('reload');
} catch (error) {
changeOkLoading(false);
}
};
const cancel = () => {
resetFields();
// emit('clearSelectedRowKeys');
closeModal();
};
return {
registerModal,
registeDictionaryForm,
submit,
t,
cancel,
};
},
});
</script>
<style lang="less" scoped></style>

69
vben271/src/views/admin/dictionary/CreateAbpDictionaryType.vue

@ -0,0 +1,69 @@
<template>
<BasicModal
:title="t('common.createText')"
:canFullscreen="false"
@ok="submit"
@cancel="cancel"
@register="registerModal"
:destroyOnClose="true"
:maskClosable="false"
:minHeight="100"
>
<BasicForm @register="registeDictionaryTypeForm" />
</BasicModal>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { createDictionaryTypeFormSchema, createDictionaryTypeAsync } from './AbpDictionary';
export default defineComponent({
name: 'EditDictionary',
components: {
BasicModal,
BasicForm,
},
setup(_, { emit }) {
const { t } = useI18n();
const [registerModal, { closeModal, changeOkLoading }] = useModalInner();
const [registeDictionaryTypeForm, { resetFields, getFieldsValue, validate }] = useForm({
labelWidth: 100,
schemas: createDictionaryTypeFormSchema,
showActionButtonGroup: false,
});
const submit = async () => {
try {
let request = getFieldsValue();
await createDictionaryTypeAsync({
request,
changeOkLoading,
closeModal,
validate,
resetFields,
});
emit('reloadType');
} catch (error) {
changeOkLoading(false);
}
};
const cancel = () => {
resetFields();
closeModal();
};
return {
registerModal,
registeDictionaryTypeForm,
submit,
t,
cancel,
};
},
});
</script>
<style lang="less" scoped></style>

74
vben271/src/views/admin/dictionary/EditAbpDictionary.vue

@ -0,0 +1,74 @@
<template>
<BasicModal
:title="t('common.editText')"
:canFullscreen="false"
@ok="submit"
@cancel="cancel"
@register="registerModal"
:height="380"
:destroyOnClose="true"
:maskClosable="false"
>
<BasicForm @register="registeDictionaryForm" />
</BasicModal>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { useI18n } from '/@/hooks/web/useI18n';
import { BasicForm, useForm } from '/@/components/Form/index';
import { editFormSchema, editDetailsDictionaryAsync } from './AbpDictionary';
export default defineComponent({
name: 'EditDictionary',
components: {
BasicModal,
BasicForm,
},
setup(_, { emit }) {
const { t } = useI18n();
const [registerModal, { closeModal, changeOkLoading }] = useModalInner((data) => {
setFieldsValue({
id: data.record.id,
dataDictionaryId: data.record.dataDictionaryId,
displayText: data.record.displayText,
description: data.record.description,
code: data.record.code,
order: data.record.order,
});
});
const [registeDictionaryForm, { setFieldsValue, getFieldsValue, validate, resetFields }] =
useForm({
labelWidth: 120,
schemas: editFormSchema,
showActionButtonGroup: false,
});
const submit = async () => {
try {
let request = getFieldsValue();
await editDetailsDictionaryAsync({ request, changeOkLoading, validate, closeModal });
emit('reload');
} catch (error) {
changeOkLoading(false);
}
};
const cancel = () => {
resetFields();
closeModal();
};
return {
registerModal,
registeDictionaryForm,
submit,
cancel,
t,
};
},
});
</script>
<style lang="less" scoped></style>
Loading…
Cancel
Save