committed by
GitHub
29 changed files with 1680 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,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:" |
|||
} |
|||
} |
|||
@ -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,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> |
|||
@ -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