255 changed files with 21197 additions and 340 deletions
@ -1,12 +1,25 @@ |
|||
VITE_APP_BASE_API= |
|||
VITE_APP_HOMEPAGE=/dashboard/workbench |
|||
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_SECRET='' |
|||
# 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_SECRET=1q2w3e* |
|||
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 { 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 [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 ( |
|||
<Card bordered={false} title={$t("abp.account.settings.noticeSettings")}> |
|||
<Empty /> |
|||
<Card title={$t("abp.account.settings.noticeSettings")} bordered={false}> |
|||
<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> |
|||
); |
|||
}; |
|||
|
|||
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