Browse Source

Merge pull request #1115 from colinin/vben5-saas

feat: add saas package.
pull/1124/head
yx lin 1 year ago
committed by GitHub
parent
commit
14999a683e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      apps/vben5/apps/app-antd/package.json
  2. 5
      apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json
  3. 5
      apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json
  4. 28
      apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts
  5. 15
      apps/vben5/apps/app-antd/src/views/saas/editions/index.vue
  6. 15
      apps/vben5/apps/app-antd/src/views/saas/tenants/index.vue
  7. 55
      apps/vben5/packages/@abp/core/src/types/dto.ts
  8. 8
      apps/vben5/packages/@abp/identity/src/types/users.ts
  9. 2
      apps/vben5/packages/@abp/permissions/src/components/definitions/permissions/PermissionDefinitionTable.vue
  10. 10
      apps/vben5/packages/@abp/request/src/hooks/useWrapperResult.ts
  11. 42
      apps/vben5/packages/@abp/saas/package.json
  12. 2
      apps/vben5/packages/@abp/saas/src/api/index.ts
  13. 84
      apps/vben5/packages/@abp/saas/src/api/useEditionsApi.ts
  14. 154
      apps/vben5/packages/@abp/saas/src/api/useTenantsApi.ts
  15. 102
      apps/vben5/packages/@abp/saas/src/components/editions/EditionModal.vue
  16. 215
      apps/vben5/packages/@abp/saas/src/components/editions/EditionTable.vue
  17. 2
      apps/vben5/packages/@abp/saas/src/components/index.ts
  18. 76
      apps/vben5/packages/@abp/saas/src/components/tenants/ConnectionStringModal.vue
  19. 114
      apps/vben5/packages/@abp/saas/src/components/tenants/ConnectionStringTable.vue
  20. 71
      apps/vben5/packages/@abp/saas/src/components/tenants/ConnectionStringsModal.vue
  21. 306
      apps/vben5/packages/@abp/saas/src/components/tenants/TenantModal.vue
  22. 258
      apps/vben5/packages/@abp/saas/src/components/tenants/TenantTable.vue
  23. 1
      apps/vben5/packages/@abp/saas/src/constants/index.ts
  24. 28
      apps/vben5/packages/@abp/saas/src/constants/permissions.ts
  25. 4
      apps/vben5/packages/@abp/saas/src/index.ts
  26. 34
      apps/vben5/packages/@abp/saas/src/types/editions.ts
  27. 2
      apps/vben5/packages/@abp/saas/src/types/index.ts
  28. 68
      apps/vben5/packages/@abp/saas/src/types/tenants.ts
  29. 6
      apps/vben5/packages/@abp/saas/tsconfig.json

1
apps/vben5/apps/app-antd/package.json

@ -35,6 +35,7 @@
"@abp/permissions": "workspace:*",
"@abp/platform": "workspace:*",
"@abp/request": "workspace:*",
"@abp/saas": "workspace:*",
"@abp/settings": "workspace:*",
"@abp/ui": "workspace:*",
"@vben/access": "workspace:*",

5
apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json

@ -76,5 +76,10 @@
"email": "Email Messages",
"sms": "Sms Messages"
}
},
"saas": {
"title": "Saas",
"editions": "Editions",
"tenants": "Tenants"
}
}

5
apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json

@ -76,5 +76,10 @@
"email": "邮件消息",
"sms": "短信消息"
}
},
"saas": {
"title": "Saas",
"editions": "版本管理",
"tenants": "租户管理"
}
}

28
apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts

@ -177,6 +177,34 @@ const routes: RouteRecordRaw[] = [
},
],
},
{
meta: {
title: $t('abp.saas.title'),
icon: 'ant-design:cloud-server-outlined',
},
name: 'Saas',
path: '/saas',
children: [
{
meta: {
title: $t('abp.saas.editions'),
icon: 'icon-park-outline:multi-rectangle',
},
name: 'SaasEditions',
path: '/saas/editions',
component: () => import('#/views/saas/editions/index.vue'),
},
{
meta: {
title: $t('abp.saas.tenants'),
icon: 'arcticons:tenantcloud-pro',
},
name: 'SaasTenants',
path: '/saas/tenants',
component: () => import('#/views/saas/tenants/index.vue'),
},
],
},
{
meta: {
title: $t('abp.openiddict.title'),

15
apps/vben5/apps/app-antd/src/views/saas/editions/index.vue

@ -0,0 +1,15 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { EditionTable } from '@abp/saas';
defineOptions({
name: 'SaasEditions',
});
</script>
<template>
<Page>
<EditionTable />
</Page>
</template>

15
apps/vben5/apps/app-antd/src/views/saas/tenants/index.vue

@ -0,0 +1,15 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { TenantTable } from '@abp/saas';
defineOptions({
name: 'SaasTenants',
});
</script>
<template>
<Page>
<TenantTable />
</Page>
</template>

55
apps/vben5/packages/@abp/core/src/types/dto.ts

@ -1,15 +1,15 @@
import {
type Clock,
type CurrentCulture,
type CurrentTenant,
type CurrentUser,
type Dictionary,
type ExtraPropertyDictionary,
type IHasExtraProperties,
type LanguageInfo,
type MultiTenancyInfo,
type NameValue,
type TimeZone,
import type {
Clock,
CurrentCulture,
CurrentTenant,
CurrentUser,
Dictionary,
ExtraPropertyDictionary,
IHasExtraProperties,
LanguageInfo,
MultiTenancyInfo,
NameValue,
TimeZone,
} from './global';
/** 包装器数据传输对象 */
interface WrapResult<T> {
@ -47,6 +47,7 @@ interface RemoteServiceErrorInfo {
}
/** 扩展属性数据传输对象 */
interface ExtensibleObject {
[key: string]: any;
/** 扩展属性 */
extraProperties: ExtraPropertyDictionary;
}
@ -102,7 +103,7 @@ interface FullAuditedEntityWithUserDto<TPrimaryKey, TUserDto>
deleter: TUserDto;
}
/** 实体扩展属性数据传输对象 */
interface ExtensibleEntityDto<TKey> extends ExtensibleObject, EntityDto<TKey> {}
interface ExtensibleEntityDto<TKey> extends EntityDto<TKey>, ExtensibleObject {}
/** 实体新增扩展属性数据传输对象 */
interface ExtensibleCreationAuditedEntityDto<TPrimaryKey>
extends CreationAuditedEntityDto<TPrimaryKey>,
@ -121,12 +122,12 @@ interface ExtensibleAuditedEntityWithUserDto<TPrimaryKey, TUserDto>
ExtensibleEntityDto<TPrimaryKey> {}
/** 实体审计全属性扩展数据传输对象 */
interface ExtensibleFullAuditedEntityDto<TPrimaryKey>
extends FullAuditedEntityDto<TPrimaryKey>,
ExtensibleEntityDto<TPrimaryKey> {}
extends ExtensibleEntityDto<TPrimaryKey>,
FullAuditedEntityDto<TPrimaryKey> {}
/** 实体审计用户全属性扩展数据传输对象 */
interface ExtensibleFullAuditedEntityWithUserDto<TPrimaryKey, TUserDto>
extends FullAuditedEntityWithUserDto<TPrimaryKey, TUserDto>,
ExtensibleEntityDto<TPrimaryKey> {}
extends ExtensibleEntityDto<TPrimaryKey>,
FullAuditedEntityWithUserDto<TPrimaryKey, TUserDto> {}
/** 最大请求数据传输对象 */
interface LimitedResultRequestDto {
/** 最大返回数据大小 */
@ -134,8 +135,8 @@ interface LimitedResultRequestDto {
}
/** 最大请求扩展数据传输对象 */
interface ExtensibleLimitedResultRequestDto
extends LimitedResultRequestDto,
ExtensibleObject {}
extends ExtensibleObject,
LimitedResultRequestDto {}
/** 排序请求数据传输对象 */
interface SortedResultRequest {
/**
@ -160,8 +161,8 @@ interface PagedAndSortedResultRequestDto
SortedResultRequest {}
/** 分页排序请求扩展数据传输对象 */
interface ExtensiblePagedAndSortedResultRequestDto
extends PagedAndSortedResultRequestDto,
ExtensibleObject {}
extends ExtensibleObject,
PagedAndSortedResultRequestDto {}
/** 列表数据传输对象 */
interface ListResultDto<T> {
/** 返回项目列表 */
@ -169,8 +170,8 @@ interface ListResultDto<T> {
}
/** 列表扩展数据传输对象 */
interface ExtensibleListResultDto<T>
extends ListResultDto<T>,
ExtensibleObject {}
extends ExtensibleObject,
ListResultDto<T> {}
/** 分页列表数据传输对象 */
interface PagedResultDto<T> extends ListResultDto<T> {
/** 符合条件的最大数量 */
@ -178,12 +179,12 @@ interface PagedResultDto<T> extends ListResultDto<T> {
}
/** 分页列表扩展数据传输对象 */
interface ExtensiblePagedResultDto<T>
extends PagedResultDto<T>,
ExtensibleObject {}
extends ExtensibleObject,
PagedResultDto<T> {}
/** 分页列表扩展数据传输对象 */
interface ExtensiblePagedResultRequestDto
extends PagedResultRequestDto,
ExtensibleObject {}
extends ExtensibleObject,
PagedResultRequestDto {}
/** 应用程序本地化资源数据传输对象 */
interface ApplicationLocalizationResourceDto {
/** 继承资源名称列表 */

8
apps/vben5/packages/@abp/identity/src/types/users.ts

@ -45,9 +45,9 @@ interface IdentityUserOrganizationUnitUpdateDto {
/** 用户实体数据传输对象 */
interface IdentityUserDto
extends FullAuditedEntityDto<string>,
IUser,
IHasConcurrencyStamp,
IHasExtraProperties {
IHasExtraProperties,
IUser {
[key: string]: any;
/** 邮箱已验证 */
emailConfirmed: boolean;
@ -95,8 +95,8 @@ interface UserLookupCountInput {
}
interface UserLookupSearchInput
extends UserLookupCountInput,
PagedAndSortedResultRequestDto {}
extends PagedAndSortedResultRequestDto,
UserLookupCountInput {}
export type {
ChangeMyPasswordInput,

2
apps/vben5/packages/@abp/permissions/src/components/definitions/permissions/PermissionDefinitionTable.vue

@ -1,6 +1,7 @@
<script setup lang="ts">
import type { VbenFormProps, VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { PermissionDefinitionDto } from '../../../types/definitions';
import type { PermissionGroupDefinitionDto } from '../../../types/groups';
import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue';
@ -25,7 +26,6 @@ import { VxeGrid } from 'vxe-table';
import { usePermissionDefinitionsApi } from '../../../api/usePermissionDefinitionsApi';
import { usePermissionGroupDefinitionsApi } from '../../../api/usePermissionGroupDefinitionsApi';
import { GroupDefinitionsPermissions } from '../../../constants/permissions';
import { type PermissionDefinitionDto } from '../../../types/definitions';
import { useTypesMap } from './types';
defineOptions({

10
apps/vben5/packages/@abp/request/src/hooks/useWrapperResult.ts

@ -24,7 +24,15 @@ export function useWrapperResult(response: AxiosResponse) {
const hasSuccess = data && Reflect.has(data, 'code') && code === '0';
if (!hasSuccess) {
const content = details || message;
throw Object.assign({}, response, { message: content, response });
throw Object.assign({}, response, {
response: {
...response,
data: {
...response.data,
message: content,
},
},
});
}
}

42
apps/vben5/packages/@abp/saas/package.json

@ -0,0 +1,42 @@
{
"name": "@abp/saas",
"version": "9.0.4",
"homepage": "https://github.com/colinin/abp-next-admin",
"bugs": "https://github.com/colinin/abp-next-admin/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/colinin/abp-next-admin.git",
"directory": "packages/@abp/saas"
},
"license": "MIT",
"type": "module",
"sideEffects": [
"**/*.css"
],
"exports": {
".": {
"types": "./src/index.ts",
"default": "./src/index.ts"
}
},
"dependencies": {
"@abp/auditing": "workspace:*",
"@abp/core": "workspace:*",
"@abp/request": "workspace:*",
"@abp/ui": "workspace:*",
"@ant-design/icons-vue": "catalog:",
"@vben/access": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/hooks": "workspace:*",
"@vben/icons": "workspace:*",
"@vben/locales": "workspace:*",
"ant-design-vue": "catalog:",
"dayjs": "catalog:",
"lodash.debounce": "catalog:",
"vue": "catalog:*",
"vxe-table": "catalog:"
},
"devDependencies": {
"@types/lodash.debounce": "catalog:"
}
}

2
apps/vben5/packages/@abp/saas/src/api/index.ts

@ -0,0 +1,2 @@
export * from './useEditionsApi';
export * from './useTenantsApi';

84
apps/vben5/packages/@abp/saas/src/api/useEditionsApi.ts

@ -0,0 +1,84 @@
import type { PagedResultDto } from '@abp/core';
import type {
EditionCreateDto,
EditionDto,
EditionUpdateDto,
GetEditionPagedListInput,
} from '../types';
import { useRequest } from '@abp/request';
export function useEditionsApi() {
const { cancel, request } = useRequest();
/**
*
* @param {EditionCreateDto} input
* @returns
*/
function createApi(input: EditionCreateDto): Promise<EditionDto> {
return request<EditionDto>('/api/saas/editions', {
data: input,
method: 'POST',
});
}
/**
*
* @param {string} id
* @param {EditionUpdateDto} input
* @returns
*/
function updateApi(id: string, input: EditionUpdateDto): Promise<EditionDto> {
return request<EditionDto>(`/api/saas/editions/${id}`, {
data: input,
method: 'PUT',
});
}
/**
*
* @param {string} id Id
* @returns
*/
function getApi(id: string): Promise<EditionDto> {
return request<EditionDto>(`/api/saas/editions/${id}`, {
method: 'GET',
});
}
/**
*
* @param {string} id Id
* @returns {void}
*/
function deleteApi(id: string): Promise<void> {
return request(`/api/saas/editions/${id}`, {
method: 'DELETE',
});
}
/**
*
* @param {GetEditionPagedListInput} input
* @returns {void}
*/
function getPagedListApi(
input?: GetEditionPagedListInput,
): Promise<PagedResultDto<EditionDto>> {
return request<PagedResultDto<EditionDto>>(`/api/saas/editions`, {
method: 'GET',
params: input,
});
}
return {
cancel,
createApi,
deleteApi,
getApi,
getPagedListApi,
updateApi,
};
}

154
apps/vben5/packages/@abp/saas/src/api/useTenantsApi.ts

@ -0,0 +1,154 @@
import type { ListResultDto, PagedResultDto } from '@abp/core';
import type {
GetTenantPagedListInput,
TenantConnectionStringDto,
TenantConnectionStringSetInput,
TenantCreateDto,
TenantDto,
TenantUpdateDto,
} from '../types';
import { useRequest } from '@abp/request';
export function useTenantsApi() {
const { cancel, request } = useRequest();
/**
*
* @param {TenantCreateDto} input
* @returns
*/
function createApi(input: TenantCreateDto): Promise<TenantDto> {
return request<TenantDto>('/api/saas/tenants', {
data: input,
method: 'POST',
});
}
/**
*
* @param {string} id
* @param {TenantUpdateDto} input
* @returns
*/
function updateApi(id: string, input: TenantUpdateDto): Promise<TenantDto> {
return request<TenantDto>(`/api/saas/tenants/${id}`, {
data: input,
method: 'PUT',
});
}
/**
*
* @param {string} id Id
* @returns
*/
function getApi(id: string): Promise<TenantDto> {
return request<TenantDto>(`/api/saas/tenants/${id}`, {
method: 'GET',
});
}
/**
*
* @param {string} id Id
* @returns {void}
*/
function deleteApi(id: string): Promise<void> {
return request(`/api/saas/tenants/${id}`, {
method: 'DELETE',
});
}
/**
*
* @param {GetTenantPagedListInput} input
* @returns {void}
*/
function getPagedListApi(
input?: GetTenantPagedListInput,
): Promise<PagedResultDto<TenantDto>> {
return request<PagedResultDto<TenantDto>>(`/api/saas/tenants`, {
method: 'GET',
params: input,
});
}
/**
*
* @param {string} id Id
* @param {TenantConnectionStringSetInput} input
* @returns
*/
function setConnectionStringApi(
id: string,
input: TenantConnectionStringSetInput,
): Promise<TenantConnectionStringDto> {
return request<TenantConnectionStringDto>(
`/api/saas/tenants/${id}/connection-string`,
{
data: input,
method: 'PUT',
},
);
}
/**
*
* @param {string} id Id
* @param {string} name
* @returns
*/
function getConnectionStringApi(
id: string,
name: string,
): Promise<TenantConnectionStringDto> {
return request<TenantConnectionStringDto>(
`/api/saas/tenants/${id}/connection-string/${name}`,
{
method: 'GET',
},
);
}
/**
*
* @param {string} id Id
* @returns
*/
function getConnectionStringsApi(
id: string,
): Promise<ListResultDto<TenantConnectionStringDto>> {
return request<ListResultDto<TenantConnectionStringDto>>(
`/api/saas/tenants/${id}/connection-string`,
{
method: 'GET',
},
);
}
/**
*
* @param {string} id Id
* @param {string} name
* @returns {void}
*/
function deleteConnectionStringApi(id: string, name: string): Promise<void> {
return request(`/api/saas/tenants/${id}/connection-string/${name}`, {
method: 'DELETE',
});
}
return {
cancel,
createApi,
deleteApi,
deleteConnectionStringApi,
getApi,
getConnectionStringApi,
getConnectionStringsApi,
getPagedListApi,
setConnectionStringApi,
updateApi,
};
}

102
apps/vben5/packages/@abp/saas/src/components/editions/EditionModal.vue

@ -0,0 +1,102 @@
<script setup lang="ts">
import type { EditionDto } from '../../types';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { useVbenForm } from '@abp/ui';
import { message } from 'ant-design-vue';
import { useEditionsApi } from '../../api';
const emits = defineEmits<{
(event: 'change', val: EditionDto): void;
}>();
const edition = ref<EditionDto>();
const { cancel, createApi, getApi, updateApi } = useEditionsApi();
const [Form, formApi] = useVbenForm({
async handleSubmit(values) {
await onSubmit(values);
},
schema: [
{
component: 'Input',
componentProps: {
allowClear: true,
autocomplete: 'off',
},
fieldName: 'displayName',
label: $t('AbpSaas.DisplayName:EditionName'),
rules: 'required',
},
],
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
onClosed: cancel,
async onConfirm() {
await formApi.validateAndSubmitForm();
},
async onOpenChange(isOpen) {
if (isOpen) {
await onGet();
}
},
title: $t('AbpSaas.Editions'),
});
async function onGet() {
const { id } = modalApi.getData<EditionDto>();
if (!id) {
formApi.setValues({});
edition.value = undefined;
modalApi.setState({ title: $t('AbpSaas.NewEdition') });
return;
}
try {
modalApi.setState({ loading: true });
const editionDto = await getApi(id);
modalApi.setState({
title: `${$t('AbpSaas.Editions')} - ${editionDto.displayName}`,
});
formApi.setValues(editionDto);
edition.value = editionDto;
} finally {
modalApi.setState({ loading: false });
}
}
async function onSubmit(values: Record<string, any>) {
const api = edition.value?.id
? updateApi(edition.value!.id, {
concurrencyStamp: values.concurrencyStamp,
displayName: values.displayName,
})
: createApi({
displayName: values.displayName,
});
try {
modalApi.setState({ submitting: true });
const dto = await api;
message.success($t('AbpUi.SavedSuccessfully'));
emits('change', dto);
modalApi.close();
} finally {
modalApi.setState({ submitting: false });
}
}
</script>
<template>
<Modal>
<Form />
</Modal>
</template>
<style scoped></style>

215
apps/vben5/packages/@abp/saas/src/components/editions/EditionTable.vue

@ -0,0 +1,215 @@
<script setup lang="ts">
import type { VbenFormProps, VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import type { EditionDto } from '../../types/editions';
import { defineAsyncComponent, h } from 'vue';
import { useAccess } from '@vben/access';
import { useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { AuditLogPermissions, EntityChangeDrawer } from '@abp/auditing';
import { useFeatures } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import {
DeleteOutlined,
EditOutlined,
EllipsisOutlined,
} from '@ant-design/icons-vue';
import { Button, Dropdown, Menu, message, Modal } from 'ant-design-vue';
import { useEditionsApi } from '../../api/useEditionsApi';
import { EditionsPermissions } from '../../constants/permissions';
defineOptions({
name: 'EditionTable',
});
const MenuItem = Menu.Item;
const AuditLogIcon = createIconifyIcon('fluent-mdl2:compliance-audit');
const { isEnabled } = useFeatures();
const { hasAccessByCodes } = useAccess();
const { cancel, deleteApi, getPagedListApi } = useEditionsApi();
const formOptions: VbenFormProps = {
//
collapsed: false,
schema: [
{
component: 'Input',
componentProps: {
allowClear: true,
autocomplete: 'off',
},
fieldName: 'filter',
formItemClass: 'col-span-2 items-baseline',
label: $t('AbpUi.Search'),
},
],
//
showCollapseButton: true,
//
submitOnEnter: true,
};
const gridOptions: VxeGridProps<EditionDto> = {
columns: [
{
align: 'left',
field: 'displayName',
title: $t('AbpSaas.DisplayName:EditionName'),
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: $t('AbpUi.Actions'),
visible: hasAccessByCodes([
EditionsPermissions.Update,
EditionsPermissions.Delete,
]),
width: 220,
},
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getPagedListApi({
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
zoom: true,
},
};
const gridEvents: VxeGridListeners<EditionDto> = {
cellClick: () => {},
};
const [EditionModal, modalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(() => import('./EditionModal.vue')),
});
const [Grid, { query }] = useVbenVxeGrid({
formOptions,
gridEvents,
gridOptions,
});
const [EditionChangeDrawer, entityChangeDrawerApi] = useVbenDrawer({
connectedComponent: EntityChangeDrawer,
});
const onCreate = () => {
modalApi.setData({});
modalApi.open();
};
const onUpdate = (row: EditionDto) => {
modalApi.setData(row);
modalApi.open();
};
const onDelete = (row: EditionDto) => {
Modal.confirm({
centered: true,
content: $t('AbpSaas.EditionDeletionConfirmationMessage', [
row.displayName,
]),
onCancel: () => {
cancel();
},
onOk: async () => {
await deleteApi(row.id);
message.success($t('AbpUi.SuccessfullyDeleted'));
query();
},
title: $t('AbpUi.AreYouSure'),
});
};
const onMenuClick = (row: EditionDto, info: MenuInfo) => {
switch (info.key) {
case 'entity-changes': {
entityChangeDrawerApi.setData({
entityId: row.id,
entityTypeFullName: 'LINGYUN.Abp.Saas.Edition',
subject: row.displayName,
});
entityChangeDrawerApi.open();
break;
}
}
};
</script>
<template>
<Grid :table-title="$t('AbpSaas.Editions')">
<template #toolbar-tools>
<Button
type="primary"
v-access:code="[EditionsPermissions.Create]"
@click="onCreate"
>
{{ $t('AbpSaas.NewEdition') }}
</Button>
</template>
<template #action="{ row }">
<div class="flex flex-row">
<Button
:icon="h(EditOutlined)"
block
type="link"
v-access:code="[EditionsPermissions.Update]"
@click="onUpdate(row)"
>
{{ $t('AbpUi.Edit') }}
</Button>
<Button
:icon="h(DeleteOutlined)"
block
danger
type="link"
v-access:code="[EditionsPermissions.Delete]"
@click="onDelete(row)"
>
{{ $t('AbpUi.Delete') }}
</Button>
<Dropdown v-if="isEnabled('AbpAuditing.Logging.AuditLog')">
<template #overlay>
<Menu @click="(info) => onMenuClick(row, info)">
<MenuItem
v-if="hasAccessByCodes([AuditLogPermissions.Default])"
key="entity-changes"
:icon="h(AuditLogIcon)"
>
{{ $t('AbpAuditLogging.EntitiesChanged') }}
</MenuItem>
</Menu>
</template>
<Button :icon="h(EllipsisOutlined)" type="link" />
</Dropdown>
</div>
</template>
</Grid>
<EditionModal @change="() => query()" />
<EditionChangeDrawer />
</template>
<style lang="scss" scoped></style>

2
apps/vben5/packages/@abp/saas/src/components/index.ts

@ -0,0 +1,2 @@
export { default as EditionTable } from './editions/EditionTable.vue';
export { default as TenantTable } from './tenants/TenantTable.vue';

76
apps/vben5/packages/@abp/saas/src/components/tenants/ConnectionStringModal.vue

@ -0,0 +1,76 @@
<script setup lang="ts">
import type { FormExpose } from 'ant-design-vue/es/form/Form';
import type { TenantConnectionStringDto } from '../../types';
import { ref, toValue, useTemplateRef } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { Form, Input, Textarea } from 'ant-design-vue';
const props = defineProps<{
submit?: (data: TenantConnectionStringDto) => Promise<void>;
}>();
const FormItem = Form.Item;
const isEditModal = ref(false);
const form = useTemplateRef<FormExpose>('form');
const formModel = ref({} as TenantConnectionStringDto);
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
await form.value?.validate();
onSubmit();
},
onOpenChange(isOpen) {
isEditModal.value = false;
let title = $t('AbpSaas.ConnectionStrings');
if (isOpen) {
form.value?.resetFields();
const dto = modalApi.getData<TenantConnectionStringDto>();
formModel.value = { ...dto };
if (dto.name) {
isEditModal.value = true;
title = `${$t('AbpSaas.ConnectionStrings')} - ${dto.name}`;
}
}
modalApi.setState({ title });
},
title: $t('AbpSaas.ConnectionStrings'),
});
async function onSubmit() {
modalApi.setState({ submitting: true });
try {
props.submit && (await props.submit(toValue(formModel)));
modalApi.close();
} finally {
modalApi.setState({ submitting: false });
}
}
</script>
<template>
<Modal>
<Form
ref="form"
:label-col="{ span: 4 }"
:wapper-col="{ span: 20 }"
:model="formModel"
>
<FormItem required name="name" :label="$t('AbpSaas.DisplayName:Name')">
<Input
:disabled="isEditModal"
autocomplete="off"
v-model:value="formModel.name"
/>
</FormItem>
<FormItem required name="value" :label="$t('AbpSaas.DisplayName:Value')">
<Textarea :auto-size="{ minRows: 3 }" v-model:value="formModel.value" />
</FormItem>
</Form>
</Modal>
</template>
<style scoped></style>

114
apps/vben5/packages/@abp/saas/src/components/tenants/ConnectionStringTable.vue

@ -0,0 +1,114 @@
<script setup lang="ts">
import type { VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { TenantConnectionStringDto } from '../../types/tenants';
import { defineAsyncComponent, h } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
import { Button, Popconfirm } from 'ant-design-vue';
import { VxeGrid } from 'vxe-table';
const props = defineProps<{
connectionStrings: TenantConnectionStringDto[];
delete?: (data: TenantConnectionStringDto) => Promise<void>;
submit?: (data: TenantConnectionStringDto) => Promise<void>;
}>();
const [ConnectionStringModal, connectionModalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(
() => import('./ConnectionStringModal.vue'),
),
});
const gridOptions: VxeGridProps<TenantConnectionStringDto> = {
columns: [
{
align: 'left',
field: 'name',
title: $t('AbpSaas.DisplayName:Name'),
width: 150,
},
{
align: 'left',
field: 'value',
title: $t('AbpSaas.DisplayName:Value'),
},
{
align: 'center',
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: $t('AbpUi.Actions'),
width: 180,
},
],
exportConfig: {},
keepSource: true,
toolbarConfig: {
buttons: [
{
code: 'add',
icon: 'vxe-icon-add',
name: $t('AbpSaas.ConnectionStrings:AddNew'),
status: 'primary',
},
],
},
};
const gridEvents: VxeGridListeners<TenantConnectionStringDto> = {
toolbarButtonClick(params) {
if (params.code === 'add') {
handleCreate();
}
},
};
function handleCreate() {
connectionModalApi.setData({});
connectionModalApi.open();
}
function handleUpdate(row: TenantConnectionStringDto) {
connectionModalApi.setData(row);
connectionModalApi.open();
}
async function onDelete(row: TenantConnectionStringDto) {
props.delete && (await props.delete(row));
}
</script>
<template>
<div>
<VxeGrid v-bind="gridOptions" v-on="gridEvents" :data="connectionStrings">
<template #action="{ row }">
<div class="flex flex-row">
<Button
:icon="h(EditOutlined)"
block
type="link"
@click="handleUpdate(row)"
>
{{ $t('AbpUi.Edit') }}
</Button>
<Popconfirm
:title="$t('AbpUi.AreYouSure')"
trigger="click"
@confirm="onDelete(row)"
>
<template #description>
<span>{{
$t('AbpUi.ItemWillBeDeletedMessageWithFormat', [row.name])
}}</span>
</template>
<Button :icon="h(DeleteOutlined)" block danger type="link">
{{ $t('AbpUi.Delete') }}
</Button>
</Popconfirm>
</div>
</template>
</VxeGrid>
<ConnectionStringModal :submit="submit" />
</div>
</template>
<style scoped></style>

71
apps/vben5/packages/@abp/saas/src/components/tenants/ConnectionStringsModal.vue

@ -0,0 +1,71 @@
<script setup lang="ts">
import type { TenantConnectionStringDto, TenantDto } from '../../types';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { message } from 'ant-design-vue';
import { useTenantsApi } from '../../api/useTenantsApi';
import ConnectionStringTable from './ConnectionStringTable.vue';
const connectionStrings = ref<TenantConnectionStringDto[]>([]);
const {
cancel,
deleteConnectionStringApi,
getConnectionStringsApi,
setConnectionStringApi,
} = useTenantsApi();
const [Modal, modalApi] = useVbenModal({
class: 'w-[800px]',
onClosed: cancel,
async onOpenChange(isOpen) {
connectionStrings.value = [];
if (isOpen) {
const dto = modalApi.getData<TenantDto>();
await onGet(dto.id);
}
},
});
async function onGet(id: string) {
const { items } = await getConnectionStringsApi(id);
connectionStrings.value = items;
}
async function onChange(data: TenantConnectionStringDto) {
const dto = modalApi.getData<TenantDto>();
try {
modalApi.setState({ submitting: true });
await setConnectionStringApi(dto.id, data);
message.success($t('AbpUi.SavedSuccessfully'));
await onGet(dto.id);
} finally {
modalApi.setState({ submitting: false });
}
}
async function onDelete(data: TenantConnectionStringDto) {
const dto = modalApi.getData<TenantDto>();
try {
modalApi.setState({ submitting: true });
await deleteConnectionStringApi(dto.id, data.name);
message.success($t('AbpUi.DeletedSuccessfully'));
await onGet(dto.id);
} finally {
modalApi.setState({ submitting: false });
}
}
</script>
<template>
<Modal :title="$t('AbpSaas.ConnectionStrings')">
<ConnectionStringTable
:connection-strings="connectionStrings"
:delete="onDelete"
:submit="onChange"
/>
</Modal>
</template>
<style scoped></style>

306
apps/vben5/packages/@abp/saas/src/components/tenants/TenantModal.vue

@ -0,0 +1,306 @@
<script setup lang="ts">
import type { FormExpose } from 'ant-design-vue/es/form/Form';
import type { EditionDto } from '../../types';
import type {
TenantConnectionStringDto,
TenantCreateDto,
TenantDto,
TenantUpdateDto,
} from '../../types/tenants';
import { computed, onMounted, ref, useTemplateRef } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { useValidation } from '@abp/core';
import {
Checkbox,
DatePicker,
Form,
Input,
InputPassword,
message,
Select,
Textarea,
} from 'ant-design-vue';
import dayjs from 'dayjs';
import debounce from 'lodash.debounce';
import { useEditionsApi } from '../../api/useEditionsApi';
import { useTenantsApi } from '../../api/useTenantsApi';
import ConnectionStringTable from './ConnectionStringTable.vue';
const emits = defineEmits<{
(event: 'change', val: TenantDto): void;
}>();
const FormItem = Form.Item;
const defaultModel = {
connectionStrings: [],
isActive: true,
useSharedDatabase: true,
} as unknown as TenantDto;
const { fieldDoNotValidEmailAddress, fieldRequired } = useValidation();
const form = useTemplateRef<FormExpose>('form');
const tenant = ref({ ...defaultModel });
const editions = ref<EditionDto[]>([]);
const activeTabKey = ref('basic');
const getFormRules = computed(() => {
return {
adminEmailAddress: [
...fieldRequired({
name: 'AdminEmailAddress',
prefix: 'DisplayName',
resourceName: 'AbpSaas',
}),
...fieldDoNotValidEmailAddress({
name: 'AdminEmailAddress',
prefix: 'DisplayName',
resourceName: 'AbpSaas',
}),
],
adminPassword: fieldRequired({
name: 'AdminPassword',
prefix: 'DisplayName',
resourceName: 'AbpSaas',
}),
defaultConnectionString: fieldRequired({
name: 'DefaultConnectionString',
prefix: 'DisplayName',
resourceName: 'AbpSaas',
}),
name: fieldRequired({
name: 'TenantName',
prefix: 'DisplayName',
resourceName: 'AbpSaas',
}),
};
});
/** 启用时间不可晚于禁用时间 */
const getDisabledEnableTime = (current: dayjs.Dayjs) => {
if (!tenant.value.disableTime) {
return false;
}
return (
current &&
current > dayjs(tenant.value.disableTime).add(-1, 'day').endOf('day')
);
};
/** 禁用时间不可早于启用时间 */
const getDisabledDisableTime = (current: dayjs.Dayjs) => {
if (!tenant.value.enableTime) {
return false;
}
return current && current < dayjs(tenant.value.enableTime).endOf('day');
};
const { cancel, createApi, getApi, updateApi } = useTenantsApi();
const { getPagedListApi: getEditions } = useEditionsApi();
const [Modal, modalApi] = useVbenModal({
class: 'w-[600px]',
onClosed: cancel,
async onConfirm() {
await form.value?.validate();
await onSubmit();
},
async onOpenChange(isOpen) {
activeTabKey.value = 'basic';
if (isOpen) {
await onGet();
}
},
title: $t('AbpSaas.Tenants'),
});
async function onGet() {
const { id } = modalApi.getData<TenantDto>();
if (!id) {
tenant.value = { ...defaultModel };
modalApi.setState({ title: $t('AbpSaas.NewTenant') });
return;
}
try {
modalApi.setState({ loading: true });
const editionDto = await getApi(id);
modalApi.setState({
title: `${$t('AbpSaas.Tenants')} - ${editionDto.name}`,
});
tenant.value = editionDto;
} finally {
modalApi.setState({ loading: false });
}
}
async function onSubmit() {
try {
modalApi.setState({ submitting: true });
const api = tenant.value.id
? updateApi(tenant.value.id, tenant.value as TenantUpdateDto)
: createApi(tenant.value as unknown as TenantCreateDto);
const dto = await api;
message.success($t('AbpUi.SavedSuccessfully'));
emits('change', dto);
modalApi.close();
} finally {
modalApi.setState({ submitting: false });
}
}
function onNameChange(name?: string) {
if (
!tenant.value.id &&
(!tenant.value.adminEmailAddress ||
!tenant.value.adminEmailAddress?.endsWith(`@${name}.com`))
) {
tenant.value.adminEmailAddress = `admin@${name}.com`;
}
form.value?.clearValidate('adminEmailAddress');
}
function onConnectionChange(data: TenantConnectionStringDto) {
return new Promise<void>((resolve) => {
tenant.value.connectionStrings ??= [];
let connectionString = tenant.value.connectionStrings.find(
(x: TenantConnectionStringDto) => x.name === data.name,
);
if (connectionString) {
connectionString.value = data.value;
} else {
connectionString = data;
tenant.value.connectionStrings = [
...tenant.value.connectionStrings,
data,
];
}
resolve();
});
}
function onConnectionDelete(data: TenantConnectionStringDto) {
return new Promise<void>((resolve) => {
tenant.value.connectionStrings ??= [];
tenant.value.connectionStrings = tenant.value.connectionStrings.filter(
(x: TenantConnectionStringDto) => x.name !== data.name,
);
resolve();
});
}
const onSearchEditions = debounce(async (filter?: string) => {
const { items } = await getEditions({ filter });
editions.value = items;
}, 500);
onMounted(onSearchEditions);
</script>
<template>
<Modal>
<Form
ref="form"
:model="tenant"
:label-col="{ span: 6 }"
:wapper-col="{ span: 18 }"
:rules="getFormRules"
>
<FormItem name="isActive" :label="$t('AbpSaas.DisplayName:IsActive')">
<Checkbox v-model:checked="tenant.isActive">
{{ $t('AbpSaas.DisplayName:IsActive') }}
</Checkbox>
</FormItem>
<FormItem
v-if="!tenant.id"
name="adminEmailAddress"
:label="$t('AbpSaas.DisplayName:AdminEmailAddress')"
:label-col="{ span: 8 }"
:wapper-col="{ span: 16 }"
>
<Input
type="email"
v-model:value="tenant.adminEmailAddress"
autocomplete="off"
/>
</FormItem>
<FormItem
v-if="!tenant.id"
name="adminPassword"
:label="$t('AbpSaas.DisplayName:AdminPassword')"
>
<InputPassword
v-model:value="tenant.adminPassword"
autocomplete="off"
/>
</FormItem>
<FormItem name="name" :label="$t('AbpSaas.DisplayName:TenantName')">
<Input
v-model:value="tenant.name"
@change="(e) => onNameChange(e.target.value)"
autocomplete="off"
/>
</FormItem>
<FormItem name="editionId" :label="$t('AbpSaas.DisplayName:EditionName')">
<Select
:options="editions"
:field-names="{ label: 'displayName', value: 'id' }"
v-model:value="tenant.editionId"
allow-clear
show-search
:filter-option="false"
@search="onSearchEditions"
/>
</FormItem>
<FormItem name="enableTime" :label="$t('AbpSaas.DisplayName:EnableTime')">
<DatePicker
class="w-full"
value-format="YYYY-MM-DD"
:disabled-date="getDisabledEnableTime"
v-model:value="tenant.enableTime"
/>
</FormItem>
<FormItem
name="disableTime"
:label="$t('AbpSaas.DisplayName:DisableTime')"
>
<DatePicker
class="w-full"
value-format="YYYY-MM-DD"
:disabled-date="getDisabledDisableTime"
v-model:value="tenant.disableTime"
/>
</FormItem>
<FormItem
v-if="!tenant.id"
name="useSharedDatabase"
:label="$t('AbpSaas.DisplayName:UseSharedDatabase')"
>
<Checkbox v-model:checked="tenant.useSharedDatabase">
{{ $t('AbpSaas.DisplayName:UseSharedDatabase') }}
</Checkbox>
</FormItem>
<template v-if="!tenant.id && !tenant.useSharedDatabase">
<FormItem
name="defaultConnectionString"
:label="$t('AbpSaas.DisplayName:DefaultConnectionString')"
>
<Textarea
:auto-size="{ minRows: 2 }"
v-model:value="tenant.defaultConnectionString"
/>
</FormItem>
<ConnectionStringTable
:connection-strings="tenant.connectionStrings"
:submit="onConnectionChange"
:delete="onConnectionDelete"
/>
</template>
</Form>
</Modal>
</template>
<style scoped></style>

258
apps/vben5/packages/@abp/saas/src/components/tenants/TenantTable.vue

@ -0,0 +1,258 @@
<script setup lang="ts">
import type { VbenFormProps, VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import type { TenantDto } from '../../types/tenants';
import { defineAsyncComponent, h } from 'vue';
import { useAccess } from '@vben/access';
import { useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { AuditLogPermissions, EntityChangeDrawer } from '@abp/auditing';
import { useFeatures } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import {
DeleteOutlined,
EditOutlined,
EllipsisOutlined,
} from '@ant-design/icons-vue';
import { Button, Dropdown, Menu, message, Modal } from 'ant-design-vue';
import { useTenantsApi } from '../../api/useTenantsApi';
import { TenantsPermissions } from '../../constants/permissions';
defineOptions({
name: 'EditionTable',
});
const MenuItem = Menu.Item;
const CheckIcon = createIconifyIcon('ant-design:check-outlined');
const CloseIcon = createIconifyIcon('ant-design:close-outlined');
const AuditLogIcon = createIconifyIcon('fluent-mdl2:compliance-audit');
const ConnectionIcon = createIconifyIcon('mdi:connection');
const { isEnabled } = useFeatures();
const { hasAccessByCodes } = useAccess();
const { cancel, deleteApi, getPagedListApi } = useTenantsApi();
const formOptions: VbenFormProps = {
//
collapsed: false,
schema: [
{
component: 'Input',
componentProps: {
allowClear: true,
autocomplete: 'off',
},
fieldName: 'filter',
formItemClass: 'col-span-2 items-baseline',
label: $t('AbpUi.Search'),
},
],
//
showCollapseButton: true,
//
submitOnEnter: true,
};
const gridOptions: VxeGridProps<TenantDto> = {
columns: [
{
align: 'center',
field: 'isActive',
slots: { default: 'isActive' },
title: $t('AbpSaas.DisplayName:IsActive'),
width: 120,
},
{
align: 'left',
field: 'name',
title: $t('AbpSaas.DisplayName:Name'),
},
{
align: 'left',
field: 'editionName',
title: $t('AbpSaas.DisplayName:EditionName'),
width: 160,
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: $t('AbpUi.Actions'),
visible: hasAccessByCodes([
TenantsPermissions.Update,
TenantsPermissions.Delete,
]),
width: 220,
},
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getPagedListApi({
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
zoom: true,
},
};
const gridEvents: VxeGridListeners<TenantDto> = {
cellClick: () => {},
};
const [TenantModal, modalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(() => import('./TenantModal.vue')),
});
const [TenantConnectionStringsModal, connectionStringsModalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(
() => import('./ConnectionStringsModal.vue'),
),
});
const [TenantChangeDrawer, entityChangeDrawerApi] = useVbenDrawer({
connectedComponent: EntityChangeDrawer,
});
const [Grid, { query }] = useVbenVxeGrid({
formOptions,
gridEvents,
gridOptions,
});
const onCreate = () => {
modalApi.setData({});
modalApi.open();
};
const onUpdate = (row: TenantDto) => {
modalApi.setData(row);
modalApi.open();
};
const onDelete = (row: TenantDto) => {
Modal.confirm({
centered: true,
content: $t('AbpSaas.TenantDeletionConfirmationMessage', [row.name]),
onCancel: () => {
cancel();
},
onOk: async () => {
await deleteApi(row.id);
message.success($t('AbpUi.SuccessfullyDeleted'));
query();
},
title: $t('AbpUi.AreYouSure'),
});
};
const onMenuClick = (row: TenantDto, info: MenuInfo) => {
switch (info.key) {
case 'connection-strings': {
connectionStringsModalApi.setData(row);
connectionStringsModalApi.open();
break;
}
case 'entity-changes': {
entityChangeDrawerApi.setData({
entityId: row.id,
entityTypeFullName: 'LINGYUN.Abp.Saas.Tenant',
subject: row.name,
});
entityChangeDrawerApi.open();
break;
}
}
};
</script>
<template>
<Grid :table-title="$t('AbpSaas.Tenants')">
<template #toolbar-tools>
<Button
type="primary"
v-access:code="[TenantsPermissions.Create]"
@click="onCreate"
>
{{ $t('AbpSaas.NewTenant') }}
</Button>
</template>
<template #isActive="{ row }">
<div class="flex flex-row justify-center">
<CheckIcon v-if="row.isActive" class="text-green-500" />
<CloseIcon v-else class="text-red-500" />
</div>
</template>
<template #action="{ row }">
<div class="flex flex-row">
<Button
:icon="h(EditOutlined)"
block
type="link"
v-access:code="[TenantsPermissions.Update]"
@click="onUpdate(row)"
>
{{ $t('AbpUi.Edit') }}
</Button>
<Button
:icon="h(DeleteOutlined)"
block
danger
type="link"
v-access:code="[TenantsPermissions.Delete]"
@click="onDelete(row)"
>
{{ $t('AbpUi.Delete') }}
</Button>
<Dropdown>
<template #overlay>
<Menu @click="(info) => onMenuClick(row, info)">
<MenuItem
v-if="
hasAccessByCodes([TenantsPermissions.ManageConnectionStrings])
"
key="connection-strings"
:icon="h(ConnectionIcon)"
>
{{ $t('AbpSaas.ConnectionStrings') }}
</MenuItem>
<MenuItem
v-if="
isEnabled('AbpAuditing.Logging.AuditLog') &&
hasAccessByCodes([AuditLogPermissions.Default])
"
key="entity-changes"
:icon="h(AuditLogIcon)"
>
{{ $t('AbpAuditLogging.EntitiesChanged') }}
</MenuItem>
</Menu>
</template>
<Button :icon="h(EllipsisOutlined)" type="link" />
</Dropdown>
</div>
</template>
</Grid>
<TenantModal @change="() => query()" />
<TenantConnectionStringsModal />
<TenantChangeDrawer />
</template>
<style lang="scss" scoped></style>

1
apps/vben5/packages/@abp/saas/src/constants/index.ts

@ -0,0 +1 @@
export * from './permissions';

28
apps/vben5/packages/@abp/saas/src/constants/permissions.ts

@ -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',
};

4
apps/vben5/packages/@abp/saas/src/index.ts

@ -0,0 +1,4 @@
export * from './api';
export * from './components';
export * from './constants';
export * from './types';

34
apps/vben5/packages/@abp/saas/src/types/editions.ts

@ -0,0 +1,34 @@
import type {
ExtensibleAuditedEntityDto,
IHasConcurrencyStamp,
PagedAndSortedResultRequestDto,
} from '@abp/core';
interface EditionDto
extends ExtensibleAuditedEntityDto<string>,
IHasConcurrencyStamp {
/** 显示名称 */
displayName: string;
}
interface EditionCreateOrUpdateBase {
/** 显示名称 */
displayName: string;
}
type EditionCreateDto = EditionCreateOrUpdateBase;
interface EditionUpdateDto
extends EditionCreateOrUpdateBase,
IHasConcurrencyStamp {}
interface GetEditionPagedListInput extends PagedAndSortedResultRequestDto {
filter?: string;
}
export type {
EditionCreateDto,
EditionDto,
EditionUpdateDto,
GetEditionPagedListInput,
};

2
apps/vben5/packages/@abp/saas/src/types/index.ts

@ -0,0 +1,2 @@
export * from './editions';
export * from './tenants';

68
apps/vben5/packages/@abp/saas/src/types/tenants.ts

@ -0,0 +1,68 @@
import type {
ExtensibleAuditedEntityDto,
ExtensibleObject,
IHasConcurrencyStamp,
NameValue,
PagedAndSortedResultRequestDto,
} from '@abp/core';
type TenantConnectionStringDto = NameValue<string>;
type TenantConnectionStringSetInput = NameValue<string>;
interface TenantDto
extends ExtensibleAuditedEntityDto<string>,
IHasConcurrencyStamp {
/** 禁用时间 */
disableTime?: string;
/** 版本Id */
editionId?: string;
/** 版本名称 */
editionName?: string;
/** 启用时间 */
enableTime?: string;
/** 是否可用 */
isActive: boolean;
/** 名称 */
name: string;
/** 名称 */
normalizedName: string;
}
interface GetTenantPagedListInput extends PagedAndSortedResultRequestDto {
filter?: string;
}
interface TenantCreateOrUpdateBase extends ExtensibleObject {
/** 禁用时间 */
disableTime?: string;
/** 版本Id */
editionId?: string;
/** 启用时间 */
enableTime?: string;
/** 是否可用 */
isActive: boolean;
/** 名称 */
name: string;
}
interface TenantCreateDto extends TenantCreateOrUpdateBase {
adminEmailAddress: string;
adminPassword: string;
connectionStrings?: TenantConnectionStringSetInput[];
defaultConnectionString?: string;
useSharedDatabase: boolean;
}
interface TenantUpdateDto
extends IHasConcurrencyStamp,
TenantCreateOrUpdateBase {}
export type {
GetTenantPagedListInput,
TenantConnectionStringDto,
TenantConnectionStringSetInput,
TenantCreateDto,
TenantDto,
TenantUpdateDto,
};

6
apps/vben5/packages/@abp/saas/tsconfig.json

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web.json",
"include": ["src"],
"exclude": ["node_modules"]
}
Loading…
Cancel
Save