committed by
GitHub
255 changed files with 21197 additions and 340 deletions
@ -1,12 +1,25 @@ |
|||||
VITE_APP_BASE_API= |
VITE_APP_BASE_API= |
||||
VITE_APP_HOMEPAGE=/dashboard/workbench |
VITE_APP_HOMEPAGE=/dashboard/workbench |
||||
VITE_APP_BASE_PATH=/ |
VITE_APP_BASE_PATH=/ |
||||
|
|
||||
|
|
||||
|
# ----------------------- |
||||
|
# 1 |
||||
|
# VITE_PROXY_API=http://192.168.31.24:30001 # milo |
||||
# VITE_GLOB_CLIENT_ID=react-admin-client |
# VITE_GLOB_CLIENT_ID=react-admin-client |
||||
# VITE_GLOB_CLIENT_SECRET='' |
# VITE_GLOB_CLIENT_SECRET='' |
||||
# VITE_GLOB_SCOPE="openid email address phone profile offline_access miwen-abp-application" |
# VITE_GLOB_SCOPE="openid email address phone profile offline_access miwen-abp-application" |
||||
# VITE_PROXY_API=http://192.168.31.246:30001 |
|
||||
|
|
||||
|
|
||||
|
# 2 |
||||
|
# VITE_PROXY_API=http://192.168.31.24:30000 # collin |
||||
|
|
||||
|
VITE_PROXY_API=http://124.223.5.95:30001/ #yun |
||||
VITE_GLOB_CLIENT_ID=vue-admin-client |
VITE_GLOB_CLIENT_ID=vue-admin-client |
||||
VITE_GLOB_CLIENT_SECRET=1q2w3e* |
VITE_GLOB_CLIENT_SECRET=1q2w3e* |
||||
VITE_GLOB_SCOPE="openid email address phone profile offline_access lingyun-abp-application" |
VITE_GLOB_SCOPE="openid email address phone profile offline_access lingyun-abp-application" |
||||
VITE_PROXY_API=http://124.223.5.95:30001 |
|
||||
|
# ----------------------- |
||||
|
|
||||
|
VITE_EXTERNAL_LOGIN_ADDRESS=http://localhost:30001/connect/external/login |
||||
|
VITE_REGISTER_ADDRESS=http://localhost:3100/ |
||||
@ -0,0 +1,20 @@ |
|||||
|
import type { GenerateQrCodeResult, QrCodeUserInfoResult } from "#/account/qrcode"; |
||||
|
|
||||
|
import requestClient from "@/api/request"; |
||||
|
|
||||
|
/** |
||||
|
* 生成登录二维码 |
||||
|
* @returns 二维码信息 |
||||
|
*/ |
||||
|
export function generateApi(): Promise<GenerateQrCodeResult> { |
||||
|
return requestClient.post<GenerateQrCodeResult>("/api/account/qrcode/generate"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查二维码状态 |
||||
|
* @param key 二维码Key |
||||
|
* @returns 二维码信息 |
||||
|
*/ |
||||
|
export function checkCodeApi(key: string): Promise<QrCodeUserInfoResult> { |
||||
|
return requestClient.get<QrCodeUserInfoResult>(`/api/account/qrcode/${key}/check`); |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
import type { PagedResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { LogDto, LogGetListInput } from "#/management/auditing"; |
||||
|
|
||||
|
import requestClient from "../../request"; |
||||
|
|
||||
|
/** |
||||
|
* 获取系统日志 |
||||
|
* @param id 日志id |
||||
|
*/ |
||||
|
export function getApi(id: string): Promise<LogDto> { |
||||
|
return requestClient.get<LogDto>(`/api/auditing/logging/${id}`, { |
||||
|
method: "GET", |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取系统日志分页列表 |
||||
|
* @param input 参数 |
||||
|
*/ |
||||
|
export function getPagedListApi(input: LogGetListInput): Promise<PagedResultDto<LogDto>> { |
||||
|
return requestClient.get<PagedResultDto<LogDto>>("/api/auditing/logging", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { |
||||
|
FeatureDefinitionCreateDto, |
||||
|
FeatureDefinitionDto, |
||||
|
FeatureDefinitionGetListInput, |
||||
|
FeatureDefinitionUpdateDto, |
||||
|
} from "#/management/features/definitions"; |
||||
|
|
||||
|
import requestClient from "../../request"; |
||||
|
|
||||
|
/** |
||||
|
* 删除功能定义 |
||||
|
* @param name 功能名称 |
||||
|
*/ |
||||
|
export function deleteApi(name: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/feature-management/definitions/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询功能定义 |
||||
|
* @param name 功能名称 |
||||
|
* @returns 功能定义数据传输对象 |
||||
|
*/ |
||||
|
export function getApi(name: string): Promise<FeatureDefinitionDto> { |
||||
|
return requestClient.get<FeatureDefinitionDto>(`/api/feature-management/definitions/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询功能定义列表 |
||||
|
* @param input 功能过滤条件 |
||||
|
* @returns 功能定义数据传输对象列表 |
||||
|
*/ |
||||
|
export function getListApi(input?: FeatureDefinitionGetListInput): Promise<ListResultDto<FeatureDefinitionDto>> { |
||||
|
return requestClient.get<ListResultDto<FeatureDefinitionDto>>("/api/feature-management/definitions", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建功能定义 |
||||
|
* @param input 功能定义参数 |
||||
|
* @returns 功能定义数据传输对象 |
||||
|
*/ |
||||
|
export function createApi(input: FeatureDefinitionCreateDto): Promise<FeatureDefinitionDto> { |
||||
|
return requestClient.post<FeatureDefinitionDto>("/api/feature-management/definitions", input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新功能定义 |
||||
|
* @param name 功能名称 |
||||
|
* @param input 功能定义参数 |
||||
|
* @returns 功能定义数据传输对象 |
||||
|
*/ |
||||
|
export function updateApi(name: string, input: FeatureDefinitionUpdateDto): Promise<FeatureDefinitionDto> { |
||||
|
return requestClient.put<FeatureDefinitionDto>(`/api/feature-management/definitions/${name}`, input); |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { |
||||
|
FeatureGroupDefinitionCreateDto, |
||||
|
FeatureGroupDefinitionDto, |
||||
|
FeatureGroupDefinitionGetListInput, |
||||
|
FeatureGroupDefinitionUpdateDto, |
||||
|
} from "#/management/features/groups"; |
||||
|
|
||||
|
import requestClient from "../../request"; |
||||
|
|
||||
|
/** |
||||
|
* 删除功能定义 |
||||
|
* @param name 功能名称 |
||||
|
*/ |
||||
|
export function deleteApi(name: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/feature-management/definitions/groups/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询功能定义 |
||||
|
* @param name 功能名称 |
||||
|
* @returns 功能定义数据传输对象 |
||||
|
*/ |
||||
|
export function getApi(name: string): Promise<FeatureGroupDefinitionDto> { |
||||
|
return requestClient.get<FeatureGroupDefinitionDto>(`/api/feature-management/definitions/groups/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询功能定义列表 |
||||
|
* @param input 功能过滤条件 |
||||
|
* @returns 功能定义数据传输对象列表 |
||||
|
*/ |
||||
|
export function getListApi( |
||||
|
input?: FeatureGroupDefinitionGetListInput, |
||||
|
): Promise<ListResultDto<FeatureGroupDefinitionDto>> { |
||||
|
return requestClient.get<ListResultDto<FeatureGroupDefinitionDto>>("/api/feature-management/definitions/groups", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建功能定义 |
||||
|
* @param input 功能定义参数 |
||||
|
* @returns 功能定义数据传输对象 |
||||
|
*/ |
||||
|
export function createApi(input: FeatureGroupDefinitionCreateDto): Promise<FeatureGroupDefinitionDto> { |
||||
|
return requestClient.post<FeatureGroupDefinitionDto>("/api/feature-management/definitions/groups", input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新功能定义 |
||||
|
* @param name 功能名称 |
||||
|
* @param input 功能定义参数 |
||||
|
* @returns 功能定义数据传输对象 |
||||
|
*/ |
||||
|
export function updateApi(name: string, input: FeatureGroupDefinitionUpdateDto): Promise<FeatureGroupDefinitionDto> { |
||||
|
return requestClient.put<FeatureGroupDefinitionDto>(`/api/feature-management/definitions/groups/${name}`, input); |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
import type { FeatureProvider, GetFeatureListResultDto, UpdateFeaturesDto } from "#/management/features/features"; |
||||
|
|
||||
|
import requestClient from "../../request"; |
||||
|
|
||||
|
/** |
||||
|
* 删除功能 |
||||
|
* @param {FeatureProvider} provider 参数 |
||||
|
* @returns {Promise<void>} |
||||
|
*/ |
||||
|
export function deleteApi(provider: FeatureProvider): Promise<void> { |
||||
|
return requestClient.delete("/api/feature-management/features", { |
||||
|
params: provider, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询功能 |
||||
|
* @param {FeatureProvider} provider 参数 |
||||
|
* @returns {Promise<GetFeatureListResultDto>} 功能实体数据传输对象 |
||||
|
*/ |
||||
|
export function getApi(provider: FeatureProvider): Promise<GetFeatureListResultDto> { |
||||
|
return requestClient.get<GetFeatureListResultDto>("/api/feature-management/features", { |
||||
|
params: provider, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新功能 |
||||
|
* @param {FeatureProvider} provider |
||||
|
* @param {UpdateFeaturesDto} input 参数 |
||||
|
* @returns {Promise<void>} |
||||
|
*/ |
||||
|
export function updateApi(provider: FeatureProvider, input: UpdateFeaturesDto): Promise<void> { |
||||
|
return requestClient.put("/api/feature-management/features", input, { |
||||
|
params: provider, |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { |
||||
|
LanguageCreateDto, |
||||
|
LanguageDto, |
||||
|
LanguageGetListInput, |
||||
|
LanguageUpdateDto, |
||||
|
} from "#/management/localization/languages"; |
||||
|
|
||||
|
import requestClient from "../../request"; |
||||
|
|
||||
|
/** |
||||
|
* 查询语言列表 |
||||
|
* @param input 参数 |
||||
|
* @returns 语言列表 |
||||
|
*/ |
||||
|
export function getListApi(input?: LanguageGetListInput): Promise<ListResultDto<LanguageDto>> { |
||||
|
return requestClient.get<ListResultDto<LanguageDto>>("/api/abp/localization/languages", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询语言 |
||||
|
* @param name 语言名称 |
||||
|
* @returns 查询的语言 |
||||
|
*/ |
||||
|
export function getApi(name: string): Promise<LanguageDto> { |
||||
|
return requestClient.get<LanguageDto>(`/api/localization/languages/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除语言 |
||||
|
* @param name 语言名称 |
||||
|
*/ |
||||
|
export function deleteApi(name: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/localization/languages/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建语言 |
||||
|
* @param input 参数 |
||||
|
* @returns 创建的语言 |
||||
|
*/ |
||||
|
export function createApi(input: LanguageCreateDto): Promise<LanguageDto> { |
||||
|
return requestClient.post<LanguageDto>("/api/localization/languagesr", input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 编辑语言 |
||||
|
* @param name 语言名称 |
||||
|
* @param input 参数 |
||||
|
* @returns 编辑的语言 |
||||
|
*/ |
||||
|
export function updateApi(name: string, input: LanguageUpdateDto): Promise<LanguageDto> { |
||||
|
return requestClient.put<LanguageDto>(`/api/localization/languages/${name}`, input); |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
import type { ApplicationLocalizationDto } from "#/abp-core"; |
||||
|
|
||||
|
import requestClient from "../../request"; |
||||
|
|
||||
|
/** |
||||
|
* 获取应用程序语言 |
||||
|
* @returns 本地化配置 |
||||
|
*/ |
||||
|
export function getLocalizationApi(options: { |
||||
|
cultureName: string; |
||||
|
onlyDynamics?: boolean; |
||||
|
}): Promise<ApplicationLocalizationDto> { |
||||
|
return requestClient.get<ApplicationLocalizationDto>("/api/abp/application-localization", { |
||||
|
params: options, |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { |
||||
|
ResourceCreateDto, |
||||
|
ResourceDto, |
||||
|
ResourceGetListInput, |
||||
|
ResourceUpdateDto, |
||||
|
} from "#/management/localization/resources"; |
||||
|
|
||||
|
import requestClient from "../../request"; |
||||
|
|
||||
|
/** |
||||
|
* 查询资源列表 |
||||
|
* @param input 参数 |
||||
|
* @returns 资源列表 |
||||
|
*/ |
||||
|
export function getListApi(input?: ResourceGetListInput): Promise<ListResultDto<ResourceDto>> { |
||||
|
return requestClient.get<ListResultDto<ResourceDto>>("/api/abp/localization/resources", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询资源 |
||||
|
* @param name 资源名称 |
||||
|
* @returns 查询的资源 |
||||
|
*/ |
||||
|
export function getApi(name: string): Promise<ResourceDto> { |
||||
|
return requestClient.get<ResourceDto>(`/api/localization/resources/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除资源 |
||||
|
* @param name 资源名称 |
||||
|
*/ |
||||
|
export function deleteApi(name: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/localization/resources/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建资源 |
||||
|
* @param input 参数 |
||||
|
* @returns 创建的资源 |
||||
|
*/ |
||||
|
export function createApi(input: ResourceCreateDto): Promise<ResourceDto> { |
||||
|
return requestClient.post<ResourceDto>("/api/localization/resources", input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 编辑资源 |
||||
|
* @param name 资源名称 |
||||
|
* @param input 参数 |
||||
|
* @returns 编辑的资源 |
||||
|
*/ |
||||
|
export function updateApi(name: string, input: ResourceUpdateDto): Promise<ResourceDto> { |
||||
|
return requestClient.put<ResourceDto>(`/api/localization/resources/${name}`, input); |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { |
||||
|
GetTextByKeyInput, |
||||
|
GetTextsInput, |
||||
|
SetTextInput, |
||||
|
TextDifferenceDto, |
||||
|
TextDto, |
||||
|
} from "#/management/localization/texts"; |
||||
|
|
||||
|
import requestClient from "../../request"; |
||||
|
|
||||
|
/** |
||||
|
* 查询文本列表 |
||||
|
* @param input 参数 |
||||
|
* @returns 文本列表 |
||||
|
*/ |
||||
|
export function getListApi(input: GetTextsInput): Promise<ListResultDto<TextDifferenceDto>> { |
||||
|
return requestClient.get<ListResultDto<TextDifferenceDto>>("/api/abp/localization/texts", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询文本 |
||||
|
* @param input 参数 |
||||
|
* @returns 查询的文本 |
||||
|
*/ |
||||
|
export function getApi(input: GetTextByKeyInput): Promise<TextDto> { |
||||
|
return requestClient.get<TextDto>("/api/abp/localization/texts/by-culture-key", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 设置文本 |
||||
|
* @param input 参数 |
||||
|
*/ |
||||
|
export function setApi(input: SetTextInput): Promise<void> { |
||||
|
return requestClient.put("/api/localization/texts", input); |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { UserSubscreNotification } from "#/notifications/subscribes"; |
||||
|
|
||||
|
import requestClient from "../../request"; |
||||
|
|
||||
|
/** |
||||
|
* 获取我的所有订阅通知 |
||||
|
* @returns 订阅通知列表 |
||||
|
*/ |
||||
|
export function getMySubscribesApi(): Promise<ListResultDto<UserSubscreNotification>> { |
||||
|
return requestClient.get<ListResultDto<UserSubscreNotification>>("/api/notifications/my-subscribes/all"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 订阅通知 |
||||
|
* @param name 通知名称 |
||||
|
*/ |
||||
|
export function subscribeApi(name: string): Promise<void> { |
||||
|
return requestClient.post("/api/notifications/my-subscribes", { |
||||
|
name, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 取消订阅通知 |
||||
|
* @param name 通知名称 |
||||
|
*/ |
||||
|
export function unSubscribeApi(name: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/notifications/my-subscribes?name=${name}`); |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { |
||||
|
NotificationDefinitionCreateDto, |
||||
|
NotificationDefinitionDto, |
||||
|
NotificationDefinitionGetListInput, |
||||
|
NotificationDefinitionUpdateDto, |
||||
|
} from "#/notifications/definitions"; |
||||
|
|
||||
|
import requestClient from "../../request"; |
||||
|
|
||||
|
/** |
||||
|
* 删除通知定义 |
||||
|
* @param name 通知名称 |
||||
|
*/ |
||||
|
export function deleteApi(name: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/notifications/definitions/notifications/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询通知定义 |
||||
|
* @param name 通知名称 |
||||
|
* @returns 通知定义数据传输对象 |
||||
|
*/ |
||||
|
export function getApi(name: string): Promise<NotificationDefinitionDto> { |
||||
|
return requestClient.get<NotificationDefinitionDto>(`/api/notifications/definitions/notifications/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询通知定义列表 |
||||
|
* @param input 通知过滤条件 |
||||
|
* @returns 通知定义数据传输对象列表 |
||||
|
*/ |
||||
|
export function getListApi( |
||||
|
input?: NotificationDefinitionGetListInput, |
||||
|
): Promise<ListResultDto<NotificationDefinitionDto>> { |
||||
|
return requestClient.get<ListResultDto<NotificationDefinitionDto>>("/api/notifications/definitions/notifications", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建通知定义 |
||||
|
* @param input 通知定义参数 |
||||
|
* @returns 通知定义数据传输对象 |
||||
|
*/ |
||||
|
export function createApi(input: NotificationDefinitionCreateDto): Promise<NotificationDefinitionDto> { |
||||
|
return requestClient.post<NotificationDefinitionDto>("/api/notifications/definitions/notifications", input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新通知定义 |
||||
|
* @param name 通知名称 |
||||
|
* @param input 通知定义参数 |
||||
|
* @returns 通知定义数据传输对象 |
||||
|
*/ |
||||
|
export function updateApi(name: string, input: NotificationDefinitionUpdateDto): Promise<NotificationDefinitionDto> { |
||||
|
return requestClient.put<NotificationDefinitionDto>(`/api/notifications/definitions/notifications/${name}`, input); |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { |
||||
|
NotificationGroupDefinitionCreateDto, |
||||
|
NotificationGroupDefinitionDto, |
||||
|
NotificationGroupDefinitionGetListInput, |
||||
|
NotificationGroupDefinitionUpdateDto, |
||||
|
} from "#/notifications/groups"; |
||||
|
|
||||
|
import requestClient from "../../request"; |
||||
|
|
||||
|
/** |
||||
|
* 删除通知分组定义 |
||||
|
* @param name 通知分组名称 |
||||
|
*/ |
||||
|
export function deleteApi(name: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/notifications/definitions/groups/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询通知分组定义 |
||||
|
* @param name 通知分组名称 |
||||
|
* @returns 通知分组定义数据传输对象 |
||||
|
*/ |
||||
|
export function getApi(name: string): Promise<NotificationGroupDefinitionDto> { |
||||
|
return requestClient.get<NotificationGroupDefinitionDto>(`/api/notifications/definitions/groups/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询通知分组定义列表 |
||||
|
* @param input 通知分组过滤条件 |
||||
|
* @returns 通知分组定义数据传输对象列表 |
||||
|
*/ |
||||
|
export function getListApi( |
||||
|
input?: NotificationGroupDefinitionGetListInput, |
||||
|
): Promise<ListResultDto<NotificationGroupDefinitionDto>> { |
||||
|
return requestClient.get<ListResultDto<NotificationGroupDefinitionDto>>("/api/notifications/definitions/groups", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建通知分组定义 |
||||
|
* @param input 通知分组定义参数 |
||||
|
* @returns 通知分组定义数据传输对象 |
||||
|
*/ |
||||
|
export function createApi(input: NotificationGroupDefinitionCreateDto): Promise<NotificationGroupDefinitionDto> { |
||||
|
return requestClient.post<NotificationGroupDefinitionDto>("/api/notifications/definitions/groups", input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新通知分组定义 |
||||
|
* @param name 通知分组名称 |
||||
|
* @param input 通知分组定义参数 |
||||
|
* @returns 通知分组定义数据传输对象 |
||||
|
*/ |
||||
|
export function updateApi( |
||||
|
name: string, |
||||
|
input: NotificationGroupDefinitionUpdateDto, |
||||
|
): Promise<NotificationGroupDefinitionDto> { |
||||
|
return requestClient.put<NotificationGroupDefinitionDto>(`/api/notifications/definitions/groups/${name}`, input); |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
import type { |
||||
|
GetOssContainersInput, |
||||
|
GetOssObjectsInput, |
||||
|
OssContainerDto, |
||||
|
OssContainersResultDto, |
||||
|
} from "#/oss/containes"; |
||||
|
import type { OssObjectsResultDto } from "#/oss/objects"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
export function deleteApi(name: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/oss-management/containes/${name}`); |
||||
|
} |
||||
|
|
||||
|
export function getApi(name: string): Promise<OssContainerDto> { |
||||
|
return requestClient.get<OssContainerDto>(`/api/oss-management/containes/${name}`); |
||||
|
} |
||||
|
|
||||
|
export function getListApi(input?: GetOssContainersInput): Promise<OssContainersResultDto> { |
||||
|
return requestClient.get<OssContainersResultDto>("/api/oss-management/containes", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function getObjectsApi(input: GetOssObjectsInput): Promise<OssObjectsResultDto> { |
||||
|
return requestClient.get<OssObjectsResultDto>("/api/oss-management/containes/objects", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function createApi(name: string): Promise<OssContainerDto> { |
||||
|
return requestClient.post<OssContainerDto>(`/api/oss-management/containes/${name}`); |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
import type { BulkDeleteOssObjectInput, CreateOssObjectInput, GetOssObjectInput, OssObjectDto } from "#/oss/objects"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
export function createApi(input: CreateOssObjectInput): Promise<OssObjectDto> { |
||||
|
const formData = new window.FormData(); |
||||
|
formData.append("bucket", input.bucket); |
||||
|
formData.append("fileName", input.fileName); |
||||
|
formData.append("overwrite", String(input.overwrite)); |
||||
|
input.expirationTime && formData.append("expirationTime", input.expirationTime.toString()); |
||||
|
input.path && formData.append("path", input.path); |
||||
|
input.file && formData.append("file", input.file); |
||||
|
|
||||
|
return requestClient.post<OssObjectDto>("/api/oss-management/objects", formData, { |
||||
|
headers: { |
||||
|
"Content-Type": "multipart/form-data;charset=utf-8", |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function generateUrlApi(input: GetOssObjectInput): Promise<string> { |
||||
|
// return requestClient.get<string>("/api/oss-management/objects/generate-url", { TODO update
|
||||
|
return requestClient.get<string>("/api/oss-management/objects/download", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function deleteApi(input: BulkDeleteOssObjectInput): Promise<void> { |
||||
|
return requestClient.delete("/api/oss-management/objects", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
import type { ListResultDto, PagedResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { |
||||
|
DataCreateDto, |
||||
|
DataDto, |
||||
|
DataItemCreateDto, |
||||
|
DataItemUpdateDto, |
||||
|
DataMoveDto, |
||||
|
DataUpdateDto, |
||||
|
GetDataListInput, |
||||
|
} from "#/platform/data-dictionaries"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
export function createApi(input: DataCreateDto): Promise<DataDto> { |
||||
|
return requestClient.post<DataDto>("/api/platform/datas", input); |
||||
|
} |
||||
|
|
||||
|
export function deleteApi(id: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/platform/datas/${id}`); |
||||
|
} |
||||
|
|
||||
|
export function createItemApi(id: string, input: DataItemCreateDto): Promise<void> { |
||||
|
return requestClient.post(`/api/platform/datas/${id}/items`, input); |
||||
|
} |
||||
|
|
||||
|
export function deleteItemApi(id: string, name: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/platform/datas/${id}/items/${name}`); |
||||
|
} |
||||
|
|
||||
|
export function getApi(id: string): Promise<DataDto> { |
||||
|
return requestClient.get<DataDto>(`/api/platform/datas/${id}`); |
||||
|
} |
||||
|
|
||||
|
export function getByNameApi(name: string): Promise<DataDto> { |
||||
|
return requestClient.get<DataDto>(`/api/platform/datas/by-name/${name}`); |
||||
|
} |
||||
|
|
||||
|
export function getAllApi(): Promise<ListResultDto<DataDto>> { |
||||
|
return requestClient.get<ListResultDto<DataDto>>("/api/platform/datas/all"); |
||||
|
} |
||||
|
|
||||
|
export function getPagedListApi(input?: GetDataListInput): Promise<PagedResultDto<DataDto>> { |
||||
|
return requestClient.get<PagedResultDto<DataDto>>("/api/platform/datas", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function moveApi(id: string, input: DataMoveDto): Promise<DataDto> { |
||||
|
return requestClient.put<DataDto>(`/api/platform/datas/${id}/move`, input); |
||||
|
} |
||||
|
|
||||
|
export function updateApi(id: string, input: DataUpdateDto): Promise<DataDto> { |
||||
|
return requestClient.put<DataDto>(`/api/platform/datas/${id}`, input); |
||||
|
} |
||||
|
|
||||
|
export function updateItemApi(id: string, name: string, input: DataItemUpdateDto): Promise<void> { |
||||
|
return requestClient.put(`/api/platform/datas/${id}/items/${name}`, input); |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
import type { PagedResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { EmailMessageDto, EmailMessageGetListInput } from "#/platform/messages"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
/** |
||||
|
* 获取邮件消息分页列表 |
||||
|
* @param {EmailMessageGetListInput} input 参数 |
||||
|
* @returns {Promise<PagedResultDto<EmailMessageDto>>} 邮件消息列表 |
||||
|
*/ |
||||
|
export function getPagedListApi(input?: EmailMessageGetListInput): Promise<PagedResultDto<EmailMessageDto>> { |
||||
|
return requestClient.get<PagedResultDto<EmailMessageDto>>("/api/platform/messages/email", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取邮件消息 |
||||
|
* @param id Id |
||||
|
* @returns {EmailMessageDto} 邮件消息 |
||||
|
*/ |
||||
|
export function getApi(id: string): Promise<EmailMessageDto> { |
||||
|
return requestClient.get<EmailMessageDto>(`/api/platform/messages/email/${id}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除邮件消息 |
||||
|
* @param id Id |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
export function deleteApi(id: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/platform/messages/email/${id}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 发送邮件消息 |
||||
|
* @param id Id |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
export function sendApi(id: string): Promise<void> { |
||||
|
return requestClient.post(`/api/platform/messages/email/${id}/send`); |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
import type { PagedResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { LayoutCreateDto, LayoutDto, LayoutGetPagedListInput, LayoutUpdateDto } from "#/platform/layouts"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
export function createApi(input: LayoutCreateDto): Promise<LayoutDto> { |
||||
|
return requestClient.post<LayoutDto>("/api/platform/layouts", input); |
||||
|
} |
||||
|
|
||||
|
export function getPagedListApi(input?: LayoutGetPagedListInput): Promise<PagedResultDto<LayoutDto>> { |
||||
|
return requestClient.get<PagedResultDto<LayoutDto>>("/api/platform/layouts", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function getApi(id: string): Promise<LayoutDto> { |
||||
|
return requestClient.get<LayoutDto>(`/api/platform/layouts/${id}`); |
||||
|
} |
||||
|
|
||||
|
export function deleteApi(id: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/platform/layouts/${id}`); |
||||
|
} |
||||
|
|
||||
|
export function updateApi(id: string, input: LayoutUpdateDto): Promise<LayoutDto> { |
||||
|
return requestClient.put<LayoutDto>(`/api/platform/layouts/${id}`, input); |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { MenuCreateDto, MenuDto, MenuGetAllInput, MenuUpdateDto } from "#/platform/menus"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
export function createApi(input: MenuCreateDto): Promise<MenuDto> { |
||||
|
return requestClient.post<MenuDto>("/api/platform/menus", input); |
||||
|
} |
||||
|
|
||||
|
export function getAllApi(input?: MenuGetAllInput): Promise<ListResultDto<MenuDto>> { |
||||
|
return requestClient.get<ListResultDto<MenuDto>>("/api/platform/menus/all", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function getApi(id: string): Promise<MenuDto> { |
||||
|
return requestClient.get<MenuDto>(`/api/platform/menus/${id}`); |
||||
|
} |
||||
|
|
||||
|
export function deleteApi(id: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/platform/menus/${id}`); |
||||
|
} |
||||
|
|
||||
|
export function updateApi(id: string, input: MenuUpdateDto): Promise<MenuDto> { |
||||
|
return requestClient.put<MenuDto>(`/api/platform/menus/${id}`, input); |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { UserFavoriteMenuCreateDto, UserFavoriteMenuDto } from "#/platform/favorites"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
/** |
||||
|
* 新增常用菜单 |
||||
|
* @param input 参数 |
||||
|
* @returns 常用菜单 |
||||
|
*/ |
||||
|
export function createApi(input: UserFavoriteMenuCreateDto): Promise<UserFavoriteMenuDto> { |
||||
|
return requestClient.post<UserFavoriteMenuDto>("/api/platform/menus/favorites/my-favorite-menus", input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除常用菜单 |
||||
|
* @param id 菜单Id |
||||
|
*/ |
||||
|
export function deleteApi(id: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/platform/menus/favorites/my-favorite-menus/${id}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取常用菜单列表 |
||||
|
* @param framework ui框架 |
||||
|
* @returns 菜单列表 |
||||
|
*/ |
||||
|
export function getListApi(framework?: string): Promise<ListResultDto<UserFavoriteMenuDto>> { |
||||
|
return requestClient.get<ListResultDto<UserFavoriteMenuDto>>("/api/platform/menus/favorites/my-favorite-menus", { |
||||
|
params: { framework }, |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { MenuDto, MenuGetInput } from "#/platform/menus"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
export function getAllApi(input?: MenuGetInput): Promise<ListResultDto<MenuDto>> { |
||||
|
return requestClient.get<ListResultDto<MenuDto>>("/api/platform/menus/by-current-user", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { MenuDto, MenuGetByRoleInput, SetRoleMenuInput, SetRoleMenuStartupInput } from "#/platform/menus"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
export function getAllApi(input: MenuGetByRoleInput): Promise<ListResultDto<MenuDto>> { |
||||
|
return requestClient.get<ListResultDto<MenuDto>>(`/api/platform/menus/by-role/${input.role}/${input.framework}`, { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function setMenusApi(input: SetRoleMenuInput): Promise<void> { |
||||
|
return requestClient.put("/api/platform/menus/by-role", input); |
||||
|
} |
||||
|
|
||||
|
export function setStartupMenuApi(meudId: string, input: SetRoleMenuStartupInput): Promise<void> { |
||||
|
return requestClient.put(`/api/platform/menus/startup/${meudId}/by-role`, input); |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
import type { PagedResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { SmsMessageDto, SmsMessageGetListInput } from "#/platform/messages"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
/** |
||||
|
* 获取短信消息分页列表 |
||||
|
* @param {EmailMessageGetListInput} input 参数 |
||||
|
* @returns {Promise<PagedResultDto<EmailMessageDto>>} 短信消息列表 |
||||
|
*/ |
||||
|
export function getPagedListApi(input?: SmsMessageGetListInput): Promise<PagedResultDto<SmsMessageDto>> { |
||||
|
return requestClient.get<PagedResultDto<SmsMessageDto>>("/api/platform/messages/sms", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取短信消息 |
||||
|
* @param id Id |
||||
|
* @returns {SmsMessageDto} 短信消息 |
||||
|
*/ |
||||
|
export function getApi(id: string): Promise<SmsMessageDto> { |
||||
|
return requestClient.get<SmsMessageDto>(`/api/platform/messages/sms/${id}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除短信消息 |
||||
|
* @param id Id |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
export function deleteApi(id: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/platform/messages/sms/${id}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 发送短信消息 |
||||
|
* @param id Id |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
export function sendApi(id: string): Promise<void> { |
||||
|
return requestClient.post(`/api/platform/messages/sms/${id}/send`); |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { MenuDto, MenuGetByUserInput, SetUserMenuInput, SetUserMenuStartupInput } from "#/platform/menus"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
export function getAllApi(input: MenuGetByUserInput): Promise<ListResultDto<MenuDto>> { |
||||
|
return requestClient.get<ListResultDto<MenuDto>>(`/api/platform/menus/by-user/${input.userId}/${input.framework}`, { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function setMenusApi(input: SetUserMenuInput): Promise<void> { |
||||
|
return requestClient.put("/api/platform/menus/by-user", input); |
||||
|
} |
||||
|
|
||||
|
export function setStartupMenuApi(meudId: string, input: SetUserMenuStartupInput): Promise<void> { |
||||
|
return requestClient.put(`/api/platform/menus/startup/${meudId}/by-user`, input); |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
import type { PagedResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { EditionCreateDto, EditionDto, EditionUpdateDto, GetEditionPagedListInput } from "#/saas/editions"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
/** |
||||
|
* 创建版本 |
||||
|
* @param {EditionCreateDto} input 参数 |
||||
|
* @returns 创建的版本 |
||||
|
*/ |
||||
|
export function createApi(input: EditionCreateDto): Promise<EditionDto> { |
||||
|
return requestClient.post<EditionDto>("/api/saas/editions", input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 编辑版本 |
||||
|
* @param {string} id 参数 |
||||
|
* @param {EditionUpdateDto} input 参数 |
||||
|
* @returns 编辑的版本 |
||||
|
*/ |
||||
|
export function updateApi(id: string, input: EditionUpdateDto): Promise<EditionDto> { |
||||
|
return requestClient.put<EditionDto>(`/api/saas/editions/${id}`, input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询版本 |
||||
|
* @param {string} id Id |
||||
|
* @returns 查询的版本 |
||||
|
*/ |
||||
|
export function getApi(id: string): Promise<EditionDto> { |
||||
|
return requestClient.get<EditionDto>(`/api/saas/editions/${id}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除版本 |
||||
|
* @param {string} id Id |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
export function deleteApi(id: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/saas/editions/${id}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询版本分页列表 |
||||
|
* @param {GetEditionPagedListInput} input 参数 |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
export function getPagedListApi(input?: GetEditionPagedListInput): Promise<PagedResultDto<EditionDto>> { |
||||
|
return requestClient.get<PagedResultDto<EditionDto>>("/api/saas/editions", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
import type { FindTenantResultDto } from "#/saas"; |
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
export function findTenantByNameApi(name: string): Promise<FindTenantResultDto> { |
||||
|
return requestClient.get<FindTenantResultDto>(`/api/abp/multi-tenancy/tenants/by-name/${name}`); |
||||
|
} |
||||
|
|
||||
|
export function findTenantByIdApi(id: string): Promise<FindTenantResultDto> { |
||||
|
return requestClient.get<FindTenantResultDto>(`/api/abp/multi-tenancy/tenants/by-id/${id}`); |
||||
|
} |
||||
@ -0,0 +1,111 @@ |
|||||
|
import type { ListResultDto, PagedResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { |
||||
|
GetTenantPagedListInput, |
||||
|
TenantConnectionStringCheckInput, |
||||
|
TenantConnectionStringDto, |
||||
|
TenantConnectionStringSetInput, |
||||
|
TenantCreateDto, |
||||
|
TenantDto, |
||||
|
TenantUpdateDto, |
||||
|
} from "#/saas/tenants"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
/** |
||||
|
* 创建租户 |
||||
|
* @param {TenantCreateDto} input 参数 |
||||
|
* @returns 创建的租户 |
||||
|
*/ |
||||
|
export function createApi(input: TenantCreateDto): Promise<TenantDto> { |
||||
|
return requestClient.post<TenantDto>("/api/saas/tenants", input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 编辑租户 |
||||
|
* @param {string} id 参数 |
||||
|
* @param {TenantUpdateDto} input 参数 |
||||
|
* @returns 编辑的租户 |
||||
|
*/ |
||||
|
export function updateApi(id: string, input: TenantUpdateDto): Promise<TenantDto> { |
||||
|
return requestClient.put<TenantDto>(`/api/saas/tenants/${id}`, input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询租户 |
||||
|
* @param {string} id Id |
||||
|
* @returns 查询的租户 |
||||
|
*/ |
||||
|
export function getApi(id: string): Promise<TenantDto> { |
||||
|
return requestClient.get<TenantDto>(`/api/saas/tenants/${id}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除租户 |
||||
|
* @param {string} id Id |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
export function deleteApi(id: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/saas/tenants/${id}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询租户分页列表 |
||||
|
* @param {GetTenantPagedListInput} input 参数 |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
export function getPagedListApi(input?: GetTenantPagedListInput): Promise<PagedResultDto<TenantDto>> { |
||||
|
return requestClient.get<PagedResultDto<TenantDto>>("/api/saas/tenants", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 设置连接字符串 |
||||
|
* @param {string} id 租户Id |
||||
|
* @param {TenantConnectionStringSetInput} input 参数 |
||||
|
* @returns 连接字符串 |
||||
|
*/ |
||||
|
export function setConnectionStringApi( |
||||
|
id: string, |
||||
|
input: TenantConnectionStringSetInput, |
||||
|
): Promise<TenantConnectionStringDto> { |
||||
|
return requestClient.put<TenantConnectionStringDto>(`/api/saas/tenants/${id}/connection-string`, input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询连接字符串 |
||||
|
* @param {string} id 租户Id |
||||
|
* @param {string} name 连接字符串名称 |
||||
|
* @returns 连接字符串 |
||||
|
*/ |
||||
|
export function getConnectionStringApi(id: string, name: string): Promise<TenantConnectionStringDto> { |
||||
|
return requestClient.get<TenantConnectionStringDto>(`/api/saas/tenants/${id}/connection-string/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询所有连接字符串 |
||||
|
* @param {string} id 租户Id |
||||
|
* @returns 连接字符串列表 |
||||
|
*/ |
||||
|
export function getConnectionStringsApi(id: string): Promise<ListResultDto<TenantConnectionStringDto>> { |
||||
|
return requestClient.get<ListResultDto<TenantConnectionStringDto>>(`/api/saas/tenants/${id}/connection-string`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除租户 |
||||
|
* @param {string} id 租户Id |
||||
|
* @param {string} name 连接字符串名称 |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
export function deleteConnectionStringApi(id: string, name: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/saas/tenants/${id}/connection-string/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查数据库连接字符串 |
||||
|
* @param input 参数 |
||||
|
*/ |
||||
|
export function checkConnectionString(input: TenantConnectionStringCheckInput): Promise<void> { |
||||
|
return requestClient.post("/api/saas/tenants/connection-string/check", input); |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
import type { ListResultDto, PagedResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { |
||||
|
BackgroundJobDefinitionDto, |
||||
|
BackgroundJobInfoBatchInput, |
||||
|
BackgroundJobInfoCreateDto, |
||||
|
BackgroundJobInfoDto, |
||||
|
BackgroundJobInfoGetListInput, |
||||
|
BackgroundJobInfoUpdateDto, |
||||
|
} from "#/tasks/job-infos"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
export function createApi(input: BackgroundJobInfoCreateDto): Promise<BackgroundJobInfoDto> { |
||||
|
return requestClient.post<BackgroundJobInfoDto>("/api/task-management/background-jobs", input); |
||||
|
} |
||||
|
|
||||
|
export function deleteApi(id: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/task-management/background-jobs/${id}`); |
||||
|
} |
||||
|
|
||||
|
export function bulkDeleteApi(input: BackgroundJobInfoBatchInput): Promise<void> { |
||||
|
return requestClient.delete("/api/task-management/background-jobs/bulk-delete", { |
||||
|
data: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function getApi(id: string): Promise<BackgroundJobInfoDto> { |
||||
|
return requestClient.get<BackgroundJobInfoDto>(`/api/task-management/background-jobs/${id}`); |
||||
|
} |
||||
|
|
||||
|
export function getPagedListApi(input?: BackgroundJobInfoGetListInput): Promise<PagedResultDto<BackgroundJobInfoDto>> { |
||||
|
return requestClient.get<PagedResultDto<BackgroundJobInfoDto>>("/api/task-management/background-jobs", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function pauseApi(id: string): Promise<void> { |
||||
|
return requestClient.put(`/api/task-management/background-jobs/${id}/pause`); |
||||
|
} |
||||
|
|
||||
|
export function bulkPauseApi(input: BackgroundJobInfoBatchInput): Promise<void> { |
||||
|
return requestClient.put("/api/task-management/background-jobs/bulk-pause", input); |
||||
|
} |
||||
|
|
||||
|
export function resumeApi(id: string): Promise<void> { |
||||
|
return requestClient.put(`/api/task-management/background-jobs/${id}/resume`); |
||||
|
} |
||||
|
|
||||
|
export function bulkResumeApi(input: BackgroundJobInfoBatchInput): Promise<void> { |
||||
|
return requestClient.put("/api/task-management/background-jobs/bulk-resume", input); |
||||
|
} |
||||
|
|
||||
|
export function triggerApi(id: string): Promise<void> { |
||||
|
return requestClient.put(`/api/task-management/background-jobs/${id}/trigger`); |
||||
|
} |
||||
|
|
||||
|
export function bulkTriggerApi(input: BackgroundJobInfoBatchInput): Promise<void> { |
||||
|
return requestClient.put("/api/task-management/background-jobs/bulk-trigger", input); |
||||
|
} |
||||
|
|
||||
|
export function stopApi(id: string): Promise<void> { |
||||
|
return requestClient.put(`/api/task-management/background-jobs/${id}/stop`); |
||||
|
} |
||||
|
|
||||
|
export function bulkStopApi(input: BackgroundJobInfoBatchInput): Promise<void> { |
||||
|
return requestClient.put("/api/task-management/background-jobs/bulk-stop", input); |
||||
|
} |
||||
|
|
||||
|
export function startApi(id: string): Promise<void> { |
||||
|
return requestClient.put(`/api/task-management/background-jobs/${id}/start`); |
||||
|
} |
||||
|
|
||||
|
export function bulkStartApi(input: BackgroundJobInfoBatchInput): Promise<void> { |
||||
|
return requestClient.put("/api/task-management/background-jobs/bulk-start", input); |
||||
|
} |
||||
|
|
||||
|
export function updateApi(id: string, input: BackgroundJobInfoUpdateDto): Promise<BackgroundJobInfoDto> { |
||||
|
return requestClient.put<BackgroundJobInfoDto>(`/api/task-management/background-jobs/${id}`, input); |
||||
|
} |
||||
|
|
||||
|
export function getDefinitionsApi(): Promise<ListResultDto<BackgroundJobDefinitionDto>> { |
||||
|
return requestClient.get<ListResultDto<BackgroundJobDefinitionDto>>( |
||||
|
"/api/task-management/background-jobs/definitions", |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
import type { PagedResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { BackgroundJobLogDto, BackgroundJobLogGetListInput } from "#/tasks/job-logs"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
export function getApi(id: string): Promise<BackgroundJobLogDto> { |
||||
|
return requestClient.get<BackgroundJobLogDto>(`/api/task-management/background-jobs/logs/${id}`); |
||||
|
} |
||||
|
|
||||
|
export function deleteApi(id: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/task-management/background-jobs/logs/${id}`); |
||||
|
} |
||||
|
|
||||
|
export function getPagedListApi(input?: BackgroundJobLogGetListInput): Promise<PagedResultDto<BackgroundJobLogDto>> { |
||||
|
return requestClient.get<PagedResultDto<BackgroundJobLogDto>>("/api/task-management/background-jobs/logs", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
import type { |
||||
|
TextTemplateContentDto, |
||||
|
TextTemplateContentGetInput, |
||||
|
TextTemplateContentUpdateDto, |
||||
|
TextTemplateRestoreInput, |
||||
|
} from "#/text-templating/contents"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
/** |
||||
|
* 获取模板内容 |
||||
|
* @param input 参数 |
||||
|
* @returns 模板内容数据传输对象 |
||||
|
*/ |
||||
|
export function getApi(input: TextTemplateContentGetInput): Promise<TextTemplateContentDto> { |
||||
|
let url = "/api/text-templating/templates/content"; |
||||
|
url += input.culture ? `/${input.culture}/${input.name}` : `/${input.name}`; |
||||
|
return requestClient.get<TextTemplateContentDto>(url); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 重置模板内容为默认值 |
||||
|
* @param name 模板名称 |
||||
|
* @param input 参数 |
||||
|
* @returns 模板定义数据传输对象列表 |
||||
|
*/ |
||||
|
export function restoreToDefaultApi(name: string, input: TextTemplateRestoreInput): Promise<void> { |
||||
|
return requestClient.put(`/api/text-templating/templates/content/${name}/restore-to-default`, input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新模板内容 |
||||
|
* @param name 模板名称 |
||||
|
* @param input 参数 |
||||
|
* @returns 模板内容数据传输对象 |
||||
|
*/ |
||||
|
export function updateApi(name: string, input: TextTemplateContentUpdateDto): Promise<TextTemplateContentDto> { |
||||
|
return requestClient.put<TextTemplateContentDto>(`/api/text-templating/templates/content/${name}`, input); |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { |
||||
|
TextTemplateDefinitionCreateDto, |
||||
|
TextTemplateDefinitionDto, |
||||
|
TextTemplateDefinitionGetListInput, |
||||
|
TextTemplateDefinitionUpdateDto, |
||||
|
} from "#/text-templating/definitions"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
/** |
||||
|
* 新增模板定义 |
||||
|
* @param input 参数 |
||||
|
* @returns 模板定义数据传输对象 |
||||
|
*/ |
||||
|
export function createApi(input: TextTemplateDefinitionCreateDto): Promise<TextTemplateDefinitionDto> { |
||||
|
return requestClient.post<TextTemplateDefinitionDto>("/api/text-templating/template/definitions", input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除模板定义 |
||||
|
* @param name 模板名称 |
||||
|
*/ |
||||
|
export function deleteApi(name: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/text-templating/template/definitions/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取模板定义 |
||||
|
* @param name 模板名称 |
||||
|
* @returns 模板定义数据传输对象 |
||||
|
*/ |
||||
|
export function getApi(name: string): Promise<TextTemplateDefinitionDto> { |
||||
|
return requestClient.get<TextTemplateDefinitionDto>(`/api/text-templating/template/definitions/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取模板定义列表 |
||||
|
* @param input 过滤参数 |
||||
|
* @returns 模板定义数据传输对象列表 |
||||
|
*/ |
||||
|
export function getListApi( |
||||
|
input?: TextTemplateDefinitionGetListInput, |
||||
|
): Promise<ListResultDto<TextTemplateDefinitionDto>> { |
||||
|
return requestClient.get<ListResultDto<TextTemplateDefinitionDto>>("/api/text-templating/template/definitions", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新模板定义 |
||||
|
* @param name 模板名称 |
||||
|
* @returns 模板定义数据传输对象 |
||||
|
*/ |
||||
|
export function updateApi(name: string, input: TextTemplateDefinitionUpdateDto): Promise<TextTemplateDefinitionDto> { |
||||
|
return requestClient.put<TextTemplateDefinitionDto>(`/api/text-templating/template/definitions/${name}`, input); |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
import type { PagedResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { |
||||
|
WebhookSendRecordDeleteManyInput, |
||||
|
WebhookSendRecordDto, |
||||
|
WebhookSendRecordGetListInput, |
||||
|
WebhookSendRecordResendManyInput, |
||||
|
} from "#/webhooks/send-attempts"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
/** |
||||
|
* 查询发送记录 |
||||
|
* @param id 记录Id |
||||
|
* @returns 发送记录Dto |
||||
|
*/ |
||||
|
export function getApi(id: string): Promise<WebhookSendRecordDto> { |
||||
|
return requestClient.get<WebhookSendRecordDto>(`/api/webhooks/send-attempts/${id}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除发送记录 |
||||
|
* @param id 记录Id |
||||
|
*/ |
||||
|
export function deleteApi(id: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/webhooks/send-attempts/${id}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 批量删除发送记录 |
||||
|
* @param input 参数 |
||||
|
*/ |
||||
|
export function bulkDeleteApi(input: WebhookSendRecordDeleteManyInput): Promise<void> { |
||||
|
return requestClient.delete("/api/webhooks/send-attempts/delete-many", { |
||||
|
data: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询发送记录分页列表 |
||||
|
* @param input 过滤参数 |
||||
|
* @returns 发送记录Dto分页列表 |
||||
|
*/ |
||||
|
export function getPagedListApi(input: WebhookSendRecordGetListInput): Promise<PagedResultDto<WebhookSendRecordDto>> { |
||||
|
return requestClient.get<PagedResultDto<WebhookSendRecordDto>>("/api/webhooks/send-attempts", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 重新发送 |
||||
|
* @param id 记录Id |
||||
|
*/ |
||||
|
export function reSendApi(id: string): Promise<void> { |
||||
|
return requestClient.post(`/api/webhooks/send-attempts/${id}/resend`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 批量重新发送 |
||||
|
* @param input 参数 |
||||
|
*/ |
||||
|
export function bulkReSendApi(input: WebhookSendRecordResendManyInput): Promise<void> { |
||||
|
return requestClient.post("/api/webhooks/send-attempts/resend-many", input); |
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
import type { ListResultDto, PagedResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { |
||||
|
WebhookAvailableGroupDto, |
||||
|
WebhookSubscriptionCreateDto, |
||||
|
WebhookSubscriptionDeleteManyInput, |
||||
|
WebhookSubscriptionDto, |
||||
|
WebhookSubscriptionGetListInput, |
||||
|
WebhookSubscriptionUpdateDto, |
||||
|
} from "#/webhooks/subscriptions"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
/** |
||||
|
* 创建订阅 |
||||
|
* @param input 参数 |
||||
|
* @returns 订阅Dto |
||||
|
*/ |
||||
|
export function createApi(input: WebhookSubscriptionCreateDto): Promise<WebhookSubscriptionDto> { |
||||
|
return requestClient.post<WebhookSubscriptionDto>("/api/webhooks/subscriptions", input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除订阅 |
||||
|
* @param id 订阅Id |
||||
|
*/ |
||||
|
export function deleteApi(id: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/webhooks/subscriptions/${id}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 批量删除订阅 |
||||
|
* @param input 参数 |
||||
|
*/ |
||||
|
export function bulkDeleteApi(input: WebhookSubscriptionDeleteManyInput): Promise<void> { |
||||
|
return requestClient.delete("/api/webhooks/subscriptions/delete-many", { |
||||
|
data: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询所有可用的Webhook分组列表 |
||||
|
* @returns Webhook分组列表 |
||||
|
*/ |
||||
|
export function getAllAvailableWebhooksApi(): Promise<ListResultDto<WebhookAvailableGroupDto>> { |
||||
|
return requestClient.get<ListResultDto<WebhookAvailableGroupDto>>("/api/webhooks/subscriptions/availables"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询订阅 |
||||
|
* @param id 订阅Id |
||||
|
* @returns 订阅Dto |
||||
|
*/ |
||||
|
export function getApi(id: string): Promise<WebhookSubscriptionDto> { |
||||
|
return requestClient.get<WebhookSubscriptionDto>(`/api/webhooks/subscriptions/${id}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询订阅分页列表 |
||||
|
* @param input 过滤参数 |
||||
|
* @returns 订阅Dto列表 |
||||
|
*/ |
||||
|
export function getPagedListApi( |
||||
|
input: WebhookSubscriptionGetListInput, |
||||
|
): Promise<PagedResultDto<WebhookSubscriptionDto>> { |
||||
|
return requestClient.get<PagedResultDto<WebhookSubscriptionDto>>("/api/webhooks/subscriptions", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新订阅 |
||||
|
* @param id 订阅Id |
||||
|
* @param input 更新参数 |
||||
|
* @returns 订阅Dto |
||||
|
*/ |
||||
|
export function updateApi(id: string, input: WebhookSubscriptionUpdateDto): Promise<WebhookSubscriptionDto> { |
||||
|
return requestClient.put<WebhookSubscriptionDto>(`/api/webhooks/subscriptions/${id}`, input); |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { |
||||
|
WebhookDefinitionCreateDto, |
||||
|
WebhookDefinitionDto, |
||||
|
WebhookDefinitionGetListInput, |
||||
|
WebhookDefinitionUpdateDto, |
||||
|
} from "#/webhooks/definitions"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
/** |
||||
|
* 删除Webhook定义 |
||||
|
* @param name Webhook名称 |
||||
|
*/ |
||||
|
export function deleteApi(name: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/webhooks/definitions/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询Webhook定义 |
||||
|
* @param name Webhook名称 |
||||
|
* @returns Webhook定义数据传输对象 |
||||
|
*/ |
||||
|
export function getApi(name: string): Promise<WebhookDefinitionDto> { |
||||
|
return requestClient.get<WebhookDefinitionDto>(`/api/webhooks/definitions/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询Webhook定义列表 |
||||
|
* @param input Webhook过滤条件 |
||||
|
* @returns Webhook定义数据传输对象列表 |
||||
|
*/ |
||||
|
export function getListApi(input?: WebhookDefinitionGetListInput): Promise<ListResultDto<WebhookDefinitionDto>> { |
||||
|
return requestClient.get<ListResultDto<WebhookDefinitionDto>>("/api/webhooks/definitions", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建Webhook定义 |
||||
|
* @param input Webhook定义参数 |
||||
|
* @returns Webhook定义数据传输对象 |
||||
|
*/ |
||||
|
export function createApi(input: WebhookDefinitionCreateDto): Promise<WebhookDefinitionDto> { |
||||
|
return requestClient.post<WebhookDefinitionDto>("/api/webhooks/definitions", input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新Webhook定义 |
||||
|
* @param name Webhook名称 |
||||
|
* @param input Webhook定义参数 |
||||
|
* @returns Webhook定义数据传输对象 |
||||
|
*/ |
||||
|
export function updateApi(name: string, input: WebhookDefinitionUpdateDto): Promise<WebhookDefinitionDto> { |
||||
|
return requestClient.put<WebhookDefinitionDto>(`/api/webhooks/definitions/${name}`, input); |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
import type { ListResultDto } from "#/abp-core"; |
||||
|
|
||||
|
import type { |
||||
|
WebhookGroupDefinitionCreateDto, |
||||
|
WebhookGroupDefinitionDto, |
||||
|
WebhookGroupDefinitionGetListInput, |
||||
|
WebhookGroupDefinitionUpdateDto, |
||||
|
} from "#/webhooks/groups"; |
||||
|
|
||||
|
import requestClient from "../request"; |
||||
|
|
||||
|
/** |
||||
|
* 删除Webhook分组定义 |
||||
|
* @param name Webhook分组名称 |
||||
|
*/ |
||||
|
export function deleteApi(name: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/webhooks/definitions/groups/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询Webhook分组定义 |
||||
|
* @param name Webhook分组名称 |
||||
|
* @returns Webhook分组定义数据传输对象 |
||||
|
*/ |
||||
|
export function getApi(name: string): Promise<WebhookGroupDefinitionDto> { |
||||
|
return requestClient.get<WebhookGroupDefinitionDto>(`/api/webhooks/definitions/groups/${name}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询Webhook分组定义列表 |
||||
|
* @param input Webhook分组过滤条件 |
||||
|
* @returns Webhook分组定义数据传输对象列表 |
||||
|
*/ |
||||
|
export function getListApi( |
||||
|
input?: WebhookGroupDefinitionGetListInput, |
||||
|
): Promise<ListResultDto<WebhookGroupDefinitionDto>> { |
||||
|
return requestClient.get<ListResultDto<WebhookGroupDefinitionDto>>("/api/webhooks/definitions/groups", { |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建Webhook分组定义 |
||||
|
* @param input Webhook分组定义参数 |
||||
|
* @returns Webhook分组定义数据传输对象 |
||||
|
*/ |
||||
|
export function createApi(input: WebhookGroupDefinitionCreateDto): Promise<WebhookGroupDefinitionDto> { |
||||
|
return requestClient.post<WebhookGroupDefinitionDto>("/api/webhooks/definitions/groups", input); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新Webhook分组定义 |
||||
|
* @param name Webhook分组名称 |
||||
|
* @param input Webhook分组定义参数 |
||||
|
* @returns Webhook分组定义数据传输对象 |
||||
|
*/ |
||||
|
export function updateApi(name: string, input: WebhookGroupDefinitionUpdateDto): Promise<WebhookGroupDefinitionDto> { |
||||
|
return requestClient.put<WebhookGroupDefinitionDto>(`/api/webhooks/definitions/groups/${name}`, input); |
||||
|
} |
||||
@ -0,0 +1,110 @@ |
|||||
|
import type React from "react"; |
||||
|
import { useState } from "react"; |
||||
|
import { Modal, Form, Input } from "antd"; |
||||
|
import { useTranslation } from "react-i18next"; |
||||
|
import { toast } from "sonner"; |
||||
|
import { useMutation } from "@tanstack/react-query"; |
||||
|
import { changePasswordApi } from "@/api/account/profile"; |
||||
|
import { usePasswordValidator } from "@/hooks/abp/identity/usePasswordValidator"; |
||||
|
|
||||
|
interface Props { |
||||
|
visible: boolean; |
||||
|
onClose: () => void; |
||||
|
} |
||||
|
|
||||
|
const ChangePasswordModal: React.FC<Props> = ({ visible, onClose }) => { |
||||
|
const { t: $t } = useTranslation(); |
||||
|
const [form] = Form.useForm(); |
||||
|
const [submitting, setSubmitting] = useState(false); |
||||
|
const { validate } = usePasswordValidator(); |
||||
|
|
||||
|
const { mutateAsync: changePassword } = useMutation({ |
||||
|
mutationFn: changePasswordApi, |
||||
|
onSuccess: () => { |
||||
|
toast.success($t("AbpIdentity.PasswordChangedMessage")); |
||||
|
onClose(); |
||||
|
form.resetFields(); |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const handleSubmit = async () => { |
||||
|
try { |
||||
|
const values = await form.validateFields(); |
||||
|
setSubmitting(true); |
||||
|
await changePassword({ |
||||
|
currentPassword: values.currentPassword, |
||||
|
newPassword: values.newPassword, |
||||
|
}); |
||||
|
} catch (error) { |
||||
|
console.error(error); |
||||
|
} finally { |
||||
|
setSubmitting(false); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<Modal |
||||
|
title={$t("AbpAccount.ResetMyPassword")} |
||||
|
open={visible} |
||||
|
onCancel={onClose} |
||||
|
onOk={handleSubmit} |
||||
|
confirmLoading={submitting} |
||||
|
destroyOnClose |
||||
|
> |
||||
|
<Form form={form} layout="vertical"> |
||||
|
<Form.Item |
||||
|
name="currentPassword" |
||||
|
label={$t("AbpAccount.DisplayName:CurrentPassword")} |
||||
|
rules={[{ required: true, message: $t("AbpUi.ThisFieldIsRequired") }]} |
||||
|
> |
||||
|
<Input.Password /> |
||||
|
</Form.Item> |
||||
|
|
||||
|
<Form.Item |
||||
|
name="newPassword" |
||||
|
label={$t("AbpAccount.DisplayName:NewPassword")} |
||||
|
rules={[ |
||||
|
{ required: true, message: $t("AbpUi.ThisFieldIsRequired") }, |
||||
|
({ getFieldValue }) => ({ |
||||
|
validator: async (_, value) => { |
||||
|
if (!value) return Promise.resolve(); |
||||
|
if (value === getFieldValue("currentPassword")) { |
||||
|
return Promise.reject(new Error($t("AbpAccount.NewPasswordSameAsOld"))); |
||||
|
} |
||||
|
try { |
||||
|
await validate(value); |
||||
|
return Promise.resolve(); |
||||
|
} catch (error: any) { |
||||
|
return Promise.reject(error); // Validation error from hook
|
||||
|
} |
||||
|
}, |
||||
|
}), |
||||
|
]} |
||||
|
> |
||||
|
<Input.Password /> |
||||
|
</Form.Item> |
||||
|
|
||||
|
<Form.Item |
||||
|
name="newPasswordConfirm" |
||||
|
label={$t("AbpAccount.DisplayName:NewPasswordConfirm")} |
||||
|
dependencies={["newPassword"]} |
||||
|
rules={[ |
||||
|
{ required: true, message: $t("AbpUi.ThisFieldIsRequired") }, |
||||
|
({ getFieldValue }) => ({ |
||||
|
validator(_, value) { |
||||
|
if (!value || getFieldValue("newPassword") === value) { |
||||
|
return Promise.resolve(); |
||||
|
} |
||||
|
return Promise.reject(new Error($t("AbpIdentity.Volo_Abp_Identity:PasswordConfirmationFailed"))); |
||||
|
}, |
||||
|
}), |
||||
|
]} |
||||
|
> |
||||
|
<Input.Password /> |
||||
|
</Form.Item> |
||||
|
</Form> |
||||
|
</Modal> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default ChangePasswordModal; |
||||
@ -0,0 +1,112 @@ |
|||||
|
import type React from "react"; |
||||
|
import { useState, useEffect } from "react"; |
||||
|
import { Modal, Form, Input, Button, Col, Row } from "antd"; |
||||
|
import { useTranslation } from "react-i18next"; |
||||
|
import { toast } from "sonner"; |
||||
|
import { useMutation } from "@tanstack/react-query"; |
||||
|
import { changePhoneNumberApi, sendChangePhoneNumberCodeApi } from "@/api/account/profile"; |
||||
|
|
||||
|
interface Props { |
||||
|
visible: boolean; |
||||
|
onClose: () => void; |
||||
|
onChange: (phoneNumber: string) => void; |
||||
|
} |
||||
|
|
||||
|
const ChangePhoneNumberModal: React.FC<Props> = ({ visible, onClose, onChange }) => { |
||||
|
const { t: $t } = useTranslation(); |
||||
|
const [form] = Form.useForm(); |
||||
|
const [submitting, setSubmitting] = useState(false); |
||||
|
const [countdown, setCountdown] = useState(0); |
||||
|
|
||||
|
// Custom countdown logic using useEffect
|
||||
|
useEffect(() => { |
||||
|
let timer: NodeJS.Timeout; |
||||
|
if (countdown > 0) { |
||||
|
timer = setInterval(() => { |
||||
|
setCountdown((prev) => (prev > 0 ? prev - 1 : 0)); |
||||
|
}, 1000); |
||||
|
} |
||||
|
return () => clearInterval(timer); |
||||
|
}, [countdown]); |
||||
|
|
||||
|
const { mutateAsync: sendCode } = useMutation({ |
||||
|
mutationFn: sendChangePhoneNumberCodeApi, |
||||
|
onSuccess: () => { |
||||
|
setCountdown(60); // Set countdown to 60 seconds
|
||||
|
toast.success($t("AbpUi.SavedSuccessfully")); |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const { mutateAsync: changePhone } = useMutation({ |
||||
|
mutationFn: changePhoneNumberApi, |
||||
|
onSuccess: (_, variables) => { |
||||
|
toast.success($t("AbpAccount.PhoneNumberChangedMessage")); |
||||
|
onChange(variables.newPhoneNumber); |
||||
|
onClose(); |
||||
|
form.resetFields(); |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const handleSendCode = async () => { |
||||
|
try { |
||||
|
await form.validateFields(["newPhoneNumber"]); |
||||
|
const newPhone = form.getFieldValue("newPhoneNumber"); |
||||
|
await sendCode({ newPhoneNumber: newPhone }); |
||||
|
} catch (error) { |
||||
|
console.error(error); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const handleSubmit = async () => { |
||||
|
try { |
||||
|
const values = await form.validateFields(); |
||||
|
setSubmitting(true); |
||||
|
await changePhone({ |
||||
|
code: values.code, |
||||
|
newPhoneNumber: values.newPhoneNumber, |
||||
|
}); |
||||
|
} catch (error) { |
||||
|
console.error(error); |
||||
|
} finally { |
||||
|
setSubmitting(false); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<Modal |
||||
|
title={$t("AbpIdentity.PhoneNumber")} |
||||
|
open={visible} |
||||
|
onCancel={onClose} |
||||
|
onOk={handleSubmit} |
||||
|
confirmLoading={submitting} |
||||
|
destroyOnClose |
||||
|
> |
||||
|
<Form form={form} layout="vertical"> |
||||
|
<Form.Item |
||||
|
name="newPhoneNumber" |
||||
|
label={$t("AbpIdentity.DisplayName:NewPhoneNumber")} |
||||
|
rules={[{ required: true, message: $t("AbpUi.ThisFieldIsRequired") }]} |
||||
|
> |
||||
|
<Input /> |
||||
|
</Form.Item> |
||||
|
|
||||
|
<Form.Item label={$t("AbpIdentity.DisplayName:SmsVerifyCode")}> |
||||
|
<Row gutter={8}> |
||||
|
<Col span={16}> |
||||
|
<Form.Item name="code" noStyle rules={[{ required: true, message: $t("AbpUi.ThisFieldIsRequired") }]}> |
||||
|
<Input /> |
||||
|
</Form.Item> |
||||
|
</Col> |
||||
|
<Col span={8}> |
||||
|
<Button block disabled={countdown > 0} onClick={handleSendCode}> |
||||
|
{countdown === 0 ? $t("authentication.sendCode") : `${countdown}s`} |
||||
|
</Button> |
||||
|
</Col> |
||||
|
</Row> |
||||
|
</Form.Item> |
||||
|
</Form> |
||||
|
</Modal> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default ChangePhoneNumberModal; |
||||
@ -1,14 +1,134 @@ |
|||||
import { Card, Empty } from "antd"; |
import type React from "react"; |
||||
|
import { useEffect, useState } from "react"; |
||||
|
import { Card, Collapse, List, Switch, Skeleton } from "antd"; |
||||
import { useTranslation } from "react-i18next"; |
import { useTranslation } from "react-i18next"; |
||||
|
import { toast } from "sonner"; |
||||
|
import { getMySubscribesApi, subscribeApi, unSubscribeApi } from "@/api/management/notifications/my-subscribes"; |
||||
|
import { getAssignableNotifiersApi } from "@/api/management/notifications/notifications"; |
||||
|
|
||||
const BindSettings: React.FC = () => { |
interface NotificationItem { |
||||
|
description?: string; |
||||
|
displayName: string; |
||||
|
isSubscribe: boolean; |
||||
|
loading: boolean; |
||||
|
name: string; |
||||
|
} |
||||
|
|
||||
|
interface NotificationGroup { |
||||
|
displayName: string; |
||||
|
name: string; |
||||
|
notifications: NotificationItem[]; |
||||
|
} |
||||
|
|
||||
|
const NotificationSettings: React.FC = () => { |
||||
const { t: $t } = useTranslation(); |
const { t: $t } = useTranslation(); |
||||
|
const [loading, setLoading] = useState(false); |
||||
|
const [notificationGroups, setNotificationGroups] = useState<NotificationGroup[]>([]); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
initData(); |
||||
|
}, []); |
||||
|
|
||||
|
const initData = async () => { |
||||
|
try { |
||||
|
setLoading(true); |
||||
|
const [subRes, notifierRes] = await Promise.all([getMySubscribesApi(), getAssignableNotifiersApi()]); |
||||
|
|
||||
|
const groups: NotificationGroup[] = notifierRes.items.map((group) => { |
||||
|
const notifications: NotificationItem[] = group.notifications.map((notification) => ({ |
||||
|
description: notification.description, |
||||
|
displayName: notification.displayName, |
||||
|
// Check if the user is already subscribed
|
||||
|
isSubscribe: subRes.items.some((x) => x.name === notification.name), |
||||
|
loading: false, |
||||
|
name: notification.name, |
||||
|
})); |
||||
|
|
||||
|
return { |
||||
|
displayName: group.displayName, |
||||
|
name: group.name, |
||||
|
notifications, |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
setNotificationGroups(groups); |
||||
|
} catch (error) { |
||||
|
console.error(error); |
||||
|
} finally { |
||||
|
setLoading(false); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const handleSubscribeChange = async (checked: boolean, groupIndex: number, itemIndex: number) => { |
||||
|
const targetGroup = notificationGroups[groupIndex]; |
||||
|
const targetItem = targetGroup.notifications[itemIndex]; |
||||
|
|
||||
|
// Optimistic Update / Loading State
|
||||
|
const updateState = (isLoading: boolean, isSubscribed: boolean) => { |
||||
|
setNotificationGroups((prev) => { |
||||
|
const next = [...prev]; |
||||
|
const group = { ...next[groupIndex] }; |
||||
|
const items = [...group.notifications]; |
||||
|
items[itemIndex] = { ...items[itemIndex], loading: isLoading, isSubscribe: isSubscribed }; |
||||
|
group.notifications = items; |
||||
|
next[groupIndex] = group; |
||||
|
return next; |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
updateState(true, checked); |
||||
|
|
||||
|
try { |
||||
|
if (checked) { |
||||
|
await subscribeApi(targetItem.name); |
||||
|
} else { |
||||
|
await unSubscribeApi(targetItem.name); |
||||
|
} |
||||
|
toast.success($t("AbpUi.SavedSuccessfully")); |
||||
|
updateState(false, checked); |
||||
|
} catch (error) { |
||||
|
console.error(error); |
||||
|
// Revert on error
|
||||
|
updateState(false, !checked); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
if (loading && notificationGroups.length === 0) { |
||||
|
return ( |
||||
|
<Card title={$t("abp.account.settings.noticeSettings")} bordered={false}> |
||||
|
<Skeleton active paragraph={{ rows: 6 }} /> |
||||
|
</Card> |
||||
|
); |
||||
|
} |
||||
|
|
||||
return ( |
return ( |
||||
<Card bordered={false} title={$t("abp.account.settings.noticeSettings")}> |
<Card title={$t("abp.account.settings.noticeSettings")} bordered={false}> |
||||
<Empty /> |
<Collapse defaultActiveKey={notificationGroups.map((g) => g.name)}> |
||||
|
{notificationGroups.map((group, groupIndex) => ( |
||||
|
<Collapse.Panel header={group.displayName} key={group.name}> |
||||
|
<List |
||||
|
itemLayout="horizontal" |
||||
|
dataSource={group.notifications} |
||||
|
renderItem={(item, itemIndex) => ( |
||||
|
<List.Item |
||||
|
actions={[ |
||||
|
<Switch |
||||
|
key="switch" |
||||
|
checked={item.isSubscribe} |
||||
|
loading={item.loading} |
||||
|
onChange={(checked) => handleSubscribeChange(checked, groupIndex, itemIndex)} |
||||
|
/>, |
||||
|
]} |
||||
|
> |
||||
|
<List.Item.Meta title={item.displayName} description={item.description} /> |
||||
|
</List.Item> |
||||
|
)} |
||||
|
/> |
||||
|
</Collapse.Panel> |
||||
|
))} |
||||
|
</Collapse> |
||||
</Card> |
</Card> |
||||
); |
); |
||||
}; |
}; |
||||
|
|
||||
export default BindSettings; |
export default NotificationSettings; |
||||
|
|||||
@ -0,0 +1,87 @@ |
|||||
|
import type React from "react"; |
||||
|
import { useEffect, useState, useMemo } from "react"; |
||||
|
import { Select, type SelectProps, Spin } from "antd"; |
||||
|
|
||||
|
function getNestedValue(obj: any, path: string) { |
||||
|
if (!path) return undefined; |
||||
|
return path.split(".").reduce((acc, part) => acc?.[part], obj); |
||||
|
} |
||||
|
|
||||
|
export interface ApiSelectProps extends Omit<SelectProps, "options"> { |
||||
|
/** Function to fetch data, returning a Promise */ |
||||
|
api?: (params?: any) => Promise<any>; |
||||
|
/** Parameters to pass to the api function */ |
||||
|
params?: any; |
||||
|
/** Key in the response object containing the array (e.g., 'items') */ |
||||
|
resultField?: string; |
||||
|
/** Property name to use for the label */ |
||||
|
labelField?: string; |
||||
|
/** Property name to use for the value */ |
||||
|
valueField?: string; |
||||
|
/** Trigger fetch immediately on mount */ |
||||
|
immediate?: boolean; |
||||
|
} |
||||
|
|
||||
|
const ApiSelect: React.FC<ApiSelectProps> = ({ |
||||
|
api, |
||||
|
params, |
||||
|
resultField = "items", |
||||
|
labelField = "label", |
||||
|
valueField = "value", |
||||
|
immediate = true, |
||||
|
...props |
||||
|
}) => { |
||||
|
const [data, setData] = useState<any[]>([]); |
||||
|
const [loading, setLoading] = useState(false); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (immediate && api) { |
||||
|
fetchData(); |
||||
|
} |
||||
|
}, [JSON.stringify(params)]); |
||||
|
|
||||
|
const fetchData = async () => { |
||||
|
if (!api) return; |
||||
|
setLoading(true); |
||||
|
try { |
||||
|
const res = await api(params); |
||||
|
// Use helper instead of lodash.get
|
||||
|
const list = resultField ? getNestedValue(res, resultField) : res; |
||||
|
|
||||
|
if (Array.isArray(list)) { |
||||
|
setData(list); |
||||
|
} else if (Array.isArray(res)) { |
||||
|
// Fallback: if extracting resultField failed but response itself is an array
|
||||
|
setData(res); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error("ApiSelect fetch error:", error); |
||||
|
} finally { |
||||
|
setLoading(false); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const options = useMemo(() => { |
||||
|
return data.map((item) => ({ |
||||
|
...item, |
||||
|
label: item[labelField], |
||||
|
value: item[valueField], |
||||
|
})); |
||||
|
}, [data, labelField, valueField]); |
||||
|
|
||||
|
return ( |
||||
|
<Select |
||||
|
loading={loading} |
||||
|
options={options} |
||||
|
notFoundContent={loading ? <Spin size="small" /> : null} |
||||
|
onDropdownVisibleChange={(open) => { |
||||
|
if (open && data.length === 0 && !loading) { |
||||
|
fetchData(); |
||||
|
} |
||||
|
}} |
||||
|
{...props} |
||||
|
/> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default ApiSelect; |
||||
@ -0,0 +1,73 @@ |
|||||
|
import type React from "react"; |
||||
|
import { useEffect, useState } from "react"; |
||||
|
import { TreeSelect, type TreeSelectProps, Spin } from "antd"; |
||||
|
|
||||
|
function getNestedValue(obj: any, path: string) { |
||||
|
if (!path) return undefined; |
||||
|
return path.split(".").reduce((acc, part) => acc?.[part], obj); |
||||
|
} |
||||
|
|
||||
|
export interface ApiTreeSelectProps extends Omit<TreeSelectProps, "treeData"> { |
||||
|
api?: (params?: any) => Promise<any>; |
||||
|
params?: any; |
||||
|
resultField?: string; |
||||
|
immediate?: boolean; |
||||
|
fieldNames?: { label: string; value: string; children: string }; |
||||
|
} |
||||
|
|
||||
|
const ApiTreeSelect: React.FC<ApiTreeSelectProps> = ({ |
||||
|
api, |
||||
|
params, |
||||
|
resultField, |
||||
|
immediate = true, |
||||
|
fieldNames = { label: "label", value: "value", children: "children" }, |
||||
|
...props |
||||
|
}) => { |
||||
|
const [treeData, setTreeData] = useState<any[]>([]); |
||||
|
const [loading, setLoading] = useState(false); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (immediate && api) { |
||||
|
fetchData(); |
||||
|
} |
||||
|
}, [JSON.stringify(params)]); |
||||
|
|
||||
|
const fetchData = async () => { |
||||
|
if (!api) return; |
||||
|
setLoading(true); |
||||
|
try { |
||||
|
const res = await api(params); |
||||
|
|
||||
|
let list = res; |
||||
|
if (resultField) { |
||||
|
list = getNestedValue(res, resultField); |
||||
|
} |
||||
|
|
||||
|
if (Array.isArray(list)) { |
||||
|
setTreeData(list); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error("ApiTreeSelect fetch error:", error); |
||||
|
} finally { |
||||
|
setLoading(false); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<TreeSelect |
||||
|
loading={loading} |
||||
|
treeData={treeData} |
||||
|
fieldNames={fieldNames} |
||||
|
dropdownStyle={{ maxHeight: 400, overflow: "auto" }} |
||||
|
notFoundContent={loading ? <Spin size="small" /> : null} |
||||
|
onDropdownVisibleChange={(open) => { |
||||
|
if (open && treeData.length === 0 && !loading) { |
||||
|
fetchData(); |
||||
|
} |
||||
|
}} |
||||
|
{...props} |
||||
|
/> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default ApiTreeSelect; |
||||
@ -0,0 +1,39 @@ |
|||||
|
import type React from "react"; |
||||
|
import { useMemo } from "react"; |
||||
|
import { Input, type InputProps } from "antd"; |
||||
|
import { Iconify } from "@/components/icon"; // Assumes you have this component
|
||||
|
|
||||
|
export interface IconPickerProps extends Omit<InputProps, "onChange"> { |
||||
|
value?: string; |
||||
|
onChange?: (value: string) => void; |
||||
|
} |
||||
|
|
||||
|
const IconPicker: React.FC<IconPickerProps> = ({ value, onChange, ...props }) => { |
||||
|
// A helper to render the icon preview
|
||||
|
const iconPreview = useMemo(() => { |
||||
|
if (!value) return null; |
||||
|
return ( |
||||
|
<div className="flex items-center justify-center w-6 h-6 text-xl"> |
||||
|
<Iconify icon={value} /> |
||||
|
</div> |
||||
|
); |
||||
|
}, [value]); |
||||
|
|
||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||
|
onChange?.(e.target.value); |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<Input |
||||
|
value={value} |
||||
|
onChange={handleChange} |
||||
|
placeholder="Type icon code (e.g. mdi:home)" |
||||
|
allowClear |
||||
|
// Display the icon at the start of the input
|
||||
|
addonBefore={iconPreview} |
||||
|
{...props} |
||||
|
/> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default IconPicker; |
||||
@ -0,0 +1,333 @@ |
|||||
|
import type { Validator } from "#/abp-core"; |
||||
|
import type React from "react"; |
||||
|
import { useEffect, useState } from "react"; |
||||
|
import { useTranslation } from "react-i18next"; |
||||
|
import { getApi, updateApi } from "@/api/management/features/features"; |
||||
|
import { useMutation } from "@tanstack/react-query"; |
||||
|
import { toast } from "sonner"; |
||||
|
import type { FeatureGroupDto, UpdateFeaturesDto } from "#/management/features/features"; |
||||
|
|
||||
|
import { Modal, Form, Tabs, Card, Input, InputNumber, Checkbox, Select, Spin } from "antd"; |
||||
|
import { useValidation } from "@/hooks/abp/use-validation"; |
||||
|
import { useLocalizer } from "@/hooks/abp/use-localization"; |
||||
|
|
||||
|
interface FeatureManagementModalProps { |
||||
|
visible: boolean; |
||||
|
onClose: () => void; |
||||
|
providerName: string; |
||||
|
providerKey?: string; |
||||
|
displayName?: string; // For the modal title
|
||||
|
} |
||||
|
|
||||
|
// Helper Interface for Form Data Structure
|
||||
|
export interface FeatureFormData { |
||||
|
groups: FeatureGroupDto[]; |
||||
|
} |
||||
|
|
||||
|
const FeatureManagementModal: React.FC<FeatureManagementModalProps> = ({ |
||||
|
visible, |
||||
|
onClose, |
||||
|
providerName, |
||||
|
providerKey, |
||||
|
displayName, |
||||
|
}) => { |
||||
|
const { t: $t } = useTranslation(); |
||||
|
const { Lr } = useLocalizer(); |
||||
|
const [form] = Form.useForm(); |
||||
|
|
||||
|
// Validation Hook
|
||||
|
const { fieldMustBeetWeen, fieldMustBeStringWithMinimumLengthAndMaximumLength, fieldRequired } = useValidation(); |
||||
|
|
||||
|
// State
|
||||
|
const [activeTabKey, setActiveTabKey] = useState<string>(""); |
||||
|
const [groups, setGroups] = useState<FeatureGroupDto[]>([]); |
||||
|
|
||||
|
// --- Helpers ---
|
||||
|
|
||||
|
/** |
||||
|
* Generates AntD Form Rules based on ABP Validator metadata (custom hook) |
||||
|
*/ |
||||
|
const createRules = (fieldLabel: string, validator: Validator) => { |
||||
|
const rules: any[] = []; |
||||
|
if (validator?.properties) { |
||||
|
switch (validator.name) { |
||||
|
case "NUMERIC": { |
||||
|
rules.push( |
||||
|
...fieldMustBeetWeen({ |
||||
|
name: fieldLabel, |
||||
|
start: Number(validator.properties.MinValue), |
||||
|
end: Number(validator.properties.MaxValue), |
||||
|
trigger: "change", |
||||
|
}), |
||||
|
); |
||||
|
break; |
||||
|
} |
||||
|
case "STRING": { |
||||
|
if (validator.properties.AllowNull && String(validator.properties.AllowNull).toLowerCase() === "true") { |
||||
|
rules.push( |
||||
|
...fieldRequired({ |
||||
|
name: fieldLabel, |
||||
|
trigger: "blur", |
||||
|
}), |
||||
|
); |
||||
|
} |
||||
|
rules.push( |
||||
|
...fieldMustBeStringWithMinimumLengthAndMaximumLength({ |
||||
|
name: fieldLabel, |
||||
|
minimum: Number(validator.properties.MinLength), |
||||
|
maximum: Number(validator.properties.MaxLength), |
||||
|
trigger: "blur", |
||||
|
}), |
||||
|
); |
||||
|
break; |
||||
|
} |
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
return rules; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Process raw API data for the UI |
||||
|
* 1. Localize Selection items |
||||
|
* 2. Convert string values to boolean/number for inputs |
||||
|
*/ |
||||
|
const mapFeaturesForUi = (rawGroups: FeatureGroupDto[]) => { |
||||
|
// Deep clone to avoid mutating read-only props if using strict mode
|
||||
|
const processedGroups = JSON.parse(JSON.stringify(rawGroups)) as FeatureGroupDto[]; |
||||
|
|
||||
|
processedGroups.forEach((group) => { |
||||
|
group.features.forEach((feature) => { |
||||
|
// Handle Selection Localization
|
||||
|
if (feature.valueType?.name === "SelectionStringValueType") { |
||||
|
const valueType: any = feature.valueType; |
||||
|
if (valueType.itemSource?.items) { |
||||
|
valueType.itemSource.items.forEach((item: any) => { |
||||
|
if (item.displayText?.resourceName === "Fixed") { |
||||
|
item.displayName = item.displayText.name; |
||||
|
} else { |
||||
|
item.displayName = Lr(item.displayText.resourceName, item.displayText.name); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Handle Value Conversion
|
||||
|
else if (feature.valueType?.validator) { |
||||
|
switch (feature.valueType.validator.name) { |
||||
|
case "BOOLEAN": |
||||
|
feature.value = String(feature.value).toLowerCase() === "true"; |
||||
|
break; |
||||
|
case "NUMERIC": |
||||
|
feature.value = Number(feature.value); |
||||
|
break; |
||||
|
default: |
||||
|
// Keep as string
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
return processedGroups; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Convert Form Data back to DTO for API submission |
||||
|
*/ |
||||
|
const getFeatureInput = (formValues: any): UpdateFeaturesDto => { |
||||
|
const input: UpdateFeaturesDto = { features: [] }; |
||||
|
|
||||
|
const formGroups = formValues.groups as FeatureGroupDto[]; |
||||
|
if (!formGroups) return input; |
||||
|
|
||||
|
formGroups.forEach((g) => { |
||||
|
if (!g?.features) return; |
||||
|
g.features.forEach((f) => { |
||||
|
// Only include non-null values
|
||||
|
if (f.value !== null && f.value !== undefined) { |
||||
|
input.features.push({ |
||||
|
name: f.name, |
||||
|
value: String(f.value), // Convert back to string
|
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
return input; |
||||
|
}; |
||||
|
|
||||
|
// --- API Hooks ---
|
||||
|
|
||||
|
const { mutateAsync: fetchData, isPending: isLoading } = useMutation({ |
||||
|
mutationFn: async () => { |
||||
|
if (!providerName) return; |
||||
|
const res = await getApi({ providerName, providerKey }); |
||||
|
return res.groups; |
||||
|
}, |
||||
|
onSuccess: (rawGroups) => { |
||||
|
if (rawGroups) { |
||||
|
const processed = mapFeaturesForUi(rawGroups); |
||||
|
setGroups(processed); |
||||
|
form.setFieldsValue({ groups: processed }); |
||||
|
|
||||
|
// Set active tab to first group
|
||||
|
if (processed.length > 0 && !activeTabKey) { |
||||
|
setActiveTabKey(processed[0].name); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const { mutateAsync: updateData, isPending: isSaving } = useMutation({ |
||||
|
mutationFn: async (input: UpdateFeaturesDto) => { |
||||
|
return updateApi({ providerName, providerKey }, input); |
||||
|
}, |
||||
|
onSuccess: () => { |
||||
|
toast.success($t("AbpUi.SavedSuccessfully")); |
||||
|
onClose(); |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
// --- Effects ---
|
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (visible) { |
||||
|
form.resetFields(); |
||||
|
setGroups([]); |
||||
|
setActiveTabKey(""); |
||||
|
fetchData(); |
||||
|
} |
||||
|
}, [visible, providerName, providerKey]); |
||||
|
|
||||
|
// --- Handlers ---
|
||||
|
|
||||
|
const handleOk = async () => { |
||||
|
try { |
||||
|
const values = await form.validateFields(); |
||||
|
const input = getFeatureInput(values); |
||||
|
await updateData(input); |
||||
|
} catch (e) { |
||||
|
console.error("Validation Failed", e); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// --- Renderers ---
|
||||
|
|
||||
|
const renderFeatureInput = (feature: any, groupIndex: number, featureIndex: number) => { |
||||
|
if (!feature.valueType) return null; |
||||
|
|
||||
|
const fieldName = ["groups", groupIndex, "features", featureIndex, "value"]; |
||||
|
const valueTypeName = feature.valueType.name; |
||||
|
const validatorName = feature.valueType.validator?.name; |
||||
|
|
||||
|
// 1. Checkbox (Boolean)
|
||||
|
if (valueTypeName === "ToggleStringValueType" && validatorName === "BOOLEAN") { |
||||
|
return ( |
||||
|
<Form.Item |
||||
|
name={fieldName} |
||||
|
valuePropName="checked" |
||||
|
label={feature.displayName} |
||||
|
extra={feature.description} |
||||
|
rules={createRules(feature.displayName, feature.valueType.validator)} |
||||
|
> |
||||
|
<Checkbox>{feature.displayName}</Checkbox> |
||||
|
</Form.Item> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// 2. Select (Selection)
|
||||
|
if (valueTypeName === "SelectionStringValueType") { |
||||
|
return ( |
||||
|
<Form.Item |
||||
|
name={fieldName} |
||||
|
label={feature.displayName} |
||||
|
extra={feature.description} |
||||
|
rules={createRules(feature.displayName, feature.valueType.validator)} |
||||
|
> |
||||
|
<Select |
||||
|
options={feature.valueType.itemSource?.items || []} |
||||
|
fieldNames={{ label: "displayName", value: "value" }} |
||||
|
/> |
||||
|
</Form.Item> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// 3. Free Text (String or Numeric)
|
||||
|
if (valueTypeName === "FreeTextStringValueType") { |
||||
|
return ( |
||||
|
<Form.Item |
||||
|
name={fieldName} |
||||
|
label={feature.displayName} |
||||
|
extra={feature.description} |
||||
|
rules={createRules(feature.displayName, feature.valueType.validator)} |
||||
|
> |
||||
|
{validatorName === "NUMERIC" ? <InputNumber style={{ width: "100%" }} /> : <Input autoComplete="off" />} |
||||
|
</Form.Item> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// Fallback
|
||||
|
return ( |
||||
|
<Form.Item name={fieldName} label={feature.displayName} extra={feature.description}> |
||||
|
<Input /> |
||||
|
</Form.Item> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
const modalTitle = displayName |
||||
|
? `${$t("AbpFeatureManagement.Features")} - ${displayName}` |
||||
|
: $t("AbpFeatureManagement.Features"); |
||||
|
|
||||
|
return ( |
||||
|
<Modal |
||||
|
title={modalTitle} |
||||
|
open={visible} |
||||
|
onCancel={onClose} |
||||
|
onOk={handleOk} |
||||
|
confirmLoading={isSaving} |
||||
|
centered |
||||
|
width="50%" |
||||
|
maskClosable={false} |
||||
|
destroyOnClose |
||||
|
> |
||||
|
<Spin spinning={isLoading}> |
||||
|
<Form form={form} layout="vertical"> |
||||
|
{/* We render hidden inputs for names to ensure they exist in the form values structure for getFeatureInput logic |
||||
|
*/} |
||||
|
{groups.map((g, gIdx) => ( |
||||
|
<div key={g.name} style={{ display: "none" }}> |
||||
|
{g.features.map((f, fIdx) => ( |
||||
|
<Form.Item key={f.name} name={["groups", gIdx, "features", fIdx, "name"]} initialValue={f.name}> |
||||
|
<Input /> |
||||
|
</Form.Item> |
||||
|
))} |
||||
|
</div> |
||||
|
))} |
||||
|
|
||||
|
<Tabs |
||||
|
tabPosition="left" |
||||
|
activeKey={activeTabKey} |
||||
|
onChange={setActiveTabKey} |
||||
|
items={groups.map((group, groupIndex) => ({ |
||||
|
key: group.name, |
||||
|
label: group.displayName, |
||||
|
children: ( |
||||
|
<div className="h-[34rem] overflow-y-auto pr-2"> |
||||
|
<Card title={group.displayName} bordered={false}> |
||||
|
{group.features.map((feature, featureIndex) => ( |
||||
|
<div key={feature.name}>{renderFeatureInput(feature, groupIndex, featureIndex)}</div> |
||||
|
))} |
||||
|
</Card> |
||||
|
</div> |
||||
|
), |
||||
|
}))} |
||||
|
/> |
||||
|
</Form> |
||||
|
</Spin> |
||||
|
</Modal> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default FeatureManagementModal; |
||||
@ -0,0 +1,149 @@ |
|||||
|
import type React from "react"; |
||||
|
import { useMemo } from "react"; |
||||
|
import { Checkbox, TreeSelect, Spin } from "antd"; |
||||
|
import { useTranslation } from "react-i18next"; |
||||
|
import { useQuery } from "@tanstack/react-query"; |
||||
|
import { getListApi as getFeaturesApi } from "@/api/management/features/feature-definitions"; |
||||
|
import { getListApi as getGroupsApi } from "@/api/management/features/feature-group-definitions"; |
||||
|
|
||||
|
import { localizationSerializer } from "@/utils/abp/localization-serializer"; |
||||
|
import { useLocalizer } from "@/hooks/abp/use-localization"; |
||||
|
|
||||
|
import { listToTree } from "@/utils/tree"; |
||||
|
import { valueTypeSerializer } from "../../string-value-type"; |
||||
|
|
||||
|
interface ValueType { |
||||
|
featureNames: string[]; |
||||
|
requiresAll: boolean; |
||||
|
} |
||||
|
|
||||
|
interface FeatureStateCheckProps { |
||||
|
value?: ValueType; |
||||
|
onChange?: (value: ValueType) => void; |
||||
|
} |
||||
|
|
||||
|
const FeatureStateCheck: React.FC<FeatureStateCheckProps> = ({ |
||||
|
value = { featureNames: [], requiresAll: false }, |
||||
|
onChange, |
||||
|
}) => { |
||||
|
const { t: $t } = useTranslation(); |
||||
|
const { deserialize } = localizationSerializer(); |
||||
|
const { Lr } = useLocalizer(); |
||||
|
|
||||
|
// 1. Fetch Data
|
||||
|
const { data: featureData, isLoading } = useQuery({ |
||||
|
queryKey: ["featureStateCheckData"], |
||||
|
queryFn: async () => { |
||||
|
const [groupsRes, featuresRes] = await Promise.all([getGroupsApi(), getFeaturesApi()]); |
||||
|
|
||||
|
// Filter: Only BOOLEAN features are relevant for state checking
|
||||
|
const validFeatures = featuresRes.items.filter((item) => { |
||||
|
if (item.valueType) { |
||||
|
try { |
||||
|
const vt = valueTypeSerializer.deserialize(item.valueType); |
||||
|
return vt.validator.name === "BOOLEAN"; |
||||
|
} catch { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
return true; |
||||
|
}); |
||||
|
|
||||
|
// Prepare localized features list for lookup and tree building
|
||||
|
const features = validFeatures.map((f) => { |
||||
|
const d = deserialize(f.displayName); |
||||
|
return { |
||||
|
...f, |
||||
|
title: Lr(d.resourceName, d.name), // Localized Title
|
||||
|
key: f.name, |
||||
|
value: f.name, |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
// Prepare localized groups
|
||||
|
const groups = groupsRes.items.map((g) => { |
||||
|
const d = deserialize(g.displayName); |
||||
|
return { |
||||
|
...g, |
||||
|
title: Lr(d.resourceName, d.name), |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
return { groups, features }; |
||||
|
}, |
||||
|
staleTime: Number.POSITIVE_INFINITY, // Config data rarely changes
|
||||
|
}); |
||||
|
|
||||
|
// 2. Construct Tree Data
|
||||
|
const treeData = useMemo(() => { |
||||
|
if (!featureData) return []; |
||||
|
const { groups, features } = featureData; |
||||
|
|
||||
|
return groups.map((group) => { |
||||
|
// Find features belonging to this group
|
||||
|
const groupFeatures = features.filter((f) => f.groupName === group.name); |
||||
|
|
||||
|
// Build hierarchy for features (parent/child)
|
||||
|
const children = listToTree(groupFeatures, { id: "name", pid: "parentName" }); |
||||
|
|
||||
|
// Return Group Node
|
||||
|
return { |
||||
|
title: group.title, |
||||
|
value: group.name, |
||||
|
key: group.name, |
||||
|
selectable: false, // Cannot select the group itself
|
||||
|
checkable: false, // Cannot check the group itself
|
||||
|
disableCheckbox: true, // Visual disabled checkbox
|
||||
|
children: children, |
||||
|
}; |
||||
|
}); |
||||
|
}, [featureData]); |
||||
|
|
||||
|
// 3. Map selected string[] to { label, value }[] for TreeSelect (Required for treeCheckStrictly)
|
||||
|
const treeValue = useMemo(() => { |
||||
|
if (!featureData || !value.featureNames) return []; |
||||
|
return value.featureNames.map((name) => { |
||||
|
const feature = featureData.features.find((f) => f.name === name); |
||||
|
return { |
||||
|
label: feature?.title || name, |
||||
|
value: name, |
||||
|
}; |
||||
|
}); |
||||
|
}, [featureData, value.featureNames]); |
||||
|
|
||||
|
// 4. Handle Change
|
||||
|
const triggerChange = (changedValue: Partial<ValueType>) => { |
||||
|
onChange?.({ ...value, ...changedValue }); |
||||
|
}; |
||||
|
|
||||
|
const onTreeChange = (labeledValues: { label: React.ReactNode; value: string }[]) => { |
||||
|
// Extract just the feature names (strings) to send back
|
||||
|
const names = labeledValues.map((item) => item.value); |
||||
|
triggerChange({ featureNames: names }); |
||||
|
}; |
||||
|
|
||||
|
if (isLoading) return <Spin className="my-2" />; |
||||
|
|
||||
|
return ( |
||||
|
<div className="flex flex-col gap-2 w-full"> |
||||
|
<Checkbox checked={value.requiresAll} onChange={(e) => triggerChange({ requiresAll: e.target.checked })}> |
||||
|
{$t("component.simple_state_checking.requireFeatures.requiresAll")} |
||||
|
</Checkbox> |
||||
|
|
||||
|
<TreeSelect |
||||
|
treeData={treeData} |
||||
|
value={treeValue} // Pass objects
|
||||
|
onChange={onTreeChange} // Receive objects
|
||||
|
allowClear |
||||
|
treeCheckable |
||||
|
treeCheckStrictly // Decouples parent/child selection logic
|
||||
|
showCheckedStrategy={TreeSelect.SHOW_ALL} // Show every selected node explicitly
|
||||
|
placeholder={$t("ui.placeholder.select")} |
||||
|
style={{ width: "100%" }} |
||||
|
treeDefaultExpandAll |
||||
|
/> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default FeatureStateCheck; |
||||
@ -0,0 +1,53 @@ |
|||||
|
import type React from "react"; |
||||
|
import { useMemo } from "react"; |
||||
|
import { Checkbox, Select } from "antd"; |
||||
|
import { useTranslation } from "react-i18next"; |
||||
|
import useAbpStore from "@/store/abpCoreStore"; |
||||
|
|
||||
|
interface ValueType { |
||||
|
globalFeatureNames: string[]; |
||||
|
requiresAll: boolean; |
||||
|
} |
||||
|
|
||||
|
interface GlobalFeatureStateCheckProps { |
||||
|
value?: ValueType; |
||||
|
onChange?: (value: ValueType) => void; |
||||
|
} |
||||
|
|
||||
|
const GlobalFeatureStateCheck: React.FC<GlobalFeatureStateCheckProps> = ({ |
||||
|
value = { globalFeatureNames: [], requiresAll: false }, |
||||
|
onChange, |
||||
|
}) => { |
||||
|
const { t: $t } = useTranslation(); |
||||
|
const application = useAbpStore((state) => state.application); |
||||
|
|
||||
|
const options = useMemo(() => { |
||||
|
if (!application?.globalFeatures?.enabledFeatures) return []; |
||||
|
return application.globalFeatures.enabledFeatures.map((f) => ({ |
||||
|
label: f, |
||||
|
value: f, |
||||
|
})); |
||||
|
}, [application]); |
||||
|
|
||||
|
const triggerChange = (changedValue: Partial<ValueType>) => { |
||||
|
onChange?.({ ...value, ...changedValue }); |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<div className="flex flex-col gap-4 w-full"> |
||||
|
<Checkbox checked={value.requiresAll} onChange={(e) => triggerChange({ requiresAll: e.target.checked })}> |
||||
|
{$t("component.simple_state_checking.requireFeatures.requiresAll")} |
||||
|
</Checkbox> |
||||
|
<Select |
||||
|
mode="tags" |
||||
|
style={{ width: "100%" }} |
||||
|
options={options} |
||||
|
value={value.globalFeatureNames} |
||||
|
onChange={(val) => triggerChange({ globalFeatureNames: val })} |
||||
|
allowClear |
||||
|
/> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default GlobalFeatureStateCheck; |
||||
@ -0,0 +1,139 @@ |
|||||
|
import type React from "react"; |
||||
|
import { useMemo } from "react"; |
||||
|
import { Checkbox, TreeSelect, Spin } from "antd"; |
||||
|
import { useTranslation } from "react-i18next"; |
||||
|
import { useQuery } from "@tanstack/react-query"; |
||||
|
import { getListApi as getPermissionsApi } from "@/api/management/permissions/definitions"; |
||||
|
import { getListApi as getGroupsApi } from "@/api/management/permissions/groups"; |
||||
|
import { listToTree } from "@/utils/tree"; |
||||
|
import { localizationSerializer } from "@/utils/abp/localization-serializer"; |
||||
|
import { useLocalizer } from "@/hooks/abp/use-localization"; |
||||
|
|
||||
|
interface ValueType { |
||||
|
permissions: string[]; |
||||
|
requiresAll: boolean; |
||||
|
} |
||||
|
|
||||
|
interface PermissionStateCheckProps { |
||||
|
value?: ValueType; |
||||
|
onChange?: (value: ValueType) => void; |
||||
|
} |
||||
|
|
||||
|
const PermissionStateCheck: React.FC<PermissionStateCheckProps> = ({ |
||||
|
value = { permissions: [], requiresAll: false }, |
||||
|
onChange, |
||||
|
}) => { |
||||
|
const { t: $t } = useTranslation(); |
||||
|
const { deserialize } = localizationSerializer(); |
||||
|
const { Lr } = useLocalizer(); |
||||
|
|
||||
|
// 1. Fetch Data
|
||||
|
const { data: permissionData, isLoading } = useQuery({ |
||||
|
queryKey: ["permissionStateCheckData"], |
||||
|
queryFn: async () => { |
||||
|
const [groupsRes, permissionsRes] = await Promise.all([getGroupsApi(), getPermissionsApi()]); |
||||
|
|
||||
|
// Prepare localized permissions list
|
||||
|
const permissions = permissionsRes.items.map((p) => { |
||||
|
const d = deserialize(p.displayName); |
||||
|
return { |
||||
|
...p, |
||||
|
displayName: Lr(d.resourceName, d.name), // Localized Name
|
||||
|
name: p.name, |
||||
|
parentName: p.parentName, |
||||
|
groupName: p.groupName, |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
// Prepare localized groups
|
||||
|
const groups = groupsRes.items.map((g) => { |
||||
|
const d = deserialize(g.displayName); |
||||
|
return { |
||||
|
...g, |
||||
|
displayName: Lr(d.resourceName, d.name), |
||||
|
name: g.name, |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
return { groups, permissions }; |
||||
|
}, |
||||
|
staleTime: Number.POSITIVE_INFINITY, |
||||
|
}); |
||||
|
|
||||
|
// 2. Construct Tree Data
|
||||
|
const treeData = useMemo(() => { |
||||
|
if (!permissionData) return []; |
||||
|
const { groups, permissions } = permissionData; |
||||
|
|
||||
|
return groups.map((group) => { |
||||
|
// Find permissions belonging to this group
|
||||
|
const groupPermissions = permissions.filter((p) => p.groupName === group.name); |
||||
|
|
||||
|
// Build hierarchy for permissions (parent/child)
|
||||
|
// Standard listToTree usage: (list, config)
|
||||
|
const children = listToTree(groupPermissions, { id: "name", pid: "parentName" }); |
||||
|
|
||||
|
// Return Group Node
|
||||
|
// Note: We use 'displayName' and 'name' which match the DTOs,
|
||||
|
// and map them using the 'fieldNames' prop on TreeSelect below.
|
||||
|
return { |
||||
|
displayName: group.displayName, |
||||
|
name: group.name, |
||||
|
// UI props to make group unselectable/uncheckable
|
||||
|
checkable: false, |
||||
|
selectable: false, |
||||
|
disableCheckbox: true, |
||||
|
children: children, |
||||
|
}; |
||||
|
}); |
||||
|
}, [permissionData]); |
||||
|
|
||||
|
// 3. Map selected string[] to { label, value } objects for TreeSelect (Strict Mode)
|
||||
|
const treeValue = useMemo(() => { |
||||
|
if (!permissionData || !value.permissions) return []; |
||||
|
return value.permissions.map((name) => { |
||||
|
const permission = permissionData.permissions.find((p) => p.name === name); |
||||
|
return { |
||||
|
label: permission?.displayName || name, |
||||
|
value: name, |
||||
|
}; |
||||
|
}); |
||||
|
}, [permissionData, value.permissions]); |
||||
|
|
||||
|
// 4. Handle Change
|
||||
|
const triggerChange = (changedValue: Partial<ValueType>) => { |
||||
|
onChange?.({ ...value, ...changedValue }); |
||||
|
}; |
||||
|
|
||||
|
const onTreeChange = (labeledValues: { label: React.ReactNode; value: string }[]) => { |
||||
|
const names = labeledValues.map((item) => item.value); |
||||
|
triggerChange({ permissions: names }); |
||||
|
}; |
||||
|
|
||||
|
if (isLoading) return <Spin className="my-2" />; |
||||
|
|
||||
|
return ( |
||||
|
<div className="flex flex-col gap-2 w-full"> |
||||
|
<Checkbox checked={value.requiresAll} onChange={(e) => triggerChange({ requiresAll: e.target.checked })}> |
||||
|
{$t("component.simple_state_checking.requirePermissions.requiresAll")} |
||||
|
</Checkbox> |
||||
|
|
||||
|
<TreeSelect |
||||
|
treeData={treeData} |
||||
|
value={treeValue} |
||||
|
onChange={onTreeChange} |
||||
|
// Map DTO fields to AntD TreeSelect expected fields
|
||||
|
fieldNames={{ label: "displayName", value: "name", children: "children" }} |
||||
|
allowClear |
||||
|
treeCheckable |
||||
|
treeCheckStrictly |
||||
|
showCheckedStrategy={TreeSelect.SHOW_ALL} |
||||
|
placeholder={$t("ui.placeholder.select")} |
||||
|
style={{ width: "100%" }} |
||||
|
treeDefaultExpandAll |
||||
|
/> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default PermissionStateCheck; |
||||
@ -0,0 +1,5 @@ |
|||||
|
import type { IHasSimpleStateCheckers, ISimpleStateChecker } from "#/abp-core"; |
||||
|
|
||||
|
export interface SimplaCheckStateBase extends IHasSimpleStateCheckers<SimplaCheckStateBase> { |
||||
|
stateCheckers: ISimpleStateChecker<SimplaCheckStateBase>[]; |
||||
|
} |
||||
@ -0,0 +1,208 @@ |
|||||
|
import type React from "react"; |
||||
|
import { useEffect, useState } from "react"; |
||||
|
import { Modal, Form, Select } from "antd"; |
||||
|
import { useTranslation } from "react-i18next"; |
||||
|
import GlobalFeatureStateCheck from "@/components/abp/features/state-check/global-feature-state-check"; |
||||
|
import PermissionStateCheck from "@/components/abp/permissions/state-check/permission-state-check"; |
||||
|
import FeatureStateCheck from "@/components/abp/features/state-check/feature-state-check"; |
||||
|
|
||||
|
interface SimpleStateCheckingModalProps { |
||||
|
visible: boolean; |
||||
|
onClose: () => void; |
||||
|
onConfirm: (data: any) => void; |
||||
|
record?: any; // The existing checker to edit
|
||||
|
options: { label: string; value: string; disabled: boolean }[]; |
||||
|
} |
||||
|
|
||||
|
const SimpleStateCheckingModal: React.FC<SimpleStateCheckingModalProps> = ({ |
||||
|
visible, |
||||
|
onClose, |
||||
|
onConfirm, |
||||
|
record, |
||||
|
options, |
||||
|
}) => { |
||||
|
const { t: $t } = useTranslation(); |
||||
|
const [form] = Form.useForm(); |
||||
|
const [selectedType, setSelectedType] = useState<string | undefined>(); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (visible) { |
||||
|
if (record) { |
||||
|
// Map record back to form values
|
||||
|
const name = record.name; |
||||
|
setSelectedType(name); |
||||
|
|
||||
|
let value = {}; |
||||
|
switch (name) { |
||||
|
case "F": |
||||
|
value = { featureNames: record.featureNames, requiresAll: record.requiresAll }; |
||||
|
break; |
||||
|
case "G": |
||||
|
value = { globalFeatureNames: record.globalFeatureNames, requiresAll: record.requiresAll }; |
||||
|
break; |
||||
|
case "P": |
||||
|
value = { permissions: record.model?.permissions || record.permissions, requiresAll: record.requiresAll }; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
form.setFieldsValue({ |
||||
|
name: name, |
||||
|
value: value, |
||||
|
}); |
||||
|
} else { |
||||
|
form.resetFields(); |
||||
|
setSelectedType(undefined); |
||||
|
} |
||||
|
} |
||||
|
}, [visible, record, form]); |
||||
|
|
||||
|
const handleTypeChange = (val: string) => { |
||||
|
setSelectedType(val); |
||||
|
// Reset the value field when type changes
|
||||
|
let initialValue = {}; |
||||
|
if (val === "F") initialValue = { featureNames: [], requiresAll: false }; |
||||
|
if (val === "G") initialValue = { globalFeatureNames: [], requiresAll: false }; |
||||
|
if (val === "P") initialValue = { permissions: [], requiresAll: false }; |
||||
|
|
||||
|
form.setFieldValue("value", initialValue); |
||||
|
}; |
||||
|
|
||||
|
const handleSubmit = async () => { |
||||
|
try { |
||||
|
const values = await form.validateFields(); |
||||
|
|
||||
|
// Transform form values back to ABP simple state checker structure
|
||||
|
const result: any = { |
||||
|
T: values.name, // T is usually the discriminator in some serializations, checking logic below
|
||||
|
}; |
||||
|
|
||||
|
// Mapping based on the Vue onSubmit logic:
|
||||
|
// A = RequiresAll (boolean)
|
||||
|
// T = Discriminator/Name
|
||||
|
// N = Names array
|
||||
|
|
||||
|
const val = values.value || {}; |
||||
|
const checkerObj: any = { |
||||
|
name: values.name, |
||||
|
// 'A' property seems to map to requiresAll for the checker itself
|
||||
|
requiresAll: val.requiresAll, |
||||
|
}; |
||||
|
|
||||
|
// Specifically for serialization logic later
|
||||
|
// The Vue code returns: { A: boolean, T: string, N: string[] }
|
||||
|
const output: any = { |
||||
|
A: val.requiresAll, |
||||
|
T: values.name, |
||||
|
}; |
||||
|
|
||||
|
switch (values.name) { |
||||
|
case "F": |
||||
|
output.N = val.featureNames; |
||||
|
break; |
||||
|
case "G": |
||||
|
output.N = val.globalFeatureNames; |
||||
|
break; |
||||
|
case "P": |
||||
|
output.N = val.permissions; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
onConfirm(output); |
||||
|
onClose(); |
||||
|
} catch (e) { |
||||
|
console.error(e); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<Modal |
||||
|
title={$t("component.simple_state_checking.title")} |
||||
|
open={visible} |
||||
|
onCancel={onClose} |
||||
|
onOk={handleSubmit} |
||||
|
destroyOnClose |
||||
|
> |
||||
|
<Form form={form} layout="vertical"> |
||||
|
<Form.Item name="name" label={$t("component.simple_state_checking.form.name")} rules={[{ required: true }]}> |
||||
|
<Select |
||||
|
options={options} |
||||
|
onChange={handleTypeChange} |
||||
|
disabled={!!record} // Cannot change type when editing
|
||||
|
/> |
||||
|
</Form.Item> |
||||
|
|
||||
|
<Form.Item noStyle shouldUpdate={(prev, curr) => prev.name !== curr.name}> |
||||
|
{({ getFieldValue }) => { |
||||
|
const type = getFieldValue("name"); |
||||
|
|
||||
|
if (type === "A") { |
||||
|
// Authenticated check requires no extra config
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
if (type === "F") { |
||||
|
return ( |
||||
|
<Form.Item |
||||
|
name="value" |
||||
|
label={$t("component.simple_state_checking.requireFeatures.featureNames")} |
||||
|
rules={[ |
||||
|
{ |
||||
|
validator: (_, val) => |
||||
|
val?.featureNames?.length > 0 |
||||
|
? Promise.resolve() |
||||
|
: Promise.reject($t("component.simple_state_checking.requireFeatures.featureNames")), |
||||
|
}, |
||||
|
]} |
||||
|
> |
||||
|
<FeatureStateCheck /> |
||||
|
</Form.Item> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
if (type === "G") { |
||||
|
return ( |
||||
|
<Form.Item |
||||
|
name="value" |
||||
|
label={$t("component.simple_state_checking.requireFeatures.featureNames")} |
||||
|
rules={[ |
||||
|
{ |
||||
|
validator: (_, val) => |
||||
|
val?.globalFeatureNames?.length > 0 |
||||
|
? Promise.resolve() |
||||
|
: Promise.reject($t("component.simple_state_checking.requireFeatures.featureNames")), |
||||
|
}, |
||||
|
]} |
||||
|
> |
||||
|
<GlobalFeatureStateCheck /> |
||||
|
</Form.Item> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
if (type === "P") { |
||||
|
return ( |
||||
|
<Form.Item |
||||
|
name="value" |
||||
|
label={$t("component.simple_state_checking.requirePermissions.permissions")} |
||||
|
rules={[ |
||||
|
{ |
||||
|
validator: (_, val) => |
||||
|
val?.permissions?.length > 0 |
||||
|
? Promise.resolve() |
||||
|
: Promise.reject($t("component.simple_state_checking.requirePermissions.permissions")), |
||||
|
}, |
||||
|
]} |
||||
|
> |
||||
|
<PermissionStateCheck /> |
||||
|
</Form.Item> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
}} |
||||
|
</Form.Item> |
||||
|
</Form> |
||||
|
</Modal> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default SimpleStateCheckingModal; |
||||
@ -0,0 +1,214 @@ |
|||||
|
import type React from "react"; |
||||
|
import { useState, useMemo, useEffect } from "react"; |
||||
|
import { Button, Card, Table, Tag, Space, Row, Col, Popconfirm } from "antd"; |
||||
|
import { EditOutlined, DeleteOutlined, PlusOutlined } from "@ant-design/icons"; |
||||
|
import { useTranslation } from "react-i18next"; |
||||
|
import { useSimpleStateCheck } from "@/hooks/abp/fake-hooks/use-simple-state-check"; // TODO re hooks in this chain
|
||||
|
import type { SimplaCheckStateBase } from "./interface"; |
||||
|
import SimpleStateCheckingModal from "./simple-state-checking-modal"; |
||||
|
import type { ColumnsType } from "antd/es/table"; |
||||
|
|
||||
|
interface SimpleStateCheckingProps { |
||||
|
state: SimplaCheckStateBase; |
||||
|
value?: string; // Serialized string
|
||||
|
onChange?: (value: string | undefined) => void; |
||||
|
disabled?: boolean; |
||||
|
allowEdit?: boolean; |
||||
|
allowDelete?: boolean; |
||||
|
} |
||||
|
|
||||
|
const SimpleStateChecking: React.FC<SimpleStateCheckingProps> = ({ |
||||
|
state, |
||||
|
value, |
||||
|
onChange, |
||||
|
disabled = false, |
||||
|
allowEdit = false, |
||||
|
allowDelete = false, |
||||
|
}) => { |
||||
|
const { t: $t } = useTranslation(); |
||||
|
const { deserializeArray, serializeArray, deserialize } = useSimpleStateCheck(); |
||||
|
|
||||
|
// Local state to manage the list of checkers
|
||||
|
const [checkers, setCheckers] = useState<any[]>([]); |
||||
|
const [modalVisible, setModalVisible] = useState(false); |
||||
|
const [editingRecord, setEditingRecord] = useState<any>(null); |
||||
|
|
||||
|
// Sync prop value with local state
|
||||
|
useEffect(() => { |
||||
|
if (!value || value.length === 0) { |
||||
|
setCheckers([]); |
||||
|
} else { |
||||
|
const deserialized = deserializeArray(value, state); |
||||
|
setCheckers(deserialized); |
||||
|
} |
||||
|
}, [value, state]); |
||||
|
|
||||
|
// Map for friendly names
|
||||
|
const simpleCheckerMap: Record<string, string> = { |
||||
|
A: $t("component.simple_state_checking.requireAuthenticated.title"), |
||||
|
F: $t("component.simple_state_checking.requireFeatures.title"), |
||||
|
G: $t("component.simple_state_checking.requireGlobalFeatures.title"), |
||||
|
P: $t("component.simple_state_checking.requirePermissions.title"), |
||||
|
}; |
||||
|
|
||||
|
const handleAddNew = () => { |
||||
|
setEditingRecord(null); |
||||
|
setModalVisible(true); |
||||
|
}; |
||||
|
|
||||
|
const handleEdit = (record: any) => { |
||||
|
setEditingRecord(record); |
||||
|
setModalVisible(true); |
||||
|
}; |
||||
|
|
||||
|
const handleUpdateCheckers = (newCheckers: any[]) => { |
||||
|
setCheckers(newCheckers); |
||||
|
const serialized = serializeArray(newCheckers); |
||||
|
onChange?.(serialized); //TODO
|
||||
|
}; |
||||
|
|
||||
|
const handleModalConfirm = (data: any) => { |
||||
|
// 'data' comes in format { T: 'type', A: bool, N: [] } from the modal
|
||||
|
|
||||
|
// Deserialize the simple object from modal back into the complex class instance used by the list
|
||||
|
const deserializedItem = deserialize(data, state); |
||||
|
if (!deserializedItem) return; |
||||
|
|
||||
|
const updatedList = [...checkers]; |
||||
|
const existingIndex = updatedList.findIndex((x) => x.name === data.T); |
||||
|
|
||||
|
if (existingIndex > -1) { |
||||
|
updatedList[existingIndex] = deserializedItem; |
||||
|
} else { |
||||
|
updatedList.push(deserializedItem); |
||||
|
} |
||||
|
|
||||
|
handleUpdateCheckers(updatedList); |
||||
|
}; |
||||
|
|
||||
|
const handleDelete = (record: any) => { |
||||
|
const updatedList = checkers.filter((x) => x.name !== record.name); |
||||
|
handleUpdateCheckers(updatedList); |
||||
|
}; |
||||
|
|
||||
|
const handleClean = () => { |
||||
|
handleUpdateCheckers([]); |
||||
|
}; |
||||
|
|
||||
|
const options = useMemo(() => { |
||||
|
return Object.keys(simpleCheckerMap).map((key) => ({ |
||||
|
label: simpleCheckerMap[key], |
||||
|
value: key, |
||||
|
// Disable if this type already exists in the list (assuming 1 per type based on Vue logic findIndex)
|
||||
|
disabled: checkers.some((x) => x.name === key), |
||||
|
})); |
||||
|
}, [checkers, $t]); |
||||
|
|
||||
|
const columns: ColumnsType<any> = [ |
||||
|
{ |
||||
|
title: $t("component.simple_state_checking.table.name"), |
||||
|
dataIndex: "name", |
||||
|
key: "name", |
||||
|
width: 150, |
||||
|
render: (name) => simpleCheckerMap[name] || name, |
||||
|
}, |
||||
|
{ |
||||
|
title: $t("component.simple_state_checking.table.properties"), |
||||
|
key: "properties", |
||||
|
render: (_, record) => { |
||||
|
if (record.name === "F") { |
||||
|
return ( |
||||
|
<Space wrap> |
||||
|
{record.featureNames?.map((f: string) => ( |
||||
|
<Tag key={f}>{f}</Tag> |
||||
|
))} |
||||
|
</Space> |
||||
|
); |
||||
|
} |
||||
|
if (record.name === "G") { |
||||
|
return ( |
||||
|
<Space wrap> |
||||
|
{record.globalFeatureNames?.map((f: string) => ( |
||||
|
<Tag key={f}>{f}</Tag> |
||||
|
))} |
||||
|
</Space> |
||||
|
); |
||||
|
} |
||||
|
if (record.name === "P") { |
||||
|
const permissions = record.model?.permissions || record.permissions || []; |
||||
|
return ( |
||||
|
<Space wrap> |
||||
|
{permissions.map((p: string) => ( |
||||
|
<Tag key={p}>{p}</Tag> |
||||
|
))} |
||||
|
</Space> |
||||
|
); |
||||
|
} |
||||
|
if (record.name === "A") { |
||||
|
return $t("component.simple_state_checking.requireAuthenticated.title"); |
||||
|
} |
||||
|
return null; |
||||
|
}, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
if (!disabled) { |
||||
|
columns.push({ |
||||
|
title: $t("component.simple_state_checking.table.actions"), |
||||
|
key: "action", |
||||
|
width: 180, |
||||
|
render: (_, record) => ( |
||||
|
<Space> |
||||
|
{allowEdit && ( |
||||
|
<Button type="link" icon={<EditOutlined />} onClick={() => handleEdit(record)}> |
||||
|
{$t("component.simple_state_checking.actions.update")} |
||||
|
</Button> |
||||
|
)} |
||||
|
{allowDelete && ( |
||||
|
<Popconfirm title="Are you sure?" onConfirm={() => handleDelete(record)}> |
||||
|
<Button type="link" danger icon={<DeleteOutlined />}> |
||||
|
{$t("component.simple_state_checking.actions.delete")} |
||||
|
</Button> |
||||
|
</Popconfirm> |
||||
|
)} |
||||
|
</Space> |
||||
|
), |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<div className="w-full"> |
||||
|
<Card |
||||
|
title={ |
||||
|
<Row justify="space-between" align="middle"> |
||||
|
<Col>{$t("component.simple_state_checking.title")}</Col> |
||||
|
<Col> |
||||
|
{!disabled && ( |
||||
|
<Space> |
||||
|
<Button type="primary" onClick={handleAddNew} icon={<PlusOutlined />}> |
||||
|
{$t("component.simple_state_checking.actions.create")} |
||||
|
</Button> |
||||
|
<Button danger onClick={handleClean}> |
||||
|
{$t("component.simple_state_checking.actions.clean")} |
||||
|
</Button> |
||||
|
</Space> |
||||
|
)} |
||||
|
</Col> |
||||
|
</Row> |
||||
|
} |
||||
|
> |
||||
|
<Table rowKey="name" columns={columns} dataSource={checkers} pagination={false} size="small" /> |
||||
|
</Card> |
||||
|
|
||||
|
<SimpleStateCheckingModal |
||||
|
visible={modalVisible} |
||||
|
onClose={() => setModalVisible(false)} |
||||
|
onConfirm={handleModalConfirm} |
||||
|
record={editingRecord} |
||||
|
options={options} |
||||
|
/> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default SimpleStateChecking; |
||||
@ -0,0 +1,4 @@ |
|||||
|
export * from "./interface"; |
||||
|
export { default as StringValueTypeInput } from "./string-value-type-input"; |
||||
|
export * from "./validator"; |
||||
|
export * from "./value-type.ts"; |
||||
@ -0,0 +1,3 @@ |
|||||
|
export interface StringValueTypeInstance { |
||||
|
validate(value: any): Promise<any>; |
||||
|
} |
||||
@ -0,0 +1,540 @@ |
|||||
|
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react"; |
||||
|
import { Button, Card, Checkbox, Col, Form, Input, InputNumber, Modal, Row, Select, Table, Space } from "antd"; |
||||
|
import { DeleteOutlined, EditOutlined, PlusOutlined } from "@ant-design/icons"; |
||||
|
import { useTranslation } from "react-i18next"; |
||||
|
import { useLocalizer } from "@/hooks/abp/use-localization"; |
||||
|
import { |
||||
|
valueTypeSerializer, |
||||
|
FreeTextStringValueType, |
||||
|
SelectionStringValueType, |
||||
|
ToggleStringValueType, |
||||
|
type StringValueType, |
||||
|
type SelectionStringValueItem, |
||||
|
} from "./value-type"; |
||||
|
import { |
||||
|
AlwaysValidValueValidator, |
||||
|
BooleanValueValidator, |
||||
|
NumericValueValidator, |
||||
|
StringValueValidator, |
||||
|
} from "./validator"; |
||||
|
import LocalizableInput from "@/components/abp/localizable-input/localizable-input"; |
||||
|
import { localizationSerializer } from "@/utils/abp/localization-serializer"; |
||||
|
import type { LocalizableStringInfo } from "#/abp-core"; |
||||
|
|
||||
|
export interface ValueTypeInputProps { |
||||
|
allowDelete?: boolean; |
||||
|
allowEdit?: boolean; |
||||
|
disabled?: boolean; |
||||
|
value?: string; |
||||
|
onChange?: (value: string | undefined) => void; |
||||
|
onValueTypeChange?: (type: string) => void; |
||||
|
onValidatorChange?: (validator: string) => void; |
||||
|
onSelectionChange?: (items: SelectionStringValueItem[]) => void; |
||||
|
} |
||||
|
|
||||
|
export interface ValueTypeInputHandle { |
||||
|
validate: (value: any) => Promise<any>; |
||||
|
} |
||||
|
|
||||
|
const ValueTypeInput = forwardRef<ValueTypeInputHandle, ValueTypeInputProps>( |
||||
|
( |
||||
|
{ |
||||
|
allowDelete = false, |
||||
|
allowEdit = false, |
||||
|
disabled = false, |
||||
|
value = "{}", |
||||
|
onChange, |
||||
|
onValueTypeChange, |
||||
|
onValidatorChange, |
||||
|
onSelectionChange, |
||||
|
}, |
||||
|
ref, |
||||
|
) => { |
||||
|
const { t: $t } = useTranslation(); |
||||
|
const { Lr } = useLocalizer(); |
||||
|
|
||||
|
const { |
||||
|
deserialize: deserializeLocalizer, |
||||
|
serialize: serializeLocalizer, |
||||
|
validate: validateLocalizer, |
||||
|
} = localizationSerializer(); |
||||
|
|
||||
|
// Internal State
|
||||
|
const [valueType, setValueType] = useState<StringValueType>(new FreeTextStringValueType()); |
||||
|
|
||||
|
// Modal State for Selection Type
|
||||
|
const [modalVisible, setModalVisible] = useState(false); |
||||
|
const [modalForm] = Form.useForm(); |
||||
|
const [editingItem, setEditingItem] = useState<{ |
||||
|
isEdit: boolean; |
||||
|
displayText?: string; |
||||
|
}>({ isEdit: false }); |
||||
|
|
||||
|
// Initialize/Sync State from Props
|
||||
|
useEffect(() => { |
||||
|
if (!value || value.trim() === "" || value === "{}") { |
||||
|
setValueType(new FreeTextStringValueType()); |
||||
|
} else { |
||||
|
try { |
||||
|
const deserialized = valueTypeSerializer.deserialize(value); |
||||
|
setValueType(deserialized); |
||||
|
} catch (e) { |
||||
|
console.warn("Failed to deserialize valueType", e); |
||||
|
} |
||||
|
} |
||||
|
}, [value]); |
||||
|
|
||||
|
// Notify Parent of Changes
|
||||
|
const triggerChange = (newValueType: StringValueType) => { |
||||
|
// We need to create a new object reference or clone to ensure React detects state changes
|
||||
|
// Serialization/Deserialization is a safe way to deep clone and ensure logic consistency
|
||||
|
const serialized = valueTypeSerializer.serialize(newValueType); |
||||
|
|
||||
|
// Update internal state only if not controlled (optimization),
|
||||
|
// but here we rely on parent prop update usually.
|
||||
|
// However, to make UI responsive immediately for nested properties:
|
||||
|
setValueType(valueTypeSerializer.deserialize(serialized)); |
||||
|
|
||||
|
if (onChange) onChange(serialized); |
||||
|
if (onValueTypeChange) onValueTypeChange(newValueType.name); |
||||
|
if (onValidatorChange) onValidatorChange(newValueType.validator.name); |
||||
|
|
||||
|
if (newValueType instanceof SelectionStringValueType && onSelectionChange) { |
||||
|
onSelectionChange(newValueType.itemSource.items); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// Expose Validate Method
|
||||
|
useImperativeHandle(ref, () => ({ |
||||
|
validate: async (val: any) => { |
||||
|
if (valueType instanceof SelectionStringValueType) { |
||||
|
const items = valueType.itemSource.items; |
||||
|
if (items.length === 0) { |
||||
|
return Promise.reject($t("component.value_type_nput.type.SELECTION.itemsNotBeEmpty")); |
||||
|
} |
||||
|
if (val && !items.some((item) => item.value === val)) { |
||||
|
return Promise.reject($t("component.value_type_nput.type.SELECTION.itemsNotFound")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!valueType.validator.isValid(val)) { |
||||
|
const validatorNameKey = `component.value_type_nput.validator.${valueType.validator.name}.name`; |
||||
|
return Promise.reject( |
||||
|
$t("component.value_type_nput.validator.isInvalidValue", { |
||||
|
0: $t(validatorNameKey), |
||||
|
}), |
||||
|
); |
||||
|
} |
||||
|
return Promise.resolve(val); |
||||
|
}, |
||||
|
})); |
||||
|
|
||||
|
// Handlers
|
||||
|
const handleValueTypeChange = (type: string) => { |
||||
|
let newValueType: StringValueType; |
||||
|
switch (type) { |
||||
|
case "SELECTION": |
||||
|
case "SelectionStringValueType": |
||||
|
newValueType = new SelectionStringValueType(); |
||||
|
break; |
||||
|
case "TOGGLE": |
||||
|
case "ToggleStringValueType": |
||||
|
newValueType = new ToggleStringValueType(); |
||||
|
break; |
||||
|
default: |
||||
|
newValueType = new FreeTextStringValueType(); |
||||
|
break; |
||||
|
} |
||||
|
triggerChange(newValueType); |
||||
|
}; |
||||
|
|
||||
|
const handleValidatorChange = (validatorName: string) => { |
||||
|
const newValueType = valueTypeSerializer.deserialize(valueTypeSerializer.serialize(valueType)); |
||||
|
|
||||
|
switch (validatorName) { |
||||
|
case "BOOLEAN": |
||||
|
newValueType.validator = new BooleanValueValidator(); |
||||
|
break; |
||||
|
case "NULL": |
||||
|
newValueType.validator = new AlwaysValidValueValidator(); |
||||
|
break; |
||||
|
case "NUMERIC": |
||||
|
newValueType.validator = new NumericValueValidator(); |
||||
|
break; |
||||
|
default: |
||||
|
newValueType.validator = new StringValueValidator(); |
||||
|
break; |
||||
|
} |
||||
|
triggerChange(newValueType); |
||||
|
}; |
||||
|
|
||||
|
// Generic Property Update Handler (Deep Update)
|
||||
|
const updateValidatorProperty = (updater: (validator: any) => void) => { |
||||
|
const serialized = valueTypeSerializer.serialize(valueType); |
||||
|
const cloned = valueTypeSerializer.deserialize(serialized); |
||||
|
updater(cloned.validator); |
||||
|
triggerChange(cloned); |
||||
|
}; |
||||
|
|
||||
|
// Selection Logic
|
||||
|
const handleAddSelection = () => { |
||||
|
setEditingItem({ isEdit: false }); |
||||
|
modalForm.resetFields(); |
||||
|
setModalVisible(true); |
||||
|
}; |
||||
|
|
||||
|
const handleClearSelection = () => { |
||||
|
if (valueType instanceof SelectionStringValueType) { |
||||
|
const cloned = valueTypeSerializer.deserialize( |
||||
|
valueTypeSerializer.serialize(valueType), |
||||
|
) as SelectionStringValueType; |
||||
|
cloned.itemSource.items = []; |
||||
|
triggerChange(cloned); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const handleEditSelection = (record: SelectionStringValueItem) => { |
||||
|
setEditingItem({ isEdit: true, displayText: serializeLocalizer(record.displayText) }); |
||||
|
modalForm.setFieldsValue({ |
||||
|
displayText: serializeLocalizer(record.displayText), |
||||
|
value: record.value, |
||||
|
}); |
||||
|
setModalVisible(true); |
||||
|
}; |
||||
|
|
||||
|
const handleDeleteSelection = (record: SelectionStringValueItem) => { |
||||
|
if (valueType instanceof SelectionStringValueType) { |
||||
|
const displayText = serializeLocalizer(record.displayText); |
||||
|
const cloned = valueType as SelectionStringValueType; |
||||
|
cloned.itemSource.items = cloned.itemSource.items.filter( |
||||
|
(x) => serializeLocalizer(x.displayText) !== displayText, |
||||
|
); |
||||
|
|
||||
|
triggerChange(cloned); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const handleModalOk = async () => { |
||||
|
try { |
||||
|
const values = await modalForm.validateFields(); |
||||
|
if (valueType instanceof SelectionStringValueType) { |
||||
|
const cloned = valueTypeSerializer.deserialize( |
||||
|
valueTypeSerializer.serialize(valueType), |
||||
|
) as SelectionStringValueType; |
||||
|
|
||||
|
if (editingItem.isEdit) { |
||||
|
const index = cloned.itemSource.items.findIndex( |
||||
|
(x) => serializeLocalizer(x.displayText) === editingItem.displayText, |
||||
|
); |
||||
|
if (index > -1) { |
||||
|
cloned.itemSource.items[index] = { |
||||
|
displayText: values.displayText, |
||||
|
value: values.value, |
||||
|
}; |
||||
|
} |
||||
|
} else { |
||||
|
cloned.itemSource.items.push({ |
||||
|
displayText: deserializeLocalizer(values.displayText), |
||||
|
value: values.value, |
||||
|
}); |
||||
|
} |
||||
|
triggerChange(cloned); |
||||
|
setModalVisible(false); |
||||
|
modalForm.resetFields(); |
||||
|
} |
||||
|
} catch (e) { |
||||
|
// Validation failed
|
||||
|
console.warn("Validation failed in handleModalOk", e); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// Columns for Selection Table
|
||||
|
const selectionColumns = useMemo(() => { |
||||
|
const cols: any[] = [ |
||||
|
{ |
||||
|
title: $t("component.value_type_nput.type.SELECTION.displayText"), |
||||
|
dataIndex: "displayText", |
||||
|
key: "displayText", |
||||
|
width: 180, |
||||
|
render: (text: LocalizableStringInfo) => Lr(text.resourceName, text.name), |
||||
|
}, |
||||
|
{ |
||||
|
title: $t("component.value_type_nput.type.SELECTION.value"), |
||||
|
dataIndex: "value", |
||||
|
key: "value", |
||||
|
width: 200, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
if (!disabled) { |
||||
|
cols.push({ |
||||
|
title: $t("component.value_type_nput.type.SELECTION.actions.title"), |
||||
|
key: "action", |
||||
|
width: 220, |
||||
|
render: (_: any, record: SelectionStringValueItem) => ( |
||||
|
<Space> |
||||
|
{allowEdit && ( |
||||
|
<Button type="link" icon={<EditOutlined />} onClick={() => handleEditSelection(record)}> |
||||
|
{$t("component.value_type_nput.type.SELECTION.actions.update")} |
||||
|
</Button> |
||||
|
)} |
||||
|
{allowDelete && ( |
||||
|
<Button type="link" danger icon={<DeleteOutlined />} onClick={() => handleDeleteSelection(record)}> |
||||
|
{$t("component.value_type_nput.type.SELECTION.actions.delete")} |
||||
|
</Button> |
||||
|
)} |
||||
|
</Space> |
||||
|
), |
||||
|
}); |
||||
|
} |
||||
|
return cols; |
||||
|
}, [disabled, allowEdit, allowDelete, $t, Lr]); |
||||
|
|
||||
|
return ( |
||||
|
<div className="w-full"> |
||||
|
<Card |
||||
|
title={ |
||||
|
<div className="w-full"> |
||||
|
<Row gutter={16}> |
||||
|
<Col span={11}>{$t("component.value_type_nput.type.name")}</Col> |
||||
|
<Col span={11} offset={2}> |
||||
|
{$t("component.value_type_nput.validator.name")} |
||||
|
</Col> |
||||
|
</Row> |
||||
|
<Row gutter={16} className="mt-2"> |
||||
|
<Col span={11}> |
||||
|
<Select |
||||
|
className="w-full" |
||||
|
disabled={disabled} |
||||
|
value={valueType.name} |
||||
|
onChange={handleValueTypeChange} |
||||
|
options={[ |
||||
|
{ |
||||
|
label: $t("component.value_type_nput.type.FREE_TEXT.name"), |
||||
|
value: "FreeTextStringValueType", |
||||
|
}, |
||||
|
{ |
||||
|
label: $t("component.value_type_nput.type.TOGGLE.name"), |
||||
|
value: "ToggleStringValueType", |
||||
|
}, |
||||
|
{ |
||||
|
label: $t("component.value_type_nput.type.SELECTION.name"), |
||||
|
value: "SelectionStringValueType", |
||||
|
}, |
||||
|
]} |
||||
|
/> |
||||
|
</Col> |
||||
|
<Col span={11} offset={2}> |
||||
|
<Select |
||||
|
className="w-full" |
||||
|
disabled={disabled} |
||||
|
value={valueType.validator.name} |
||||
|
onChange={handleValidatorChange} |
||||
|
> |
||||
|
<Select.Option value="NULL">{$t("component.value_type_nput.validator.NULL.name")}</Select.Option> |
||||
|
<Select.Option value="BOOLEAN" disabled={valueType.name !== "ToggleStringValueType"}> |
||||
|
{$t("component.value_type_nput.validator.BOOLEAN.name")} |
||||
|
</Select.Option> |
||||
|
<Select.Option value="NUMERIC" disabled={valueType.name !== "FreeTextStringValueType"}> |
||||
|
{$t("component.value_type_nput.validator.NUMERIC.name")} |
||||
|
</Select.Option> |
||||
|
<Select.Option value="STRING" disabled={valueType.name !== "FreeTextStringValueType"}> |
||||
|
{$t("component.value_type_nput.validator.STRING.name")} |
||||
|
</Select.Option> |
||||
|
</Select> |
||||
|
</Col> |
||||
|
</Row> |
||||
|
</div> |
||||
|
} |
||||
|
> |
||||
|
{/* FreeText - NUMERIC */} |
||||
|
{valueType.name === "FreeTextStringValueType" && valueType.validator.name === "NUMERIC" && ( |
||||
|
<div> |
||||
|
<Row gutter={16}> |
||||
|
<Col span={11}>{$t("component.value_type_nput.validator.NUMERIC.minValue")}</Col> |
||||
|
<Col span={11} offset={2}> |
||||
|
{$t("component.value_type_nput.validator.NUMERIC.maxValue")} |
||||
|
</Col> |
||||
|
</Row> |
||||
|
<Row gutter={16} className="mt-1"> |
||||
|
<Col span={11}> |
||||
|
<InputNumber |
||||
|
className="w-full" |
||||
|
disabled={disabled} |
||||
|
value={(valueType.validator as NumericValueValidator).minValue} |
||||
|
onChange={(val) => updateValidatorProperty((v) => (v.minValue = val ? Number(val) : undefined))} |
||||
|
/> |
||||
|
</Col> |
||||
|
<Col span={11} offset={2}> |
||||
|
<InputNumber |
||||
|
className="w-full" |
||||
|
disabled={disabled} |
||||
|
value={(valueType.validator as NumericValueValidator).maxValue} |
||||
|
onChange={(val) => updateValidatorProperty((v) => (v.maxValue = val ? Number(val) : undefined))} |
||||
|
/> |
||||
|
</Col> |
||||
|
</Row> |
||||
|
</div> |
||||
|
)} |
||||
|
|
||||
|
{/* FreeText - STRING */} |
||||
|
{valueType.name === "FreeTextStringValueType" && valueType.validator.name === "STRING" && ( |
||||
|
<div className="flex flex-col gap-4 mt-2"> |
||||
|
<Checkbox |
||||
|
disabled={disabled} |
||||
|
checked={(valueType.validator as StringValueValidator).allowNull} |
||||
|
onChange={(e) => updateValidatorProperty((v) => (v.allowNull = e.target.checked))} |
||||
|
> |
||||
|
{$t("component.value_type_nput.validator.STRING.allowNull")} |
||||
|
</Checkbox> |
||||
|
|
||||
|
<div> |
||||
|
<div className="mb-1">{$t("component.value_type_nput.validator.STRING.regularExpression")}</div> |
||||
|
<Input |
||||
|
className="w-full" |
||||
|
disabled={disabled} |
||||
|
value={(valueType.validator as StringValueValidator).regularExpression} |
||||
|
onChange={(e) => updateValidatorProperty((v) => (v.regularExpression = e.target.value))} |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div> |
||||
|
<Row gutter={16}> |
||||
|
<Col span={11}>{$t("component.value_type_nput.validator.STRING.minLength")}</Col> |
||||
|
<Col span={11} offset={2}> |
||||
|
{$t("component.value_type_nput.validator.STRING.maxLength")} |
||||
|
</Col> |
||||
|
</Row> |
||||
|
<Row gutter={16} className="mt-1"> |
||||
|
<Col span={11}> |
||||
|
<InputNumber |
||||
|
className="w-full" |
||||
|
disabled={disabled} |
||||
|
value={(valueType.validator as StringValueValidator).minLength} |
||||
|
onChange={(val) => updateValidatorProperty((v) => (v.minLength = val ? Number(val) : undefined))} |
||||
|
/> |
||||
|
</Col> |
||||
|
<Col span={11} offset={2}> |
||||
|
<InputNumber |
||||
|
className="w-full" |
||||
|
disabled={disabled} |
||||
|
value={(valueType.validator as StringValueValidator).maxLength} |
||||
|
onChange={(val) => updateValidatorProperty((v) => (v.maxLength = val ? Number(val) : undefined))} |
||||
|
/> |
||||
|
</Col> |
||||
|
</Row> |
||||
|
</div> |
||||
|
</div> |
||||
|
)} |
||||
|
|
||||
|
{/* SELECTION Type */} |
||||
|
{valueType instanceof SelectionStringValueType && ( |
||||
|
<Card |
||||
|
type="inner" |
||||
|
title={ |
||||
|
<Row justify="space-between" align="middle"> |
||||
|
<Col> |
||||
|
{valueType.itemSource.items.length <= 0 && ( |
||||
|
<span className="text-red-500"> |
||||
|
{$t("component.value_type_nput.type.SELECTION.itemsNotBeEmpty")} |
||||
|
</span> |
||||
|
)} |
||||
|
</Col> |
||||
|
<Col> |
||||
|
{!disabled && ( |
||||
|
<Space> |
||||
|
<Button type="primary" onClick={handleAddSelection} icon={<PlusOutlined />}> |
||||
|
{$t("component.value_type_nput.type.SELECTION.actions.create")} |
||||
|
</Button> |
||||
|
<Button danger onClick={handleClearSelection}> |
||||
|
{$t("component.value_type_nput.type.SELECTION.actions.clean")} |
||||
|
</Button> |
||||
|
</Space> |
||||
|
)} |
||||
|
</Col> |
||||
|
</Row> |
||||
|
} |
||||
|
> |
||||
|
<Table |
||||
|
rowKey="value" |
||||
|
columns={selectionColumns} |
||||
|
dataSource={valueType.itemSource.items} |
||||
|
pagination={false} |
||||
|
size="small" |
||||
|
scroll={{ x: true }} |
||||
|
/> |
||||
|
</Card> |
||||
|
)} |
||||
|
</Card> |
||||
|
|
||||
|
{/* Modal for Selection Item */} |
||||
|
<Modal |
||||
|
title={$t("component.value_type_nput.type.SELECTION.modal.title")} |
||||
|
open={modalVisible} |
||||
|
onOk={handleModalOk} |
||||
|
onCancel={() => { |
||||
|
setModalVisible(false); |
||||
|
modalForm.resetFields(); |
||||
|
}} |
||||
|
maskClosable={false} |
||||
|
width={600} |
||||
|
> |
||||
|
<Form form={modalForm} layout="horizontal" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}> |
||||
|
<Form.Item |
||||
|
name="displayText" |
||||
|
label={$t("component.value_type_nput.type.SELECTION.displayText")} |
||||
|
rules={[ |
||||
|
{ |
||||
|
validator: async (_, value) => { |
||||
|
// Logic to validate duplicate Display Text
|
||||
|
if (!validateLocalizer(value)) { |
||||
|
return Promise.reject($t("component.value_type_nput.type.SELECTION.displayTextNotBeEmpty")); |
||||
|
} |
||||
|
if (valueType instanceof SelectionStringValueType) { |
||||
|
// const serializedValue = serializeLocalizer(value);
|
||||
|
const exists = valueType.itemSource.items.some((x) => { |
||||
|
return serializeLocalizer(x.displayText) === value; |
||||
|
}); |
||||
|
if (exists) { |
||||
|
return Promise.reject($t("component.value_type_nput.type.SELECTION.duplicateKeyOrValue")); |
||||
|
} |
||||
|
} |
||||
|
return Promise.resolve(); |
||||
|
}, |
||||
|
}, |
||||
|
]} |
||||
|
> |
||||
|
<LocalizableInput disabled={disabled || editingItem.isEdit} /> |
||||
|
</Form.Item> |
||||
|
<Form.Item |
||||
|
name="value" |
||||
|
label={$t("component.value_type_nput.type.SELECTION.value")} |
||||
|
rules={[ |
||||
|
{ required: true }, |
||||
|
{ |
||||
|
validator: async (_, val) => { |
||||
|
if (valueType instanceof SelectionStringValueType) { |
||||
|
const exists = valueType.itemSource.items.some((x) => { |
||||
|
return x.value === val; |
||||
|
}); |
||||
|
if (exists) { |
||||
|
return Promise.reject($t("component.value_type_nput.type.SELECTION.duplicateKeyOrValue")); |
||||
|
} |
||||
|
} |
||||
|
return Promise.resolve(); |
||||
|
}, |
||||
|
}, |
||||
|
]} |
||||
|
> |
||||
|
<Input disabled={disabled} /> |
||||
|
</Form.Item> |
||||
|
</Form> |
||||
|
</Modal> |
||||
|
</div> |
||||
|
); |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
ValueTypeInput.displayName = "ValueTypeInput"; |
||||
|
|
||||
|
export default ValueTypeInput; |
||||
@ -0,0 +1,130 @@ |
|||||
|
import type { Dictionary } from "#/abp-core"; |
||||
|
import { isNullOrUnDef } from "@/utils/abp/is"; |
||||
|
import { isBoolean, isNumber } from "@/utils/inference"; |
||||
|
import { isNullOrWhiteSpace } from "@/utils/string"; |
||||
|
|
||||
|
export interface ValueValidator { |
||||
|
isValid(value?: any): boolean; |
||||
|
name: string; |
||||
|
|
||||
|
properties: Dictionary<string, any>; |
||||
|
} |
||||
|
|
||||
|
export class AlwaysValidValueValidator implements ValueValidator { |
||||
|
name = "NULL"; |
||||
|
properties: Dictionary<string, any>; |
||||
|
constructor() { |
||||
|
this.properties = {}; |
||||
|
} |
||||
|
isValid(_value?: any): boolean { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class BooleanValueValidator implements ValueValidator { |
||||
|
name = "BOOLEAN"; |
||||
|
properties: Dictionary<string, any>; |
||||
|
constructor() { |
||||
|
this.properties = {}; |
||||
|
} |
||||
|
isValid(value?: any): boolean { |
||||
|
if (isNullOrUnDef(value)) return true; |
||||
|
if (isBoolean(value)) return true; |
||||
|
const bolString = String(value).toLowerCase(); |
||||
|
if (bolString === "true" || bolString === "false") return true; |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class NumericValueValidator implements ValueValidator { |
||||
|
name = "NUMERIC"; |
||||
|
properties: Dictionary<string, any>; |
||||
|
get maxValue(): number | undefined { |
||||
|
return Number(this.properties.MaxValue); |
||||
|
} |
||||
|
|
||||
|
set maxValue(value: number) { |
||||
|
this.properties.MaxValue = value; |
||||
|
} |
||||
|
|
||||
|
get minValue(): number | undefined { |
||||
|
return Number(this.properties.MinValue); |
||||
|
} |
||||
|
|
||||
|
set minValue(value: number) { |
||||
|
this.properties.MinValue = value; |
||||
|
} |
||||
|
|
||||
|
constructor() { |
||||
|
this.properties = {}; |
||||
|
} |
||||
|
|
||||
|
_isValidInternal(value: number): boolean { |
||||
|
if (this.minValue && value < this.minValue) return false; |
||||
|
if (this.maxValue && value > this.maxValue) return false; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
isValid(value?: any): boolean { |
||||
|
if (isNullOrUnDef(value)) return true; |
||||
|
if (isNumber(value)) return this._isValidInternal(value); |
||||
|
const numString = String(value); |
||||
|
if (!isNullOrUnDef(numString)) { |
||||
|
const num = Number(numString); |
||||
|
if (num) return this._isValidInternal(num); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class StringValueValidator implements ValueValidator { |
||||
|
name = "STRING"; |
||||
|
properties: Dictionary<string, any>; |
||||
|
get allowNull(): boolean { |
||||
|
return String(this.properties.AllowNull ?? "true")?.toLowerCase() === "true"; |
||||
|
} |
||||
|
|
||||
|
set allowNull(value: boolean) { |
||||
|
this.properties.AllowNull = value; |
||||
|
} |
||||
|
|
||||
|
get maxLength(): number | undefined { |
||||
|
return Number(this.properties.MaxLength); |
||||
|
} |
||||
|
|
||||
|
set maxLength(value: number) { |
||||
|
this.properties.MaxLength = value; |
||||
|
} |
||||
|
|
||||
|
get minLength(): number | undefined { |
||||
|
return Number(this.properties.MinLength); |
||||
|
} |
||||
|
|
||||
|
set minLength(value: number) { |
||||
|
this.properties.MinLength = value; |
||||
|
} |
||||
|
|
||||
|
get regularExpression(): string { |
||||
|
return String(this.properties.RegularExpression ?? ""); |
||||
|
} |
||||
|
|
||||
|
set regularExpression(value: string) { |
||||
|
this.properties.RegularExpression = value; |
||||
|
} |
||||
|
|
||||
|
constructor() { |
||||
|
this.properties = {}; |
||||
|
} |
||||
|
|
||||
|
isValid(value?: any): boolean { |
||||
|
if (!this.allowNull && isNullOrUnDef(value)) return false; |
||||
|
const valueString = String(value); |
||||
|
if (!this.allowNull && isNullOrWhiteSpace(valueString.trim())) return false; |
||||
|
if (this.minLength && this.minLength > 0 && valueString.length < this.minLength) return false; |
||||
|
if (this.maxLength && this.maxLength > 0 && valueString.length > this.maxLength) return false; |
||||
|
if (!isNullOrWhiteSpace(this.regularExpression)) { |
||||
|
return new RegExp(this.regularExpression).test(valueString); |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,119 @@ |
|||||
|
import type { Dictionary, LocalizableStringInfo } from "#/abp-core"; |
||||
|
|
||||
|
import type { ValueValidator } from "./validator"; |
||||
|
|
||||
|
import { |
||||
|
AlwaysValidValueValidator, |
||||
|
BooleanValueValidator, |
||||
|
NumericValueValidator, |
||||
|
StringValueValidator, |
||||
|
} from "./validator"; |
||||
|
|
||||
|
export interface StringValueType { |
||||
|
name: string; |
||||
|
properties: Dictionary<string, any>; |
||||
|
validator: ValueValidator; |
||||
|
} |
||||
|
|
||||
|
export interface SelectionStringValueItem { |
||||
|
displayText: LocalizableStringInfo; |
||||
|
value: string; |
||||
|
} |
||||
|
|
||||
|
export interface SelectionStringValueItemSource { |
||||
|
items: SelectionStringValueItem[]; |
||||
|
} |
||||
|
|
||||
|
export class FreeTextStringValueType implements StringValueType { |
||||
|
name = "FreeTextStringValueType"; |
||||
|
properties: Dictionary<string, any>; |
||||
|
validator: ValueValidator; |
||||
|
constructor(validator?: ValueValidator) { |
||||
|
this.properties = {}; |
||||
|
this.validator = validator ?? new AlwaysValidValueValidator(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class ToggleStringValueType implements StringValueType { |
||||
|
name = "ToggleStringValueType"; |
||||
|
properties: Dictionary<string, any>; |
||||
|
validator: ValueValidator; |
||||
|
constructor(validator?: ValueValidator) { |
||||
|
this.properties = {}; |
||||
|
this.validator = validator ?? new BooleanValueValidator(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class SelectionStringValueType implements StringValueType { |
||||
|
itemSource: SelectionStringValueItemSource; |
||||
|
name = "SelectionStringValueType"; |
||||
|
properties: Dictionary<string, any>; |
||||
|
validator: ValueValidator; |
||||
|
constructor(validator?: ValueValidator) { |
||||
|
this.properties = {}; |
||||
|
this.itemSource = { |
||||
|
items: [], |
||||
|
}; |
||||
|
this.validator = validator ?? new AlwaysValidValueValidator(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class StringValueTypeSerializer { |
||||
|
_deserializeValidator(validator: any): ValueValidator { |
||||
|
let convertValidator: ValueValidator = new AlwaysValidValueValidator(); |
||||
|
if (validator.name) { |
||||
|
switch (validator.name) { |
||||
|
case "BOOLEAN": { |
||||
|
convertValidator = new BooleanValueValidator(); |
||||
|
break; |
||||
|
} |
||||
|
case "NULL": { |
||||
|
convertValidator = new AlwaysValidValueValidator(); |
||||
|
break; |
||||
|
} |
||||
|
case "NUMERIC": { |
||||
|
convertValidator = new NumericValueValidator(); |
||||
|
break; |
||||
|
} |
||||
|
case "STRING": { |
||||
|
convertValidator = new StringValueValidator(); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
convertValidator.properties = validator.properties; |
||||
|
return convertValidator; |
||||
|
} |
||||
|
|
||||
|
deserialize(value: string): StringValueType { |
||||
|
let valueType: StringValueType; |
||||
|
const valueTypeObj = JSON.parse(value); |
||||
|
switch (valueTypeObj.name) { |
||||
|
case "SELECTION": |
||||
|
case "SelectionStringValueType": { |
||||
|
valueType = new SelectionStringValueType(); |
||||
|
(valueType as SelectionStringValueType).itemSource = valueTypeObj.itemSource; |
||||
|
break; |
||||
|
} |
||||
|
case "TOGGLE": |
||||
|
case "ToggleStringValueType": { |
||||
|
valueType = new ToggleStringValueType(); |
||||
|
break; |
||||
|
} |
||||
|
default: { |
||||
|
valueType = new FreeTextStringValueType(); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
valueType.properties = valueTypeObj.properties; |
||||
|
valueType.validator = this._deserializeValidator(valueTypeObj.validator); |
||||
|
return valueType; |
||||
|
} |
||||
|
|
||||
|
serialize(value: StringValueType): string { |
||||
|
const valueTypeString = JSON.stringify(value); |
||||
|
return valueTypeString; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const valueTypeSerializer = new StringValueTypeSerializer(); |
||||
@ -0,0 +1 @@ |
|||||
|
export * from "./permissions"; |
||||
@ -0,0 +1,20 @@ |
|||||
|
/** 分组权限 */ |
||||
|
export const GroupDefinitionsPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "FeatureManagement.GroupDefinitions.Create", |
||||
|
Default: "FeatureManagement.GroupDefinitions", |
||||
|
/** 删除 */ |
||||
|
Delete: "FeatureManagement.GroupDefinitions.Delete", |
||||
|
/** 更新 */ |
||||
|
Update: "FeatureManagement.GroupDefinitions.Update", |
||||
|
}; |
||||
|
/** 功能定义权限 */ |
||||
|
export const FeatureDefinitionsPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "FeatureManagement.Definitions.Create", |
||||
|
Default: "FeatureManagement.Definitions", |
||||
|
/** 删除 */ |
||||
|
Delete: "FeatureManagement.Definitions.Delete", |
||||
|
/** 更新 */ |
||||
|
Update: "FeatureManagement.Definitions.Update", |
||||
|
}; |
||||
@ -0,0 +1 @@ |
|||||
|
export * from "./permissions"; |
||||
@ -0,0 +1,30 @@ |
|||||
|
/** 资源管理权限 */ |
||||
|
export const ResourcesPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "LocalizationManagement.Resource.Create", |
||||
|
Default: "LocalizationManagement.Resource", |
||||
|
/** 删除 */ |
||||
|
Delete: "LocalizationManagement.Resource.Delete", |
||||
|
/** 更新 */ |
||||
|
Update: "LocalizationManagement.Resource.Update", |
||||
|
}; |
||||
|
/** 语言管理权限 */ |
||||
|
export const LanguagesPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "LocalizationManagement.Language.Create", |
||||
|
Default: "LocalizationManagement.Language", |
||||
|
/** 删除 */ |
||||
|
Delete: "LocalizationManagement.Language.Delete", |
||||
|
/** 更新 */ |
||||
|
Update: "LocalizationManagement.Language.Update", |
||||
|
}; |
||||
|
/** 文本管理权限 */ |
||||
|
export const TextsPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "LocalizationManagement.Text.Create", |
||||
|
Default: "LocalizationManagement.Text", |
||||
|
/** 删除 */ |
||||
|
Delete: "LocalizationManagement.Text.Delete", |
||||
|
/** 更新 */ |
||||
|
Update: "LocalizationManagement.Text.Update", |
||||
|
}; |
||||
@ -0,0 +1,28 @@ |
|||||
|
/** 分组权限 */ |
||||
|
export const GroupDefinitionsPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "Notifications.GroupDefinitions.Create", |
||||
|
Default: "Notifications.GroupDefinitions", |
||||
|
/** 删除 */ |
||||
|
Delete: "Notifications.GroupDefinitions.Delete", |
||||
|
/** 更新 */ |
||||
|
Update: "Notifications.GroupDefinitions.Update", |
||||
|
}; |
||||
|
/** 通知定义权限 */ |
||||
|
export const NotificationDefinitionsPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "Notifications.Definitions.Create", |
||||
|
Default: "Notifications.Definitions", |
||||
|
/** 删除 */ |
||||
|
Delete: "Notifications.Definitions.Delete", |
||||
|
/** 更新 */ |
||||
|
Update: "Notifications.Definitions.Update", |
||||
|
}; |
||||
|
/** 通知权限 */ |
||||
|
export const NotificationPermissions = { |
||||
|
/** 发送通知 */ |
||||
|
Create: "Notifications.Notification.Send", |
||||
|
Default: "Notifications.Notification", |
||||
|
/** 删除 */ |
||||
|
Delete: "Notifications.Notification.Delete", |
||||
|
}; |
||||
@ -0,0 +1,18 @@ |
|||||
|
/** 容器权限 */ |
||||
|
export const ContainerPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "AbpOssManagement.Container.Create", |
||||
|
Default: "AbpOssManagement.Container", |
||||
|
/** 删除 */ |
||||
|
Delete: "AbpOssManagement.Container.Delete", |
||||
|
}; |
||||
|
/** 容器权限 */ |
||||
|
export const OssObjectPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "AbpOssManagement.OssObject.Create", |
||||
|
Default: "AbpOssManagement.OssObject", |
||||
|
/** 删除 */ |
||||
|
Delete: "AbpOssManagement.OssObject.Delete", |
||||
|
/** 下载 */ |
||||
|
Download: "AbpOssManagement.OssObject.Download", |
||||
|
}; |
||||
@ -0,0 +1,59 @@ |
|||||
|
/** 邮件消息权限 */ |
||||
|
export const EmailMessagesPermissions = { |
||||
|
Default: "Platform.EmailMessage", |
||||
|
/** 删除 */ |
||||
|
Delete: "Platform.EmailMessage.Delete", |
||||
|
/** 发送消息 */ |
||||
|
SendMessage: "Platform.EmailMessage.SendMessage", |
||||
|
}; |
||||
|
/** 短信消息权限 */ |
||||
|
export const SmsMessagesPermissions = { |
||||
|
Default: "Platform.SmsMessage", |
||||
|
/** 删除 */ |
||||
|
Delete: "Platform.SmsMessage.Delete", |
||||
|
/** 发送消息 */ |
||||
|
SendMessage: "Platform.SmsMessage.SendMessage", |
||||
|
}; |
||||
|
/** 数据字典权限 */ |
||||
|
export const DataDictionaryPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "Platform.DataDictionary.Create", |
||||
|
/** 默认 */ |
||||
|
Default: "Platform.DataDictionary", |
||||
|
/** 删除 */ |
||||
|
Delete: "Platform.DataDictionary.Delete", |
||||
|
/** 管理项目 */ |
||||
|
ManageItems: "Platform.DataDictionary.ManageItems", |
||||
|
/** 移动 */ |
||||
|
Move: "Platform.DataDictionary.Move", |
||||
|
/** 更新 */ |
||||
|
Update: "Platform.DataDictionary.Update", |
||||
|
}; |
||||
|
/** 布局权限 */ |
||||
|
export const LayoutPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "Platform.Layout.Create", |
||||
|
/** 默认 */ |
||||
|
Default: "Platform.Layout", |
||||
|
/** 删除 */ |
||||
|
Delete: "Platform.Layout.Delete", |
||||
|
/** 更新 */ |
||||
|
Update: "Platform.Layout.Update", |
||||
|
}; |
||||
|
/** 菜单权限 */ |
||||
|
export const MenuPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "Platform.Menu.Create", |
||||
|
/** 默认 */ |
||||
|
Default: "Platform.Menu", |
||||
|
/** 删除 */ |
||||
|
Delete: "Platform.Menu.Delete", |
||||
|
/** 管理角色菜单 */ |
||||
|
ManageRoles: "Platform.Menu.ManageRoles", |
||||
|
/** 管理用户收藏菜单 */ |
||||
|
ManageUserFavorites: "Platform.Menu.ManageUserFavorites", |
||||
|
/** 管理用户菜单 */ |
||||
|
ManageUsers: "Platform.Menu.ManageUsers", |
||||
|
/** 更新 */ |
||||
|
Update: "Platform.Menu.Update", |
||||
|
}; |
||||
@ -0,0 +1,44 @@ |
|||||
|
export enum HttpStatusCode { |
||||
|
Accepted = 202, |
||||
|
Ambiguous = 300, |
||||
|
BadGateway = 502, |
||||
|
BadRequest = 400, |
||||
|
Conflict = 409, |
||||
|
Continue = 100, |
||||
|
Created = 201, |
||||
|
ExpectationFailed = 417, |
||||
|
Forbidden = 403, |
||||
|
GatewayTimeout = 504, |
||||
|
Gone = 410, |
||||
|
HttpVersionNotSupported = 505, |
||||
|
InternalServerError = 500, |
||||
|
LengthRequired = 411, |
||||
|
MethodNotAllowed = 405, |
||||
|
Moved = 301, |
||||
|
NoContent = 204, |
||||
|
NonAuthoritativeInformation = 203, |
||||
|
NotAcceptable = 406, |
||||
|
NotFound = 404, |
||||
|
NotImplemented = 501, |
||||
|
NotModified = 304, |
||||
|
OK = 200, |
||||
|
PartialContent = 206, |
||||
|
PaymentRequired = 402, |
||||
|
PreconditionFailed = 412, |
||||
|
ProxyAuthenticationRequired = 407, |
||||
|
Redirect = 302, |
||||
|
RedirectKeepVerb = 307, |
||||
|
RedirectMethod = 303, |
||||
|
RequestedRangeNotSatisfiable = 416, |
||||
|
RequestEntityTooLarge = 413, |
||||
|
RequestTimeout = 408, |
||||
|
RequestUriTooLong = 414, |
||||
|
ResetContent = 205, |
||||
|
ServiceUnavailable = 503, |
||||
|
SwitchingProtocols = 101, |
||||
|
Unauthorized = 401, |
||||
|
UnsupportedMediaType = 415, |
||||
|
Unused = 306, |
||||
|
UpgradeRequired = 426, |
||||
|
UseProxy = 305, |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
/** 版本权限 */ |
||||
|
export const EditionsPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "AbpSaas.Editions.Create", |
||||
|
/** 默认 */ |
||||
|
Default: "AbpSaas.Editions", |
||||
|
/** 删除 */ |
||||
|
Delete: "AbpSaas.Editions.Delete", |
||||
|
/** 管理功能 */ |
||||
|
ManageFeatures: "AbpSaas.Editions.ManageFeatures", |
||||
|
/** 更新 */ |
||||
|
Update: "AbpSaas.Editions.Update", |
||||
|
}; |
||||
|
/** 租户权限 */ |
||||
|
export const TenantsPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "AbpSaas.Tenants.Create", |
||||
|
/** 默认 */ |
||||
|
Default: "AbpSaas.Tenants", |
||||
|
/** 删除 */ |
||||
|
Delete: "AbpSaas.Tenants.Delete", |
||||
|
/** 管理连接字符串 */ |
||||
|
ManageConnectionStrings: "AbpSaas.Tenants.ManageConnectionStrings", |
||||
|
/** 管理功能 */ |
||||
|
ManageFeatures: "AbpSaas.Tenants.ManageFeatures", |
||||
|
/** 更新 */ |
||||
|
Update: "AbpSaas.Tenants.Update", |
||||
|
}; |
||||
@ -0,0 +1,24 @@ |
|||||
|
/** 作业管理权限 */ |
||||
|
export const BackgroundJobsPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "TaskManagement.BackgroundJobs.Create", |
||||
|
Default: "TaskManagement.BackgroundJobs", |
||||
|
/** 删除 */ |
||||
|
Delete: "TaskManagement.BackgroundJobs.Delete", |
||||
|
/** 管理触发器 */ |
||||
|
ManageActions: "TaskManagement.BackgroundJobs.ManageActions", |
||||
|
/** 管理系统作业 */ |
||||
|
ManageSystemJobs: "TaskManagement.BackgroundJobs.ManageSystemJobs", |
||||
|
/** 暂停 */ |
||||
|
Pause: "TaskManagement.BackgroundJobs.Pause", |
||||
|
/** 恢复 */ |
||||
|
Resume: "TaskManagement.BackgroundJobs.Resume", |
||||
|
/** 启动 */ |
||||
|
Start: "TaskManagement.BackgroundJobs.Start", |
||||
|
/** 停止 */ |
||||
|
Stop: "TaskManagement.BackgroundJobs.Stop", |
||||
|
/** 触发 */ |
||||
|
Trigger: "TaskManagement.BackgroundJobs.Trigger", |
||||
|
/** 修改 */ |
||||
|
Update: "TaskManagement.BackgroundJobs.Update", |
||||
|
}; |
||||
@ -0,0 +1,10 @@ |
|||||
|
/** 模板定义权限 */ |
||||
|
export const TextTemplatePermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "AbpTextTemplating.TextTemplateDefinitions.Create", |
||||
|
Default: "AbpTextTemplating.TextTemplateDefinitions", |
||||
|
/** 删除 */ |
||||
|
Delete: "AbpTextTemplating.TextTemplateDefinitions.Delete", |
||||
|
/** 更新 */ |
||||
|
Update: "AbpTextTemplating.TextTemplateDefinitions.Update", |
||||
|
}; |
||||
@ -0,0 +1,41 @@ |
|||||
|
/** 分组权限 */ |
||||
|
export const GroupDefinitionsPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "AbpWebhooks.GroupDefinitions.Create", |
||||
|
Default: "AbpWebhooks.GroupDefinitions", |
||||
|
/** 删除 */ |
||||
|
Delete: "AbpWebhooks.GroupDefinitions.Delete", |
||||
|
/** 更新 */ |
||||
|
Update: "AbpWebhooks.GroupDefinitions.Update", |
||||
|
}; |
||||
|
|
||||
|
/** Webhook定义权限 */ |
||||
|
export const WebhookDefinitionsPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "AbpWebhooks.Definitions.Create", |
||||
|
Default: "AbpWebhooks.Definitions", |
||||
|
/** 删除 */ |
||||
|
Delete: "AbpWebhooks.Definitions.Delete", |
||||
|
/** 更新 */ |
||||
|
Update: "AbpWebhooks.Definitions.Update", |
||||
|
}; |
||||
|
|
||||
|
/** Webhook订阅权限 */ |
||||
|
export const WebhookSubscriptionPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: "AbpWebhooks.Subscriptions.Create", |
||||
|
Default: "AbpWebhooks.Subscriptions", |
||||
|
/** 删除 */ |
||||
|
Delete: "AbpWebhooks.Subscriptions.Delete", |
||||
|
/** 更新 */ |
||||
|
Update: "AbpWebhooks.Subscriptions.Update", |
||||
|
}; |
||||
|
|
||||
|
/** Webhook发送记录权限 */ |
||||
|
export const WebhooksSendAttemptsPermissions = { |
||||
|
Default: "AbpWebhooks.SendAttempts", |
||||
|
/** 删除 */ |
||||
|
Delete: "AbpWebhooks.SendAttempts.Delete", |
||||
|
/** 更新 */ |
||||
|
Resend: "AbpWebhooks.SendAttempts.Resend", |
||||
|
}; |
||||
@ -0,0 +1,2 @@ |
|||||
|
1. change them to fake as little as possible |
||||
|
2. /abp-react-admin/slash-admin/src/utils/abp |
||||
@ -0,0 +1,37 @@ |
|||||
|
import type { |
||||
|
CurrentUser, |
||||
|
IHasSimpleStateCheckers, |
||||
|
ISimpleStateChecker, |
||||
|
SimpleStateCheckerContext, |
||||
|
} from "#/abp-core/global"; |
||||
|
import useAbpStore from "@/store/abpCoreStore"; |
||||
|
|
||||
|
export interface RequireAuthenticatedStateChecker { |
||||
|
name: string; |
||||
|
} |
||||
|
|
||||
|
export class RequireAuthenticatedSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>> |
||||
|
implements ISimpleStateChecker<TState>, RequireAuthenticatedStateChecker |
||||
|
{ |
||||
|
_currentUser?: CurrentUser; |
||||
|
name = "A"; |
||||
|
constructor(currentUser?: CurrentUser) { |
||||
|
this._currentUser = currentUser; |
||||
|
} |
||||
|
isEnabled(_context: SimpleStateCheckerContext<TState>): boolean { |
||||
|
return this._currentUser?.isAuthenticated ?? false; |
||||
|
} |
||||
|
|
||||
|
serialize(): string { |
||||
|
return JSON.stringify({ |
||||
|
T: this.name, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function requireAuthenticatedSimpleStateChecker< |
||||
|
TState extends IHasSimpleStateCheckers<TState>, |
||||
|
>(): ISimpleStateChecker<TState> { |
||||
|
const { application } = useAbpStore.getState(); |
||||
|
return new RequireAuthenticatedSimpleStateChecker<TState>(application?.currentUser); |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
import type { IHasSimpleStateCheckers, ISimpleStateChecker, SimpleStateCheckerContext } from "#/abp-core/global"; |
||||
|
import type { IFeatureChecker } from "#/features"; |
||||
|
import { useFeatures } from "../use-abp-feature"; |
||||
|
|
||||
|
export interface RequireFeaturesStateChecker { |
||||
|
featureNames: string[]; |
||||
|
name: string; |
||||
|
requiresAll: boolean; |
||||
|
} |
||||
|
|
||||
|
export class RequireFeaturesSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>> |
||||
|
implements ISimpleStateChecker<TState>, RequireFeaturesStateChecker |
||||
|
{ |
||||
|
_featureChecker: IFeatureChecker; |
||||
|
featureNames: string[]; |
||||
|
name = "F"; |
||||
|
requiresAll: boolean; |
||||
|
constructor(featureChecker: IFeatureChecker, featureNames: string[], requiresAll = false) { |
||||
|
this._featureChecker = featureChecker; |
||||
|
this.featureNames = featureNames; |
||||
|
this.requiresAll = requiresAll; |
||||
|
} |
||||
|
isEnabled(_context: SimpleStateCheckerContext<TState>): boolean { |
||||
|
return this._featureChecker.isEnabled(this.featureNames, this.requiresAll); |
||||
|
} |
||||
|
|
||||
|
serialize(): string { |
||||
|
return JSON.stringify({ |
||||
|
A: this.requiresAll, |
||||
|
N: this.featureNames, |
||||
|
T: this.name, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function useRequireFeaturesSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>>( |
||||
|
featureNames: string[], |
||||
|
requiresAll = false, |
||||
|
): ISimpleStateChecker<TState> { |
||||
|
const featureChecker = useFeatures(); |
||||
|
return new RequireFeaturesSimpleStateChecker(featureChecker, featureNames, requiresAll); |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
import type { IGlobalFeatureChecker } from "#/features"; |
||||
|
import type { IHasSimpleStateCheckers, ISimpleStateChecker, SimpleStateCheckerContext } from "#/abp-core/global"; |
||||
|
|
||||
|
import { useGlobalFeatures } from "../use-abp-global-feature"; |
||||
|
|
||||
|
export interface RequireGlobalFeaturesStateChecker { |
||||
|
globalFeatureNames: string[]; |
||||
|
name: string; |
||||
|
requiresAll: boolean; |
||||
|
} |
||||
|
|
||||
|
export class RequireGlobalFeaturesSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>> |
||||
|
implements ISimpleStateChecker<TState>, RequireGlobalFeaturesStateChecker |
||||
|
{ |
||||
|
_globalFeatureChecker: IGlobalFeatureChecker; |
||||
|
globalFeatureNames: string[]; |
||||
|
name = "G"; |
||||
|
requiresAll: boolean; |
||||
|
constructor(globalFeatureChecker: IGlobalFeatureChecker, globalFeatureNames: string[], requiresAll = false) { |
||||
|
this._globalFeatureChecker = globalFeatureChecker; |
||||
|
this.globalFeatureNames = globalFeatureNames; |
||||
|
this.requiresAll = requiresAll; |
||||
|
} |
||||
|
isEnabled(_context: SimpleStateCheckerContext<TState>): boolean { |
||||
|
return this._globalFeatureChecker.isEnabled(this.globalFeatureNames, this.requiresAll); |
||||
|
} |
||||
|
|
||||
|
serialize(): string { |
||||
|
return JSON.stringify({ |
||||
|
A: this.requiresAll, |
||||
|
N: this.globalFeatureNames, |
||||
|
T: this.name, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function useRequireGlobalFeaturesSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>>( |
||||
|
globalFeatureNames: string[], |
||||
|
requiresAll = false, |
||||
|
): ISimpleStateChecker<TState> { |
||||
|
const globalFeatureChecker = useGlobalFeatures(); |
||||
|
return new RequireGlobalFeaturesSimpleStateChecker(globalFeatureChecker, globalFeatureNames, requiresAll); |
||||
|
} |
||||
@ -0,0 +1,93 @@ |
|||||
|
import type { |
||||
|
IHasSimpleStateCheckers, |
||||
|
ISimpleBatchStateChecker, |
||||
|
ISimpleStateChecker, |
||||
|
SimpleBatchStateCheckerContext, |
||||
|
SimpleStateCheckerContext, |
||||
|
} from "#/abp-core/global"; |
||||
|
import type { IPermissionChecker } from "#/abp-core/permissions"; |
||||
|
|
||||
|
import { useAuthorization } from "../use-abp-authorization"; |
||||
|
|
||||
|
export class RequirePermissionsSimpleBatchStateCheckerModel<TState extends IHasSimpleStateCheckers<TState>> { |
||||
|
permissions: string[]; |
||||
|
requiresAll: boolean; |
||||
|
state: TState; |
||||
|
constructor(state: TState, permissions: string[], requiresAll = true) { |
||||
|
this.state = state; |
||||
|
this.permissions = permissions; |
||||
|
this.requiresAll = requiresAll; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export interface RequirePermissionsStateChecker<TState extends IHasSimpleStateCheckers<TState>> { |
||||
|
model: RequirePermissionsSimpleBatchStateCheckerModel<TState>; |
||||
|
name: string; |
||||
|
} |
||||
|
|
||||
|
export class RequirePermissionsSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>> |
||||
|
implements ISimpleStateChecker<TState>, RequirePermissionsStateChecker<TState> |
||||
|
{ |
||||
|
_permissionChecker: IPermissionChecker; |
||||
|
model: RequirePermissionsSimpleBatchStateCheckerModel<TState>; |
||||
|
name = "P"; |
||||
|
constructor(permissionChecker: IPermissionChecker, model: RequirePermissionsSimpleBatchStateCheckerModel<TState>) { |
||||
|
this.model = model; |
||||
|
this._permissionChecker = permissionChecker; |
||||
|
} |
||||
|
isEnabled(_context: SimpleStateCheckerContext<TState>): boolean { |
||||
|
return this._permissionChecker.isGranted(this.model.permissions, this.model.requiresAll); |
||||
|
} |
||||
|
|
||||
|
serialize(): string { |
||||
|
return JSON.stringify({ |
||||
|
A: this.model.requiresAll, |
||||
|
N: this.model.permissions, |
||||
|
T: this.name, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class RequirePermissionsSimpleBatchStateChecker<TState extends IHasSimpleStateCheckers<TState>> |
||||
|
implements ISimpleBatchStateChecker<TState> |
||||
|
{ |
||||
|
_permissionChecker: IPermissionChecker; |
||||
|
models: RequirePermissionsSimpleBatchStateCheckerModel<TState>[]; |
||||
|
name = "P"; |
||||
|
constructor(permissionChecker: IPermissionChecker, models: RequirePermissionsSimpleBatchStateCheckerModel<TState>[]) { |
||||
|
this.models = models; |
||||
|
this._permissionChecker = permissionChecker; |
||||
|
} |
||||
|
isEnabled(context: SimpleBatchStateCheckerContext<TState>) { |
||||
|
// 1. Initialize a Map instead of a plain object
|
||||
|
const result = new Map<TState, boolean>(); |
||||
|
|
||||
|
context.states.forEach((state) => { |
||||
|
const model = this.models.find((x) => x.state === state); |
||||
|
if (model) { |
||||
|
// 2. Use .set() to map the object key to the boolean value
|
||||
|
result.set(model.state, this._permissionChecker.isGranted(model.permissions, model.requiresAll)); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
serialize(): string | undefined { |
||||
|
return undefined; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function useRequirePermissionsSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>>( |
||||
|
model: RequirePermissionsSimpleBatchStateCheckerModel<TState>, |
||||
|
): ISimpleStateChecker<TState> { |
||||
|
const permissionChecker = useAuthorization(); |
||||
|
return new RequirePermissionsSimpleStateChecker<TState>(permissionChecker, model); |
||||
|
} |
||||
|
|
||||
|
export function useRequirePermissionsSimpleBatchStateChecker<TState extends IHasSimpleStateCheckers<TState>>( |
||||
|
models: RequirePermissionsSimpleBatchStateCheckerModel<TState>[], |
||||
|
): ISimpleBatchStateChecker<TState> { |
||||
|
const permissionChecker = useAuthorization(); |
||||
|
return new RequirePermissionsSimpleBatchStateChecker<TState>(permissionChecker, models); |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
import type { IPermissionChecker } from "#/abp-core"; |
||||
|
import useAbpStore from "@/store/abpCoreStore"; |
||||
|
// import { useMemo } from "react";
|
||||
|
|
||||
|
export function useAuthorization(): IPermissionChecker { |
||||
|
// const application = useAbpStore((state) => state.application);
|
||||
|
const { application } = useAbpStore.getState(); |
||||
|
// const grantedPolicies = useMemo(() => {
|
||||
|
// return application?.auth.grantedPolicies ?? {};
|
||||
|
// }, [application]);
|
||||
|
const grantedPolicies = application?.auth.grantedPolicies ?? {}; |
||||
|
|
||||
|
function isGranted(name: string | string[], requiresAll?: boolean): boolean { |
||||
|
if (Array.isArray(name)) { |
||||
|
if (requiresAll === undefined || requiresAll === true) { |
||||
|
return name.every((n) => grantedPolicies[n]); |
||||
|
} |
||||
|
return name.some((n) => grantedPolicies[n]); |
||||
|
} |
||||
|
return grantedPolicies[name] ?? false; |
||||
|
} |
||||
|
|
||||
|
function authorize(name: string | string[]): void { |
||||
|
if (!isGranted(name)) { |
||||
|
throw new Error(`Authorization failed! Given policy has not granted: ${name}`); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
authorize, |
||||
|
isGranted, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
import type { FeatureValue, IFeatureChecker } from "#/features"; |
||||
|
import useAbpStore from "@/store/abpCoreStore"; |
||||
|
// import { useMemo } from "react";
|
||||
|
|
||||
|
export function useFeatures(): IFeatureChecker { |
||||
|
// const application = useAbpStore((state) => state.application);
|
||||
|
const { application } = useAbpStore.getState(); |
||||
|
// const features = useMemo<FeatureValue[]>(() => {
|
||||
|
// if (!application?.features?.values) {
|
||||
|
// return [];
|
||||
|
// }
|
||||
|
// return Object.keys(application.features.values).map((name) => ({
|
||||
|
// name,
|
||||
|
// value: application.features.values[name] ?? "",
|
||||
|
// }));
|
||||
|
// }, [application]);
|
||||
|
|
||||
|
let features: FeatureValue[]; |
||||
|
if (!application?.features?.values) { |
||||
|
features = []; |
||||
|
} else { |
||||
|
features = Object.keys(application.features.values).map((name) => ({ |
||||
|
name, |
||||
|
value: application.features.values[name] ?? "", |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
function get(name: string): FeatureValue | undefined { |
||||
|
return features.find((feature) => feature.name === name); |
||||
|
} |
||||
|
|
||||
|
function _isEnabled(name: string): boolean { |
||||
|
const setting = get(name); |
||||
|
return setting?.value.toLowerCase() === "true"; |
||||
|
} |
||||
|
|
||||
|
const featureChecker: IFeatureChecker = { |
||||
|
getOrEmpty(name: string) { |
||||
|
return get(name)?.value ?? ""; |
||||
|
}, |
||||
|
|
||||
|
isEnabled(featureNames: string | string[], requiresAll?: boolean) { |
||||
|
if (Array.isArray(featureNames)) { |
||||
|
if (featureNames.length === 0) return true; |
||||
|
if (requiresAll === undefined || requiresAll === true) { |
||||
|
return featureNames.every(_isEnabled); |
||||
|
} |
||||
|
return featureNames.some(_isEnabled); |
||||
|
} |
||||
|
return _isEnabled(featureNames); |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
return featureChecker; |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
import useAbpStore from "@/store/abpCoreStore"; |
||||
|
import { isNullOrWhiteSpace } from "@/utils/string"; |
||||
|
// import { useMemo } from "react";
|
||||
|
|
||||
|
export function useGlobalFeatures() { |
||||
|
// const application = useAbpStore((state) => state.application);
|
||||
|
const { application } = useAbpStore.getState(); |
||||
|
|
||||
|
// const enabledFeatures = useMemo<string[]>(() => {
|
||||
|
// if (!application?.globalFeatures?.enabledFeatures) {
|
||||
|
// return [];
|
||||
|
// }
|
||||
|
// return application.globalFeatures.enabledFeatures;
|
||||
|
// }, [application]);
|
||||
|
|
||||
|
let enabledFeatures: string[]; |
||||
|
if (!application?.globalFeatures?.enabledFeatures) { |
||||
|
enabledFeatures = []; |
||||
|
} else { |
||||
|
enabledFeatures = application.globalFeatures.enabledFeatures; |
||||
|
} |
||||
|
|
||||
|
function _isEnabled(name: string): boolean { |
||||
|
// Find if the feature exists in the enabled list
|
||||
|
const feature = enabledFeatures.find((f) => f === name); |
||||
|
return !isNullOrWhiteSpace(feature); |
||||
|
} |
||||
|
|
||||
|
function isEnabled(featureNames: string | string[], requiresAll?: boolean): boolean { |
||||
|
if (Array.isArray(featureNames)) { |
||||
|
if (featureNames.length === 0) return true; |
||||
|
if (requiresAll === undefined || requiresAll === true) { |
||||
|
return featureNames.every(_isEnabled); |
||||
|
} |
||||
|
return featureNames.some(_isEnabled); |
||||
|
} |
||||
|
return _isEnabled(featureNames); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
isEnabled, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,71 @@ |
|||||
|
import { HttpStatusCode } from "@/constants/request/http-status"; |
||||
|
|
||||
|
export function useHttpStatusCodeMap() { |
||||
|
const httpStatusCodeMap: { [key: number]: string } = { |
||||
|
[HttpStatusCode.Accepted]: "202 - Accepted", |
||||
|
[HttpStatusCode.Ambiguous]: "300 - Ambiguous/Multiple Choices", |
||||
|
[HttpStatusCode.BadGateway]: "502 - Bad Gateway", |
||||
|
[HttpStatusCode.BadRequest]: "400 - Bad Request", |
||||
|
[HttpStatusCode.Conflict]: "409 - Conflict", |
||||
|
[HttpStatusCode.Continue]: "100 - Continue", |
||||
|
[HttpStatusCode.Created]: "201 - Created", |
||||
|
[HttpStatusCode.ExpectationFailed]: "417 - Expectation Failed", |
||||
|
[HttpStatusCode.Forbidden]: "403 - Forbidden", |
||||
|
[HttpStatusCode.GatewayTimeout]: "504 - Gateway Timeout", |
||||
|
[HttpStatusCode.Gone]: "410 - Gone", |
||||
|
[HttpStatusCode.HttpVersionNotSupported]: "505 - Http Version Not Supported", |
||||
|
[HttpStatusCode.InternalServerError]: "500 - Internal Server Error", |
||||
|
[HttpStatusCode.LengthRequired]: "411 - Length Required", |
||||
|
[HttpStatusCode.MethodNotAllowed]: "405 - Method Not Allowed", |
||||
|
[HttpStatusCode.Moved]: "301 - Moved/Moved Permanently", |
||||
|
[HttpStatusCode.NoContent]: "204 - No Content", |
||||
|
[HttpStatusCode.NonAuthoritativeInformation]: "203 - Non Authoritative Information", |
||||
|
[HttpStatusCode.NotAcceptable]: "406 - Not Acceptable", |
||||
|
[HttpStatusCode.NotFound]: "404 - Not Found", |
||||
|
[HttpStatusCode.NotImplemented]: "501 - Not Implemented", |
||||
|
[HttpStatusCode.NotModified]: "304 - Not Modified", |
||||
|
[HttpStatusCode.OK]: "200 - OK", |
||||
|
[HttpStatusCode.PartialContent]: "206 - Partial Content", |
||||
|
[HttpStatusCode.PaymentRequired]: "402 - Payment Required", |
||||
|
[HttpStatusCode.PreconditionFailed]: "412 - Precondition Failed", |
||||
|
[HttpStatusCode.ProxyAuthenticationRequired]: "407 - Proxy Authentication Required", |
||||
|
[HttpStatusCode.Redirect]: "302 - Found/Redirect", |
||||
|
[HttpStatusCode.RedirectKeepVerb]: "307 - Redirect Keep Verb/Temporary Redirect", |
||||
|
[HttpStatusCode.RedirectMethod]: "303 - Redirect Method/See Other", |
||||
|
[HttpStatusCode.RequestedRangeNotSatisfiable]: "416 - Requested Range Not Satisfiable", |
||||
|
[HttpStatusCode.RequestEntityTooLarge]: "413 - Request Entity Too Large", |
||||
|
[HttpStatusCode.RequestTimeout]: "408 - Request Timeout", |
||||
|
[HttpStatusCode.RequestUriTooLong]: "414 - Request Uri Too Long", |
||||
|
[HttpStatusCode.ResetContent]: "205 - Reset Content", |
||||
|
[HttpStatusCode.ServiceUnavailable]: "503 - Service Unavailable", |
||||
|
[HttpStatusCode.SwitchingProtocols]: "101 - Switching Protocols", |
||||
|
[HttpStatusCode.Unauthorized]: "401 - Unauthorized", |
||||
|
[HttpStatusCode.UnsupportedMediaType]: "415 - Unsupported Media Type", |
||||
|
[HttpStatusCode.Unused]: "306 - Unused", |
||||
|
[HttpStatusCode.UpgradeRequired]: "426 - Upgrade Required", |
||||
|
[HttpStatusCode.UseProxy]: "305 - Use Proxy", |
||||
|
}; |
||||
|
|
||||
|
function getHttpStatusColor(statusCode: HttpStatusCode) { |
||||
|
if (statusCode < 200) { |
||||
|
return "default"; |
||||
|
} |
||||
|
if (statusCode >= 200 && statusCode < 300) { |
||||
|
return "success"; |
||||
|
} |
||||
|
if (statusCode >= 300 && statusCode < 400) { |
||||
|
return "processing"; |
||||
|
} |
||||
|
if (statusCode >= 400 && statusCode < 500) { |
||||
|
return "warning"; |
||||
|
} |
||||
|
if (statusCode >= 500) { |
||||
|
return "error"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
getHttpStatusColor, |
||||
|
httpStatusCodeMap, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,107 @@ |
|||||
|
import { isNullOrUnDef } from "@/utils/abp/is"; |
||||
|
import type { IHasSimpleStateCheckers, ISimpleStateChecker, ISimpleStateCheckerSerializer } from "#/abp-core/global"; |
||||
|
|
||||
|
import { isNullOrWhiteSpace } from "@/utils/string"; |
||||
|
import { requireAuthenticatedSimpleStateChecker } from "./simple-state-checking/use-require-authenticated-simple-state-checker"; |
||||
|
import { useRequireFeaturesSimpleStateChecker } from "./simple-state-checking/use-require-features-simple-state-checker"; |
||||
|
import { useRequirePermissionsSimpleStateChecker } from "./simple-state-checking/use-require-permissions-simple-state-checker"; |
||||
|
import { useRequireGlobalFeaturesSimpleStateChecker } from "./simple-state-checking/use-require-global-features-simple-state-checker"; |
||||
|
|
||||
|
export function useSimpleStateCheck<TState extends IHasSimpleStateCheckers<TState>>(): ISimpleStateCheckerSerializer { |
||||
|
function deserialize<TState extends IHasSimpleStateCheckers<TState>>( |
||||
|
jsonObject: any, |
||||
|
state: TState, |
||||
|
): ISimpleStateChecker<TState> | undefined { |
||||
|
if (isNullOrUnDef(jsonObject) || !Reflect.has(jsonObject, "T")) { |
||||
|
return undefined; |
||||
|
} |
||||
|
switch (String(jsonObject.T)) { |
||||
|
case "A": { |
||||
|
return requireAuthenticatedSimpleStateChecker(); |
||||
|
} |
||||
|
case "F": { |
||||
|
const features = jsonObject.N as string[]; |
||||
|
if (features === undefined) { |
||||
|
throw new Error(`'N' is not an array in the serialized state checker! JsonObject: ${jsonObject}`); |
||||
|
} |
||||
|
return useRequireFeaturesSimpleStateChecker(features, jsonObject.A === true); |
||||
|
} |
||||
|
case "G": { |
||||
|
const globalFeatures = jsonObject.N as string[]; |
||||
|
if (globalFeatures === undefined) { |
||||
|
throw new Error(`'N' is not an array in the serialized state checker! JsonObject: ${jsonObject}`); |
||||
|
} |
||||
|
return useRequireGlobalFeaturesSimpleStateChecker(globalFeatures, jsonObject.A === true); |
||||
|
} |
||||
|
case "P": { |
||||
|
const permissions = jsonObject.N as string[]; |
||||
|
if (permissions === undefined) { |
||||
|
throw new Error(`'N' is not an array in the serialized state checker! JsonObject: ${jsonObject}`); |
||||
|
} |
||||
|
return useRequirePermissionsSimpleStateChecker({ |
||||
|
permissions, |
||||
|
requiresAll: jsonObject.A === true, |
||||
|
state, |
||||
|
}); |
||||
|
} |
||||
|
default: { |
||||
|
return undefined; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function deserializeArray<TState extends IHasSimpleStateCheckers<TState>>( |
||||
|
value: string, |
||||
|
state: TState, |
||||
|
): ISimpleStateChecker<TState>[] { |
||||
|
if (isNullOrWhiteSpace(value)) return []; |
||||
|
const jsonObject = JSON.parse(value); |
||||
|
if (isNullOrUnDef(jsonObject)) return []; |
||||
|
if (Array.isArray(jsonObject)) { |
||||
|
if (jsonObject.length === 0) return []; |
||||
|
return jsonObject |
||||
|
.map((json) => deserialize(json, state)) |
||||
|
.filter((checker) => !isNullOrUnDef(checker)) |
||||
|
.map((checker) => checker); |
||||
|
} |
||||
|
const stateChecker = deserialize(jsonObject, state); |
||||
|
if (!stateChecker) return []; |
||||
|
return [stateChecker]; |
||||
|
} |
||||
|
|
||||
|
function serialize<TState extends IHasSimpleStateCheckers<TState>>( |
||||
|
checker: ISimpleStateChecker<TState>, |
||||
|
): string | undefined { |
||||
|
return checker.serialize(); |
||||
|
} |
||||
|
|
||||
|
function serializeArray<TState extends IHasSimpleStateCheckers<TState>>( |
||||
|
stateCheckers: ISimpleStateChecker<TState>[], |
||||
|
): string | undefined { |
||||
|
if (stateCheckers.length === 0) return undefined; |
||||
|
if (stateCheckers.length === 1) { |
||||
|
const stateChecker = stateCheckers[0]; |
||||
|
const single = stateChecker?.serialize(); |
||||
|
if (isNullOrUnDef(single)) return undefined; |
||||
|
return `[${single}]`; |
||||
|
} |
||||
|
let serializedCheckers = ""; |
||||
|
stateCheckers.forEach((checker) => { |
||||
|
const serializedChecker = checker.serialize(); |
||||
|
if (!isNullOrUnDef(serializedChecker)) { |
||||
|
serializedCheckers += `${serializedChecker},`; |
||||
|
} |
||||
|
}); |
||||
|
if (serializedCheckers.endsWith(",")) { |
||||
|
serializedCheckers = serializedCheckers.slice(0, Math.max(0, serializedCheckers.length - 1)); |
||||
|
} |
||||
|
return serializedCheckers.length > 0 ? `[${serializedCheckers}]` : undefined; |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
deserialize, |
||||
|
deserializeArray, |
||||
|
serialize, |
||||
|
serializeArray, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
import { useAbpSettings } from "@/store/abpSettingStore"; |
||||
|
import { ValidationEnum } from "@/constants/abp-core"; |
||||
|
import { getUnique, isNullOrWhiteSpace } from "@/utils/string"; |
||||
|
import { isDigit, isLetterOrDigit, isLower, isUpper } from "@/utils/abp/regex"; |
||||
|
import { useLocalizer } from "../use-localization"; |
||||
|
import { useMemo } from "react"; |
||||
|
|
||||
|
export function usePasswordValidator() { |
||||
|
const { getNumber, isTrue } = useAbpSettings(); |
||||
|
const { L } = useLocalizer(["AbpIdentity", "AbpValidation", "AbpUi"]); |
||||
|
|
||||
|
const passwordSetting = useMemo(() => { |
||||
|
return { |
||||
|
requiredDigit: isTrue("Abp.Identity.Password.RequireDigit"), |
||||
|
requiredLength: getNumber("Abp.Identity.Password.RequiredLength"), |
||||
|
requiredLowercase: isTrue("Abp.Identity.Password.RequireLowercase"), |
||||
|
requiredUniqueChars: getNumber("Abp.Identity.Password.RequiredUniqueChars"), |
||||
|
requireNonAlphanumeric: isTrue("Abp.Identity.Password.RequireNonAlphanumeric"), |
||||
|
requireUppercase: isTrue("Abp.Identity.Password.RequireUppercase"), |
||||
|
}; |
||||
|
}, [getNumber, isTrue]); |
||||
|
|
||||
|
function validate(password: string): Promise<void> { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
// 1. Check Empty
|
||||
|
if (isNullOrWhiteSpace(password)) { |
||||
|
return reject(L(ValidationEnum.FieldRequired, [L("DisplayName:Password")])); |
||||
|
} |
||||
|
|
||||
|
const setting = passwordSetting; |
||||
|
|
||||
|
// 2. Check Length
|
||||
|
if (setting.requiredLength > 0 && password.length < setting.requiredLength) { |
||||
|
return reject(L("Volo.Abp.Identity:PasswordTooShort", [String(setting.requiredLength)])); |
||||
|
} |
||||
|
|
||||
|
// 3. Check Non-Alphanumeric
|
||||
|
if (setting.requireNonAlphanumeric && isLetterOrDigit(password)) { |
||||
|
return reject(L("Volo.Abp.Identity:PasswordRequiresNonAlphanumeric")); |
||||
|
} |
||||
|
|
||||
|
// 4. Check Digit
|
||||
|
if (setting.requiredDigit && !isDigit(password)) { |
||||
|
return reject(L("Volo.Abp.Identity:PasswordRequiresDigit")); |
||||
|
} |
||||
|
|
||||
|
// 5. Check Lowercase
|
||||
|
if (setting.requiredLowercase && !isLower(password)) { |
||||
|
return reject(L("Volo.Abp.Identity:PasswordRequiresLower")); |
||||
|
} |
||||
|
|
||||
|
// 6. Check Uppercase
|
||||
|
if (setting.requireUppercase && !isUpper(password)) { |
||||
|
return reject(L("Volo.Abp.Identity:PasswordRequiresUpper")); |
||||
|
} |
||||
|
|
||||
|
// 7. Check Unique Chars
|
||||
|
if (setting.requiredUniqueChars >= 1 && getUnique(password).length < setting.requiredUniqueChars) { |
||||
|
return reject(L("Volo.Abp.Identity:PasswordRequiredUniqueChars", [String(setting.requiredUniqueChars)])); |
||||
|
} |
||||
|
|
||||
|
return resolve(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
validate, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
import { useAbpSettings } from "@/store/abpSettingStore"; |
||||
|
|
||||
|
/** |
||||
|
* 摘自 https://www.html5tricks.com/demo/js-passwd-generator/index.html
|
||||
|
* 侵权请联系删除 |
||||
|
*/ |
||||
|
export function useRandomPassword() { |
||||
|
const randomFunc: { [key: string]: () => string } = { |
||||
|
defaultNumber: getRandomNumber, |
||||
|
lower: getRandomLower, |
||||
|
number: getRandomNumber, |
||||
|
symbol: getRandomSymbol, |
||||
|
upper: getRandomUpper, |
||||
|
}; |
||||
|
|
||||
|
function getRandomLower() { |
||||
|
return String.fromCodePoint(Math.floor(Math.random() * 26) + 97); |
||||
|
} |
||||
|
|
||||
|
function getRandomUpper() { |
||||
|
return String.fromCodePoint(Math.floor(Math.random() * 26) + 65); |
||||
|
} |
||||
|
|
||||
|
function getRandomNumber() { |
||||
|
return String.fromCodePoint(Math.floor(Math.random() * 10) + 48); |
||||
|
} |
||||
|
|
||||
|
function getRandomSymbol() { |
||||
|
const symbols = '~!@#$%^&*()_+{}":?><;.,'; |
||||
|
return symbols[Math.floor(Math.random() * symbols.length)] ?? ""; |
||||
|
} |
||||
|
|
||||
|
function generatePassword() { |
||||
|
const { getNumber, isTrue } = useAbpSettings(); |
||||
|
// 根据配置项生成随机密码
|
||||
|
// 密码长度
|
||||
|
const length = getNumber("Abp.Identity.Password.RequiredLength"); |
||||
|
// 需要小写字母
|
||||
|
const lower = isTrue("Abp.Identity.Password.RequireLowercase"); |
||||
|
// 需要大写字母
|
||||
|
const upper = isTrue("Abp.Identity.Password.RequireUppercase"); |
||||
|
// 需要数字
|
||||
|
const number = isTrue("Abp.Identity.Password.RequireDigit"); |
||||
|
// 需要符号
|
||||
|
const symbol = isTrue("Abp.Identity.Password.RequireNonAlphanumeric"); |
||||
|
// 默认生成数字
|
||||
|
const defaultNumber = !lower && !upper && !number && !symbol; |
||||
|
|
||||
|
let generatedPassword = ""; |
||||
|
const typesArr = [{ lower }, { upper }, { number }, { symbol }, { defaultNumber }].filter( |
||||
|
(item) => Object.values(item)[0], |
||||
|
); |
||||
|
for (let i = 0; i < length; i++) { |
||||
|
typesArr.forEach((type) => { |
||||
|
const funcName = Object.keys(type)[0]; |
||||
|
if (funcName && randomFunc[funcName]) { |
||||
|
generatedPassword += randomFunc[funcName](); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
return generatedPassword.slice(0, length); |
||||
|
} |
||||
|
|
||||
|
return { generatePassword }; |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
import { useTranslation } from "react-i18next"; |
||||
|
import { JobPriority, JobSource, JobStatus, JobType } from "#/tasks/job-infos"; |
||||
|
|
||||
|
export function useJobEnumsMap() { |
||||
|
const { t: $t } = useTranslation(); |
||||
|
|
||||
|
const jobStatusMap: Record<number, string> = { |
||||
|
[JobStatus.Completed]: $t("TaskManagement.DisplayName:Completed"), |
||||
|
[JobStatus.FailedRetry]: $t("TaskManagement.DisplayName:FailedRetry"), |
||||
|
[JobStatus.None]: $t("TaskManagement.DisplayName:None"), |
||||
|
[JobStatus.Paused]: $t("TaskManagement.DisplayName:Paused"), |
||||
|
[JobStatus.Queuing]: $t("TaskManagement.DisplayName:Queuing"), |
||||
|
[JobStatus.Running]: $t("TaskManagement.DisplayName:Running"), |
||||
|
[JobStatus.Stopped]: $t("TaskManagement.DisplayName:Stopped"), |
||||
|
}; |
||||
|
|
||||
|
const jobStatusColor: Record<number, string> = { |
||||
|
[JobStatus.Completed]: "#339933", |
||||
|
[JobStatus.FailedRetry]: "#FF6600", |
||||
|
[JobStatus.None]: "", |
||||
|
[JobStatus.Paused]: "#CC6633", |
||||
|
[JobStatus.Queuing]: "#008B8B", |
||||
|
[JobStatus.Running]: "#3399CC", |
||||
|
[JobStatus.Stopped]: "#F00000", |
||||
|
}; |
||||
|
|
||||
|
const jobTypeMap: Record<number, string> = { |
||||
|
[JobType.Once]: $t("TaskManagement.DisplayName:Once"), |
||||
|
[JobType.Period]: $t("TaskManagement.DisplayName:Period"), |
||||
|
[JobType.Persistent]: $t("TaskManagement.DisplayName:Persistent"), |
||||
|
}; |
||||
|
|
||||
|
const jobPriorityMap: Record<number, string> = { |
||||
|
[JobPriority.AboveNormal]: $t("TaskManagement.DisplayName:AboveNormal"), |
||||
|
[JobPriority.BelowNormal]: $t("TaskManagement.DisplayName:BelowNormal"), |
||||
|
[JobPriority.High]: $t("TaskManagement.DisplayName:High"), |
||||
|
[JobPriority.Low]: $t("TaskManagement.DisplayName:Low"), |
||||
|
[JobPriority.Normal]: $t("TaskManagement.DisplayName:Normal"), |
||||
|
}; |
||||
|
|
||||
|
const jobPriorityColor: Record<number, string> = { |
||||
|
[JobPriority.AboveNormal]: "orange", |
||||
|
[JobPriority.BelowNormal]: "cyan", |
||||
|
[JobPriority.High]: "red", |
||||
|
[JobPriority.Low]: "purple", |
||||
|
[JobPriority.Normal]: "blue", |
||||
|
}; |
||||
|
|
||||
|
const jobSourceMap: Record<number, string> = { |
||||
|
[JobSource.None]: $t("TaskManagement.DisplayName:None"), |
||||
|
[JobSource.System]: $t("TaskManagement.DisplayName:System"), |
||||
|
[JobSource.User]: $t("TaskManagement.DisplayName:User"), |
||||
|
}; |
||||
|
|
||||
|
return { |
||||
|
jobPriorityColor, |
||||
|
jobPriorityMap, |
||||
|
jobSourceMap, |
||||
|
jobStatusColor, |
||||
|
jobStatusMap, |
||||
|
jobTypeMap, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,346 @@ |
|||||
|
import type { RuleCreator } from "#/abp-core"; |
||||
|
import type { |
||||
|
Field, |
||||
|
FieldBeetWeen, |
||||
|
FieldContains, |
||||
|
FieldDefineValidator, |
||||
|
FieldLength, |
||||
|
FieldMatch, |
||||
|
FieldRange, |
||||
|
FieldRegular, |
||||
|
FieldValidator, |
||||
|
Rule, |
||||
|
RuleType, |
||||
|
} from "#/abp-core"; |
||||
|
import { ValidationEnum } from "@/constants/abp-core"; |
||||
|
|
||||
|
import { useLocalizer } from "./use-localization"; |
||||
|
import { isEmail, isPhone } from "@/utils/abp/regex"; |
||||
|
|
||||
|
export function useValidation(): RuleCreator { |
||||
|
const { L } = useLocalizer(["AbpValidation"]); |
||||
|
|
||||
|
function _getFieldName(field: Field) { |
||||
|
return __getFieldName(field.name ?? "", field.resourceName, field.prefix, field.connector); |
||||
|
} |
||||
|
|
||||
|
function __getFieldName(fieldName: string, resourceName?: string, prefix?: string, connector?: string) { |
||||
|
if (fieldName && resourceName) { |
||||
|
const finalFieldName = prefix ? `${prefix}${connector ?? ":"}${fieldName}` : fieldName; |
||||
|
const { L: l } = useLocalizer(resourceName); |
||||
|
return l(finalFieldName); |
||||
|
} |
||||
|
return fieldName; |
||||
|
} |
||||
|
|
||||
|
function _createRule(options: { |
||||
|
len?: number; |
||||
|
max?: number; |
||||
|
message?: string; |
||||
|
min?: number; |
||||
|
required?: boolean; |
||||
|
trigger?: "blur" | "change" | ["change", "blur"]; |
||||
|
type?: "array" | RuleType; |
||||
|
validator?: (rule: any, value: any, callback: any, source?: any, options?: any) => Promise<void> | void; |
||||
|
}): Rule[] { |
||||
|
return [ |
||||
|
{ |
||||
|
len: options.len, |
||||
|
max: options.max, |
||||
|
message: options.message, |
||||
|
min: options.min, |
||||
|
required: options.required, |
||||
|
trigger: options.trigger, |
||||
|
type: options.type, |
||||
|
validator: options.validator, |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
function _createValidator(field: Field, useNameEnum: string, notNameEnum: string, required?: boolean): Rule { |
||||
|
const message = field.name ? L(useNameEnum, [_getFieldName(field)]) : L(notNameEnum); |
||||
|
return { |
||||
|
message, |
||||
|
required, |
||||
|
trigger: field.trigger, |
||||
|
type: field.type, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function _createLengthValidator( |
||||
|
field: FieldLength, |
||||
|
checkMaximum: boolean, |
||||
|
useNameEnum: string, |
||||
|
notNameEnum: string, |
||||
|
required?: boolean, |
||||
|
): Rule { |
||||
|
const message = field.name ? L(useNameEnum, [_getFieldName(field), field.length]) : L(notNameEnum, [field.length]); |
||||
|
|
||||
|
function checkLength(value: any[] | string) { |
||||
|
return checkMaximum ? field.length > value.length : value.length > field.length; |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
message, |
||||
|
required, |
||||
|
trigger: field.trigger, |
||||
|
type: field.type, |
||||
|
validator: (_: any, value: string) => { |
||||
|
if (!checkLength(value)) { |
||||
|
return Promise.reject(message); |
||||
|
} |
||||
|
return Promise.resolve(); |
||||
|
}, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function _createLengthRangValidator( |
||||
|
field: FieldRange, |
||||
|
useNameEnum: string, |
||||
|
notNameEnum: string, |
||||
|
required?: boolean, |
||||
|
): Rule { |
||||
|
const message = field.name |
||||
|
? L(useNameEnum, [_getFieldName(field), field.maximum, field.minimum]) |
||||
|
: L(notNameEnum, [field.minimum, field.maximum]); |
||||
|
return { |
||||
|
message, |
||||
|
required, |
||||
|
trigger: field.trigger, |
||||
|
type: field.type, |
||||
|
validator: (_: any, value: string) => { |
||||
|
if (value.length < field.minimum || value.length > field.maximum) { |
||||
|
return Promise.reject(message); |
||||
|
} |
||||
|
return Promise.resolve(); |
||||
|
}, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function _createBeetWeenValidator(field: FieldBeetWeen): Rule { |
||||
|
const message = field.name |
||||
|
? L(ValidationEnum.FieldMustBeetWeen, [_getFieldName(field), field.start, field.end]) |
||||
|
: L(ValidationEnum.ThisFieldMustBeBetween, [field.start, field.end]); |
||||
|
return { |
||||
|
message, |
||||
|
trigger: field.trigger, |
||||
|
validator: (_: any, value: number) => { |
||||
|
// beetween不在进行必输检查, 改为数字有效性检查
|
||||
|
if (Number.isNaN(value)) { |
||||
|
return Promise.reject(message); |
||||
|
} |
||||
|
return value < field.start || value > field.end ? Promise.reject(message) : Promise.resolve(); |
||||
|
}, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function _createRegularExpressionValidator(field: FieldRegular, required?: boolean): Rule { |
||||
|
const message = field.name |
||||
|
? L(ValidationEnum.FieldMustMatchRegularExpression, [_getFieldName(field), field.expression]) |
||||
|
: L(ValidationEnum.ThisFieldMustMatchTheRegularExpression, [field.expression]); |
||||
|
return { |
||||
|
message, |
||||
|
pattern: new RegExp(field.expression), |
||||
|
required, |
||||
|
trigger: field.trigger, |
||||
|
type: field.type, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function _createEmailValidator(field: Field, required?: boolean): Rule { |
||||
|
const message = field.name |
||||
|
? L(ValidationEnum.FieldDoNotValidEmailAddress, [_getFieldName(field)]) |
||||
|
: L(ValidationEnum.ThisFieldIsNotAValidEmailAddress); |
||||
|
return { |
||||
|
message, |
||||
|
required, |
||||
|
trigger: field.trigger, |
||||
|
type: field.type, |
||||
|
validator: (_: any, value: string) => { |
||||
|
if (!isEmail(value)) { |
||||
|
return Promise.reject(message); |
||||
|
} |
||||
|
return Promise.resolve(); |
||||
|
}, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function _createPhoneValidator(field: Field, required?: boolean): Rule { |
||||
|
const message = field.name |
||||
|
? L(ValidationEnum.FieldDoNotValidPhoneNumber, [_getFieldName(field)]) |
||||
|
: L(ValidationEnum.ThisFieldIsNotAValidPhoneNumber); |
||||
|
return { |
||||
|
message, |
||||
|
required, |
||||
|
trigger: field.trigger, |
||||
|
type: field.type, |
||||
|
validator: (_: any, value: string) => { |
||||
|
if (!isPhone(value)) { |
||||
|
return Promise.reject(message); |
||||
|
} |
||||
|
return Promise.resolve(); |
||||
|
}, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
const ruleCreator: RuleCreator = { |
||||
|
defineValidator(field: FieldDefineValidator) { |
||||
|
return _createRule(field); |
||||
|
}, |
||||
|
doNotMatch(field: FieldMatch) { |
||||
|
const message = L(ValidationEnum.DoNotMatch, [ |
||||
|
__getFieldName(field.name, field.resourceName, field.prefix), |
||||
|
__getFieldName(field.matchField, field.resourceName, field.prefix), |
||||
|
]); |
||||
|
return _createRule({ |
||||
|
message, |
||||
|
required: field.required, |
||||
|
trigger: field.trigger, |
||||
|
type: field.type, |
||||
|
validator: (_, value: string) => { |
||||
|
if (value !== field.matchValue) { |
||||
|
return Promise.reject(message); |
||||
|
} |
||||
|
return Promise.resolve(); |
||||
|
}, |
||||
|
}); |
||||
|
}, |
||||
|
fieldDoNotValidCreditCardNumber(field: Field) { |
||||
|
if (field.name) { |
||||
|
return _createRule({ |
||||
|
message: L(ValidationEnum.FieldDoNotValidCreditCardNumber, [_getFieldName(field)]), |
||||
|
trigger: field.trigger, |
||||
|
type: field.type, |
||||
|
}); |
||||
|
} |
||||
|
return _createRule({ |
||||
|
message: L(ValidationEnum.ThisFieldIsNotAValidCreditCardNumber), |
||||
|
trigger: field.trigger, |
||||
|
type: field.type, |
||||
|
}); |
||||
|
}, |
||||
|
fieldDoNotValidEmailAddress(field: Field) { |
||||
|
return [_createEmailValidator(field)]; |
||||
|
}, |
||||
|
fieldDoNotValidFullyQualifiedUrl(field: Field) { |
||||
|
if (field.name) { |
||||
|
return _createRule({ |
||||
|
message: L(ValidationEnum.FieldDoNotValidFullyQualifiedUrl, [_getFieldName(field)]), |
||||
|
trigger: field.trigger, |
||||
|
type: field.type, |
||||
|
}); |
||||
|
} |
||||
|
return _createRule({ |
||||
|
message: L(ValidationEnum.ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl), |
||||
|
trigger: field.trigger, |
||||
|
type: field.type, |
||||
|
}); |
||||
|
}, |
||||
|
fieldDoNotValidPhoneNumber(field: Field) { |
||||
|
return [_createPhoneValidator(field)]; |
||||
|
}, |
||||
|
fieldInvalid(field: FieldValidator) { |
||||
|
const message = field.name |
||||
|
? L(ValidationEnum.FieldInvalid, [_getFieldName(field)]) |
||||
|
: L(ValidationEnum.ThisFieldIsInvalid); |
||||
|
return _createRule({ |
||||
|
message, |
||||
|
required: field.required, |
||||
|
trigger: field.trigger, |
||||
|
type: field.type, |
||||
|
validator: (_, value: any) => { |
||||
|
if (!field.validator(value)) { |
||||
|
return Promise.reject(message); |
||||
|
} |
||||
|
return Promise.resolve(); |
||||
|
}, |
||||
|
}); |
||||
|
}, |
||||
|
fieldIsNotValid(field: FieldValidator) { |
||||
|
const message = field.name |
||||
|
? L(ValidationEnum.FieldIsNotValid, [_getFieldName(field)]) |
||||
|
: L(ValidationEnum.ThisFieldIsNotValid); |
||||
|
return _createRule({ |
||||
|
message, |
||||
|
required: field.required, |
||||
|
trigger: field.trigger, |
||||
|
type: field.type, |
||||
|
validator: (_, value: any) => { |
||||
|
if (field.validator(value)) { |
||||
|
return Promise.reject(message); |
||||
|
} |
||||
|
return Promise.resolve(); |
||||
|
}, |
||||
|
}); |
||||
|
}, |
||||
|
fieldMustBeetWeen(field: FieldBeetWeen) { |
||||
|
return [_createBeetWeenValidator(field)]; |
||||
|
}, |
||||
|
fieldMustBeStringOrArrayWithMaximumLength(field: FieldLength) { |
||||
|
return [ |
||||
|
_createLengthValidator( |
||||
|
field, |
||||
|
true, |
||||
|
ValidationEnum.FieldMustBeStringOrArrayWithMaximumLength, |
||||
|
ValidationEnum.ThisFieldMustBeAStringOrArrayTypeWithAMaximumLength, |
||||
|
), |
||||
|
]; |
||||
|
}, |
||||
|
fieldMustBeStringOrArrayWithMinimumLength(field: FieldLength) { |
||||
|
return [ |
||||
|
_createLengthValidator( |
||||
|
field, |
||||
|
false, |
||||
|
ValidationEnum.FieldMustBeStringOrArrayWithMinimumLength, |
||||
|
ValidationEnum.ThisFieldMustBeAStringOrArrayTypeWithAMinimumLength, |
||||
|
), |
||||
|
]; |
||||
|
}, |
||||
|
fieldMustBeStringWithMaximumLength(field: FieldLength) { |
||||
|
return [ |
||||
|
_createLengthValidator( |
||||
|
field, |
||||
|
true, |
||||
|
ValidationEnum.FieldMustBeStringWithMaximumLength, |
||||
|
ValidationEnum.ThisFieldMustBeAStringWithAMaximumLength, |
||||
|
), |
||||
|
]; |
||||
|
}, |
||||
|
fieldMustBeStringWithMinimumLengthAndMaximumLength(field: FieldRange) { |
||||
|
return [ |
||||
|
_createLengthRangValidator( |
||||
|
field, |
||||
|
ValidationEnum.FieldMustBeStringWithMinimumLengthAndMaximumLength, |
||||
|
ValidationEnum.ThisFieldMustBeAStringWithAMinimumLengthAndAMaximumLength, |
||||
|
), |
||||
|
]; |
||||
|
}, |
||||
|
fieldMustMatchRegularExpression(field: FieldRegular) { |
||||
|
return [_createRegularExpressionValidator(field)]; |
||||
|
}, |
||||
|
fieldOnlyAcceptsFilesExtensions(field: FieldContains) { |
||||
|
const message = field.name |
||||
|
? L(ValidationEnum.FieldOnlyAcceptsFilesExtensions, [_getFieldName(field), field.value]) |
||||
|
: L(ValidationEnum.ThisFieldMustMatchTheRegularExpression, [field.value]); |
||||
|
return _createRule({ |
||||
|
message, |
||||
|
trigger: field.trigger, |
||||
|
type: field.type, |
||||
|
validator: (_, value: string) => { |
||||
|
if (!field.value.includes(value)) { |
||||
|
return Promise.reject(message); |
||||
|
} |
||||
|
return Promise.resolve(); |
||||
|
}, |
||||
|
}); |
||||
|
}, |
||||
|
fieldRequired(field: Field) { |
||||
|
return [_createValidator(field, ValidationEnum.FieldRequired, ValidationEnum.ThisFieldIsRequired, true)]; |
||||
|
}, |
||||
|
mapEnumValidMessage(enumName: string, args?: any[] | Record<string, string> | undefined) { |
||||
|
return L(enumName, args); |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
return ruleCreator; |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
{ |
||||
|
"authentication": { |
||||
|
"welcomeBack": "Welcome Back", |
||||
|
"pageTitle": "Plug-and-play Admin system", |
||||
|
"pageDesc": "Efficient, versatile frontend template", |
||||
|
"loginSuccess": "Login Successful", |
||||
|
"loginSuccessDesc": "Welcome Back", |
||||
|
"loginSubtitle": "Enter your account details to manage your projects", |
||||
|
"selectAccount": "Quick Select Account", |
||||
|
"username": "Username", |
||||
|
"password": "Password", |
||||
|
"usernameTip": "Please enter username", |
||||
|
"passwordErrorTip": "Password is incorrect", |
||||
|
"passwordTip": "Please enter password", |
||||
|
"verifyRequiredTip": "Please complete the verification first", |
||||
|
"rememberMe": "Remember Me", |
||||
|
"createAnAccount": "Create an Account", |
||||
|
"createAccount": "Create Account", |
||||
|
"alreadyHaveAccount": "Already have an account?", |
||||
|
"accountTip": "Don't have an account?", |
||||
|
"signUp": "Sign Up", |
||||
|
"signUpSubtitle": "Make managing your applications simple and fun", |
||||
|
"confirmPassword": "Confirm Password", |
||||
|
"confirmPasswordTip": "The passwords do not match", |
||||
|
"agree": "I agree to", |
||||
|
"privacyPolicy": "Privacy-policy", |
||||
|
"terms": "Terms", |
||||
|
"agreeTip": "Please agree to the Privacy Policy and Terms", |
||||
|
"goToLogin": "Login instead", |
||||
|
"passwordStrength": "Use 8 or more characters with a mix of letters, numbers & symbols", |
||||
|
"forgetPassword": "Forget Password?", |
||||
|
"forgetPasswordSubtitle": "Enter your email and we'll send you instructions to reset your password", |
||||
|
"emailTip": "Please enter email", |
||||
|
"emailValidErrorTip": "The email format you entered is incorrect", |
||||
|
"sendResetLink": "Send Reset Link", |
||||
|
"email": "Email", |
||||
|
"qrcodeSubtitle": "Scan the QR code with your phone to login", |
||||
|
"qrcodePrompt": "Click 'Confirm' after scanning to complete login", |
||||
|
"qrcodeLogin": "QR Code Login", |
||||
|
"codeSubtitle": "Enter your phone number to start managing your project", |
||||
|
"code": "Security code", |
||||
|
"codeTip": "Security code required {0} characters", |
||||
|
"mobile": "Mobile", |
||||
|
"mobileLogin": "Mobile Login", |
||||
|
"mobileTip": "Please enter mobile number", |
||||
|
"mobileErrortip": "The phone number format is incorrect", |
||||
|
"sendCode": "Get Security code", |
||||
|
"sendText": "Resend in {0}s", |
||||
|
"thirdPartyLogin": "Or continue with", |
||||
|
"loginAgainTitle": "Please Log In Again", |
||||
|
"loginAgainSubTitle": "Your login session has expired. Please log in again to continue.", |
||||
|
"layout": { |
||||
|
"center": "Align Center", |
||||
|
"alignLeft": "Align Left", |
||||
|
"alignRight": "Align Right" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
{ |
||||
|
"workbench": { |
||||
|
"header": { |
||||
|
"welcome": { |
||||
|
"atoon": "Good afternoon, {0}, pay attention to rest oh~", |
||||
|
"afternoon": "Good afternoon, {0}, relax in time, can improve work efficiency~", |
||||
|
"evening": "Good evening, {0}. Still at work? The off work~", |
||||
|
"morning": "Good morning, {0}. Begin your day~" |
||||
|
}, |
||||
|
"notifier": { |
||||
|
"title": "Notifier", |
||||
|
"count": "({0})" |
||||
|
} |
||||
|
}, |
||||
|
"content": { |
||||
|
"favoriteMenu": { |
||||
|
"title": "Favorite Menus", |
||||
|
"home": "Home", |
||||
|
"dashboard": "Dashboard", |
||||
|
"profile": "Personal Profile", |
||||
|
"settings": "Personal Settings", |
||||
|
"notifiers": "Notifiers", |
||||
|
"manage": "Manage menu", |
||||
|
"create": "New menu", |
||||
|
"delete": "Delete Menu", |
||||
|
"select": "Select Menu", |
||||
|
"color": "Select Color", |
||||
|
"alias": "Alias Name", |
||||
|
"icon": "Icon" |
||||
|
}, |
||||
|
"trends": { |
||||
|
"title": "Latest News" |
||||
|
}, |
||||
|
"todo": { |
||||
|
"title": "Todo List" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
{ |
||||
|
"authentication": { |
||||
|
"welcomeBack": "欢迎回来", |
||||
|
"pageTitle": "开箱即用的大型中后台管理系统", |
||||
|
"pageDesc": "工程化、高性能、跨组件库的前端模版", |
||||
|
"loginSuccess": "登录成功", |
||||
|
"loginSuccessDesc": "欢迎回来", |
||||
|
"loginSubtitle": "请输入您的帐户信息以开始管理您的项目", |
||||
|
"selectAccount": "快速选择账号", |
||||
|
"username": "账号", |
||||
|
"password": "密码", |
||||
|
"usernameTip": "请输入用户名", |
||||
|
"passwordTip": "请输入密码", |
||||
|
"verifyRequiredTip": "请先完成验证", |
||||
|
"passwordErrorTip": "密码错误", |
||||
|
"rememberMe": "记住账号", |
||||
|
"createAnAccount": "创建一个账号", |
||||
|
"createAccount": "创建账号", |
||||
|
"alreadyHaveAccount": "已经有账号了?", |
||||
|
"accountTip": "还没有账号?", |
||||
|
"signUp": "注册", |
||||
|
"signUpSubtitle": "让您的应用程序管理变得简单而有趣", |
||||
|
"confirmPassword": "确认密码", |
||||
|
"confirmPasswordTip": "两次输入的密码不一致", |
||||
|
"agree": "我同意", |
||||
|
"privacyPolicy": "隐私政策", |
||||
|
"terms": "条款", |
||||
|
"agreeTip": "请同意隐私政策和条款", |
||||
|
"goToLogin": "去登录", |
||||
|
"passwordStrength": "使用 8 个或更多字符,混合字母、数字和符号", |
||||
|
"forgetPassword": "忘记密码?", |
||||
|
"forgetPasswordSubtitle": "输入您的电子邮件,我们将向您发送重置密码的连接", |
||||
|
"emailTip": "请输入邮箱", |
||||
|
"emailValidErrorTip": "你输入的邮箱格式不正确", |
||||
|
"sendResetLink": "发送重置链接", |
||||
|
"email": "邮箱", |
||||
|
"qrcodeSubtitle": "请用手机扫描二维码登录", |
||||
|
"qrcodePrompt": "扫码后点击 '确认',即可完成登录", |
||||
|
"qrcodeLogin": "扫码登录", |
||||
|
"codeSubtitle": "请输入您的手机号码以开始管理您的项目", |
||||
|
"code": "验证码", |
||||
|
"codeTip": "请输入{0}位验证码", |
||||
|
"mobile": "手机号码", |
||||
|
"mobileTip": "请输入手机号", |
||||
|
"mobileErrortip": "手机号码格式错误", |
||||
|
"mobileLogin": "手机号登录", |
||||
|
"sendCode": "获取验证码", |
||||
|
"sendText": "{0}秒后重新获取", |
||||
|
"thirdPartyLogin": "其他登录方式", |
||||
|
"loginAgainTitle": "重新登录", |
||||
|
"loginAgainSubTitle": "您的登录状态已过期,请重新登录以继续。", |
||||
|
"layout": { |
||||
|
"center": "居中", |
||||
|
"alignLeft": "居左", |
||||
|
"alignRight": "居右" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue