29 changed files with 1682 additions and 33 deletions
@ -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> |
||||
@ -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> |
||||
@ -0,0 +1,45 @@ |
|||||
|
{ |
||||
|
"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/components": "workspace:*", |
||||
|
"@abp/core": "workspace:*", |
||||
|
"@abp/request": "workspace:*", |
||||
|
"@abp/signalr": "workspace:*", |
||||
|
"@abp/ui": "workspace:*", |
||||
|
"@ant-design/icons-vue": "catalog:", |
||||
|
"@vben/access": "workspace:*", |
||||
|
"@vben/common-ui": "workspace:*", |
||||
|
"@vben/hooks": "workspace:*", |
||||
|
"@vben/icons": "workspace:*", |
||||
|
"@vben/layouts": "workspace:*", |
||||
|
"@vben/locales": "workspace:*", |
||||
|
"ant-design-vue": "catalog:", |
||||
|
"dayjs": "catalog:", |
||||
|
"lodash.debounce": "catalog:", |
||||
|
"vue": "catalog:*", |
||||
|
"vxe-table": "catalog:" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@types/lodash.debounce": "catalog:" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,2 @@ |
|||||
|
export * from './useEditionsApi'; |
||||
|
export * from './useTenantsApi'; |
||||
@ -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, |
||||
|
}; |
||||
|
} |
||||
@ -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, |
||||
|
}; |
||||
|
} |
||||
@ -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> |
||||
@ -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> |
||||
@ -0,0 +1,2 @@ |
|||||
|
export { default as EditionTable } from './editions/EditionTable.vue'; |
||||
|
export { default as TenantTable } from './tenants/TenantTable.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> |
||||
@ -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> |
||||
@ -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> |
||||
@ -0,0 +1,305 @@ |
|||||
|
<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`; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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> |
||||
@ -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> |
||||
@ -0,0 +1 @@ |
|||||
|
export * from './permissions'; |
||||
@ -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,4 @@ |
|||||
|
export * from './api'; |
||||
|
export * from './components'; |
||||
|
export * from './constants'; |
||||
|
export * from './types'; |
||||
@ -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, |
||||
|
}; |
||||
@ -0,0 +1,2 @@ |
|||||
|
export * from './editions'; |
||||
|
export * from './tenants'; |
||||
@ -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, |
||||
|
}; |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"$schema": "https://json.schemastore.org/tsconfig", |
||||
|
"extends": "@vben/tsconfig/web.json", |
||||
|
"include": ["src"], |
||||
|
"exclude": ["node_modules"] |
||||
|
} |
||||
Loading…
Reference in new issue