committed by
GitHub
385 changed files with 21267 additions and 2653 deletions
@ -0,0 +1,37 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { MdiGithub, MdiGoogle, MdiQqchat, MdiWechat } from '@vben/icons'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { VbenIconButton } from '@vben-core/shadcn-ui'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'ThirdPartyLogin', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div class="w-full sm:mx-auto md:max-w-md"> |
||||
|
<div class="mt-4 flex items-center justify-between"> |
||||
|
<span class="border-input w-[35%] border-b dark:border-gray-600"></span> |
||||
|
<span class="text-muted-foreground text-center text-xs uppercase"> |
||||
|
{{ $t('authentication.thirdPartyLogin') }} |
||||
|
</span> |
||||
|
<span class="border-input w-[35%] border-b dark:border-gray-600"></span> |
||||
|
</div> |
||||
|
|
||||
|
<div class="mt-4 flex flex-wrap justify-center"> |
||||
|
<VbenIconButton class="mb-3"> |
||||
|
<MdiWechat /> |
||||
|
</VbenIconButton> |
||||
|
<VbenIconButton class="mb-3"> |
||||
|
<MdiQqchat /> |
||||
|
</VbenIconButton> |
||||
|
<VbenIconButton class="mb-3"> |
||||
|
<MdiGithub /> |
||||
|
</VbenIconButton> |
||||
|
<VbenIconButton class="mb-3"> |
||||
|
<MdiGoogle /> |
||||
|
</VbenIconButton> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
|
||||
|
import { LoggingTable } from '@abp/auditing'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'Vben5AuditingLoggings', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page> |
||||
|
<LoggingTable /> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
|
||||
|
import { ContainerTable } from '@abp/oss'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'Vben5OssContainers', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page> |
||||
|
<ContainerTable /> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
|
||||
|
import { ObjectPage } from '@abp/oss'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'Vben5OssObjects', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page> |
||||
|
<ObjectPage /> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
|
||||
|
import { DataDictionaryTable } from '@abp/platform'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'Vben5PlatformDataDictionaries', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page> |
||||
|
<DataDictionaryTable /> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
|
||||
|
import { LayoutTable } from '@abp/platform'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'Vben5PlatformLayouts', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page> |
||||
|
<LayoutTable /> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
|
||||
|
import { MenuTable } from '@abp/platform'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'Vben5PlatformMenus', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page> |
||||
|
<MenuTable /> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
|
||||
|
import { JobInfoTable } from '@abp/tasks'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'Vben5TasksJobInfos', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page> |
||||
|
<JobInfoTable /> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
|
||||
|
import { TemplateDefinitionTable } from '@abp/text-templating'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'Vben5TextTemplatingDefinitions', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page> |
||||
|
<TemplateDefinitionTable /> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
|
||||
|
import { WebhookDefinitionTable } from '@abp/webhooks'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'Vben5WebhooksDefinitions', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page> |
||||
|
<WebhookDefinitionTable /> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
|
||||
|
import { WebhookGroupDefinitionTable } from '@abp/webhooks'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'Vben5WebhooksGroupDefinitions', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page> |
||||
|
<WebhookGroupDefinitionTable /> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
|
||||
|
import { WebhookSendAttemptTable } from '@abp/webhooks'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'Vben5WebhooksSendAttempts', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page> |
||||
|
<WebhookSendAttemptTable /> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
|
||||
|
import { WebhookSubscriptionTable } from '@abp/webhooks'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'Vben5WebhooksSubscriptions', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page> |
||||
|
<WebhookSubscriptionTable /> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -1,2 +1,3 @@ |
|||||
export { useAuditLogsApi } from './useAuditLogsApi'; |
export { useAuditLogsApi } from './useAuditLogsApi'; |
||||
export { useEntityChangesApi } from './useEntityChangesApi'; |
export { useEntityChangesApi } from './useEntityChangesApi'; |
||||
|
export { useLoggingsApi } from './useLoggingsApi'; |
||||
|
|||||
@ -0,0 +1,38 @@ |
|||||
|
import type { PagedResultDto } from '@abp/core'; |
||||
|
|
||||
|
import type { LogDto, LogGetListInput } from '../types/loggings'; |
||||
|
|
||||
|
import { useRequest } from '@abp/request'; |
||||
|
|
||||
|
export function useLoggingsApi() { |
||||
|
const { cancel, request } = useRequest(); |
||||
|
|
||||
|
/** |
||||
|
* 获取系统日志 |
||||
|
* @param id 日志id |
||||
|
*/ |
||||
|
function getApi(id: string): Promise<LogDto> { |
||||
|
return request<LogDto>(`/api/auditing/logging/${id}`, { |
||||
|
method: 'GET', |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取系统日志分页列表 |
||||
|
* @param input 参数 |
||||
|
*/ |
||||
|
function getPagedListApi( |
||||
|
input: LogGetListInput, |
||||
|
): Promise<PagedResultDto<LogDto>> { |
||||
|
return request<PagedResultDto<LogDto>>('/api/auditing/logging', { |
||||
|
method: 'GET', |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
cancel, |
||||
|
getApi, |
||||
|
getPagedListApi, |
||||
|
}; |
||||
|
} |
||||
@ -1,2 +1,3 @@ |
|||||
export { default as AuditLogTable } from './audit-logs/AuditLogTable.vue'; |
export { default as AuditLogTable } from './audit-logs/AuditLogTable.vue'; |
||||
export { default as EntityChangeDrawer } from './entity-changes/EntityChangeDrawer.vue'; |
export { default as EntityChangeDrawer } from './entity-changes/EntityChangeDrawer.vue'; |
||||
|
export { default as LoggingTable } from './loggings/LoggingTable.vue'; |
||||
|
|||||
@ -0,0 +1,138 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { LogDto, LogLevel } from '../../types/loggings'; |
||||
|
|
||||
|
import { ref } from 'vue'; |
||||
|
|
||||
|
import { useVbenDrawer } from '@vben/common-ui'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { formatToDateTime } from '@abp/core'; |
||||
|
import { Descriptions, Tabs, Tag } from 'ant-design-vue'; |
||||
|
|
||||
|
import { useLoggingsApi } from '../../api/useLoggingsApi'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'LoggingDrawer', |
||||
|
}); |
||||
|
|
||||
|
defineProps<{ |
||||
|
logLevelOptions: { color: string; label: string; value: LogLevel }[]; |
||||
|
}>(); |
||||
|
const TabPane = Tabs.TabPane; |
||||
|
const DescriptionsItem = Descriptions.Item; |
||||
|
|
||||
|
const activedTab = ref('basic'); |
||||
|
const logModel = ref<LogDto>({} as LogDto); |
||||
|
|
||||
|
const { getApi } = useLoggingsApi(); |
||||
|
const [Drawer, drawerApi] = useVbenDrawer({ |
||||
|
class: 'w-auto', |
||||
|
onCancel() { |
||||
|
drawerApi.close(); |
||||
|
}, |
||||
|
onConfirm: async () => { |
||||
|
drawerApi.close(); |
||||
|
}, |
||||
|
onOpenChange: async (isOpen: boolean) => { |
||||
|
if (isOpen) { |
||||
|
try { |
||||
|
logModel.value = {} as LogDto; |
||||
|
drawerApi.setState({ loading: true }); |
||||
|
const dto = drawerApi.getData<LogDto>(); |
||||
|
await onGet(dto.fields.id); |
||||
|
} finally { |
||||
|
drawerApi.setState({ loading: false }); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
title: $t('AbpAuditLogging.AuditLog'), |
||||
|
}); |
||||
|
async function onGet(id: string) { |
||||
|
const dto = await getApi(id); |
||||
|
logModel.value = dto; |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Drawer> |
||||
|
<div style="width: 800px"> |
||||
|
<Tabs v-model="activedTab"> |
||||
|
<TabPane key="basic" :tab="$t('AbpAuditLogging.Operation')"> |
||||
|
<Descriptions |
||||
|
:colon="false" |
||||
|
:column="1" |
||||
|
bordered |
||||
|
size="small" |
||||
|
:label-style="{ minWidth: '110px' }" |
||||
|
> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.TimeStamp')"> |
||||
|
{{ formatToDateTime(logModel.timeStamp) }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.Level')"> |
||||
|
<Tag :color="logLevelOptions[logModel.level]?.color"> |
||||
|
{{ logLevelOptions[logModel.level]?.label }} |
||||
|
</Tag> |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.Message')" :span="2"> |
||||
|
{{ logModel.message }} |
||||
|
</DescriptionsItem> |
||||
|
</Descriptions> |
||||
|
</TabPane> |
||||
|
<TabPane key="fields" :tab="$t('AbpAuditLogging.Fields')"> |
||||
|
<Descriptions |
||||
|
:colon="false" |
||||
|
:column="1" |
||||
|
bordered |
||||
|
size="small" |
||||
|
:label-style="{ minWidth: '110px' }" |
||||
|
> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.ApplicationName')"> |
||||
|
{{ logModel.fields.application }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.MachineName')"> |
||||
|
{{ logModel.fields.machineName }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.Environment')"> |
||||
|
{{ logModel.fields.environment }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.ProcessId')"> |
||||
|
{{ logModel.fields.processId }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.ThreadId')"> |
||||
|
{{ logModel.fields.threadId }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.Context')"> |
||||
|
{{ logModel.fields.context }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.ActionId')"> |
||||
|
{{ logModel.fields.actionId }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.MethodName')"> |
||||
|
{{ logModel.fields.actionName }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.RequestId')"> |
||||
|
{{ logModel.fields.requestId }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.RequestPath')"> |
||||
|
{{ logModel.fields.requestPath }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.ConnectionId')"> |
||||
|
{{ logModel.fields.connectionId }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.CorrelationId')"> |
||||
|
{{ logModel.fields.correlationId }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.ClientId')"> |
||||
|
{{ logModel.fields.clientId }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.UserId')"> |
||||
|
{{ logModel.fields.userId }} |
||||
|
</DescriptionsItem> |
||||
|
</Descriptions> |
||||
|
</TabPane> |
||||
|
</Tabs> |
||||
|
</div> |
||||
|
</Drawer> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped></style> |
||||
@ -0,0 +1,345 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { SortOrder } from '@abp/core'; |
||||
|
import type { VxeGridListeners, VxeGridProps } from '@abp/ui'; |
||||
|
|
||||
|
import type { VbenFormProps } from '@vben/common-ui'; |
||||
|
|
||||
|
import type { LogDto } from '../../types/loggings'; |
||||
|
|
||||
|
import { defineAsyncComponent, h, reactive, ref } from 'vue'; |
||||
|
|
||||
|
import { useVbenDrawer } from '@vben/common-ui'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { formatToDateTime } from '@abp/core'; |
||||
|
import { useVbenVxeGrid } from '@abp/ui'; |
||||
|
import { EditOutlined } from '@ant-design/icons-vue'; |
||||
|
import { Button, Tag } from 'ant-design-vue'; |
||||
|
|
||||
|
import { useLoggingsApi } from '../../api/useLoggingsApi'; |
||||
|
import { SystemLogPermissions } from '../../constants/permissions'; |
||||
|
import { LogLevel } from '../../types/loggings'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'LoggingTable', |
||||
|
}); |
||||
|
const { getPagedListApi } = useLoggingsApi(); |
||||
|
|
||||
|
const selectedKeys = ref<string[]>([]); |
||||
|
const logLevelOptions = reactive([ |
||||
|
{ |
||||
|
color: 'purple', |
||||
|
label: 'Trace', |
||||
|
value: LogLevel.Trace, |
||||
|
}, |
||||
|
{ |
||||
|
color: 'blue', |
||||
|
label: 'Debug', |
||||
|
value: LogLevel.Debug, |
||||
|
}, |
||||
|
{ |
||||
|
color: 'green', |
||||
|
label: 'Information', |
||||
|
value: LogLevel.Information, |
||||
|
}, |
||||
|
{ |
||||
|
color: 'orange', |
||||
|
label: 'Warning', |
||||
|
value: LogLevel.Warning, |
||||
|
}, |
||||
|
{ |
||||
|
color: 'red', |
||||
|
label: 'Error', |
||||
|
value: LogLevel.Error, |
||||
|
}, |
||||
|
{ |
||||
|
color: '#f50', |
||||
|
label: 'Critical', |
||||
|
value: LogLevel.Critical, |
||||
|
}, |
||||
|
{ |
||||
|
color: '', |
||||
|
label: 'None', |
||||
|
value: LogLevel.None, |
||||
|
}, |
||||
|
]); |
||||
|
const formOptions: VbenFormProps = { |
||||
|
// 默认展开 |
||||
|
collapsed: true, |
||||
|
collapsedRows: 2, |
||||
|
fieldMappingTime: [ |
||||
|
[ |
||||
|
'timeStamp', |
||||
|
['startTime', 'endTime'], |
||||
|
['YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss'], |
||||
|
], |
||||
|
], |
||||
|
schema: [ |
||||
|
{ |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: logLevelOptions, |
||||
|
}, |
||||
|
fieldName: 'level', |
||||
|
label: $t('AbpAuditLogging.Level'), |
||||
|
}, |
||||
|
{ |
||||
|
component: 'RangePicker', |
||||
|
componentProps: { |
||||
|
showTime: true, |
||||
|
}, |
||||
|
fieldName: 'timeStamp', |
||||
|
formItemClass: 'col-span-2 items-baseline', |
||||
|
label: $t('AbpAuditLogging.TimeStamp'), |
||||
|
}, |
||||
|
{ |
||||
|
component: 'Input', |
||||
|
fieldName: 'application', |
||||
|
label: $t('AbpAuditLogging.ApplicationName'), |
||||
|
}, |
||||
|
{ |
||||
|
component: 'Input', |
||||
|
fieldName: 'machineName', |
||||
|
label: $t('AbpAuditLogging.MachineName'), |
||||
|
}, |
||||
|
{ |
||||
|
component: 'Input', |
||||
|
fieldName: 'environment', |
||||
|
label: $t('AbpAuditLogging.Environment'), |
||||
|
}, |
||||
|
{ |
||||
|
component: 'Input', |
||||
|
fieldName: 'requestId', |
||||
|
formItemClass: 'col-span-2 items-baseline', |
||||
|
label: $t('AbpAuditLogging.RequestId'), |
||||
|
}, |
||||
|
{ |
||||
|
component: 'Input', |
||||
|
fieldName: 'requestPath', |
||||
|
formItemClass: 'col-span-2 items-baseline', |
||||
|
label: $t('AbpAuditLogging.RequestPath'), |
||||
|
labelWidth: 150, |
||||
|
}, |
||||
|
{ |
||||
|
component: 'Input', |
||||
|
fieldName: 'correlationId', |
||||
|
formItemClass: 'col-span-2 items-baseline', |
||||
|
label: $t('AbpAuditLogging.CorrelationId'), |
||||
|
}, |
||||
|
{ |
||||
|
component: 'Checkbox', |
||||
|
componentProps: { |
||||
|
render: () => { |
||||
|
return h('span', $t('AbpAuditLogging.HasException')); |
||||
|
}, |
||||
|
}, |
||||
|
fieldName: 'hasException', |
||||
|
label: $t('AbpAuditLogging.HasException'), |
||||
|
}, |
||||
|
], |
||||
|
// 控制表单是否显示折叠按钮 |
||||
|
showCollapseButton: true, |
||||
|
// 按下回车时是否提交表单 |
||||
|
submitOnEnter: true, |
||||
|
wrapperClass: 'grid-cols-4', |
||||
|
}; |
||||
|
|
||||
|
const gridOptions: VxeGridProps<LogDto> = { |
||||
|
columns: [ |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'applicationName', |
||||
|
formatter: ({ row }) => { |
||||
|
return row.fields?.application; |
||||
|
}, |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.ApplicationName'), |
||||
|
width: 150, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'timeStamp', |
||||
|
formatter: ({ cellValue }) => { |
||||
|
return cellValue ? formatToDateTime(cellValue) : cellValue; |
||||
|
}, |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.TimeStamp'), |
||||
|
width: 150, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'level', |
||||
|
slots: { default: 'level' }, |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.Level'), |
||||
|
width: 120, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'message', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.Message'), |
||||
|
width: 500, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'machineName', |
||||
|
formatter: ({ row }) => { |
||||
|
return row.fields?.machineName; |
||||
|
}, |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.MachineName'), |
||||
|
width: 140, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'environment', |
||||
|
formatter: ({ row }) => { |
||||
|
return row.fields?.environment; |
||||
|
}, |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.Environment'), |
||||
|
width: 150, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'requestId', |
||||
|
formatter: ({ row }) => { |
||||
|
return row.fields?.requestId; |
||||
|
}, |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.RequestId'), |
||||
|
width: 200, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'requestPath', |
||||
|
formatter: ({ row }) => { |
||||
|
return row.fields?.requestPath; |
||||
|
}, |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.RequestPath'), |
||||
|
width: 300, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'connectionId', |
||||
|
formatter: ({ row }) => { |
||||
|
return row.fields?.connectionId; |
||||
|
}, |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.ConnectionId'), |
||||
|
width: 150, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'correlationId', |
||||
|
formatter: ({ row }) => { |
||||
|
return row.fields?.correlationId; |
||||
|
}, |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.CorrelationId'), |
||||
|
width: 240, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'action', |
||||
|
fixed: 'right', |
||||
|
slots: { default: 'action' }, |
||||
|
title: $t('AbpUi.Actions'), |
||||
|
width: 180, |
||||
|
}, |
||||
|
], |
||||
|
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', |
||||
|
}, |
||||
|
}, |
||||
|
sortConfig: { |
||||
|
remote: true, |
||||
|
}, |
||||
|
toolbarConfig: { |
||||
|
custom: true, |
||||
|
export: true, |
||||
|
// import: true, |
||||
|
refresh: true, |
||||
|
zoom: true, |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
const gridEvents: VxeGridListeners<LogDto> = { |
||||
|
checkboxAll: (params) => { |
||||
|
selectedKeys.value = params.records.map((x) => x.fields.id); |
||||
|
}, |
||||
|
checkboxChange: (params) => { |
||||
|
selectedKeys.value = params.records.map((x) => x.fields.id); |
||||
|
}, |
||||
|
sortChange: onSort, |
||||
|
}; |
||||
|
|
||||
|
const [Grid, gridApi] = useVbenVxeGrid({ |
||||
|
formOptions, |
||||
|
gridEvents, |
||||
|
gridOptions, |
||||
|
}); |
||||
|
const [LoggingDrawer, logDrawerApi] = useVbenDrawer({ |
||||
|
connectedComponent: defineAsyncComponent(() => import('./LoggingDrawer.vue')), |
||||
|
}); |
||||
|
|
||||
|
function onUpdate(row: LogDto) { |
||||
|
logDrawerApi.setData(row); |
||||
|
logDrawerApi.open(); |
||||
|
} |
||||
|
|
||||
|
function onSort(params: { field: string; order: SortOrder }) { |
||||
|
const sorting = params.order ? `${params.field} ${params.order}` : undefined; |
||||
|
gridApi.query({ sorting }); |
||||
|
} |
||||
|
|
||||
|
function onFilter(field: string, value: any) { |
||||
|
gridApi.formApi.setFieldValue(field, value); |
||||
|
gridApi.formApi.validateAndSubmitForm(); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Grid :table-title="$t('AbpAuditLogging.Logging')"> |
||||
|
<template #level="{ row }"> |
||||
|
<Tag :color="logLevelOptions[row.level]?.color"> |
||||
|
<a |
||||
|
class="link" |
||||
|
href="javaScript:void(0);" |
||||
|
@click="onFilter('level', row.level)" |
||||
|
>{{ logLevelOptions[row.level]?.label }} |
||||
|
</a> |
||||
|
</Tag> |
||||
|
</template> |
||||
|
<template #action="{ row }"> |
||||
|
<div class="flex flex-row"> |
||||
|
<Button |
||||
|
:icon="h(EditOutlined)" |
||||
|
block |
||||
|
type="link" |
||||
|
v-access:code="[SystemLogPermissions.Default]" |
||||
|
@click="onUpdate(row)" |
||||
|
> |
||||
|
{{ $t('AbpAuditLogging.ShowLogDialog') }} |
||||
|
</Button> |
||||
|
</div> |
||||
|
</template> |
||||
|
</Grid> |
||||
|
<LoggingDrawer :log-level-options="logLevelOptions" /> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped></style> |
||||
@ -1,2 +1,3 @@ |
|||||
export * from './audit-logs'; |
export * from './audit-logs'; |
||||
export * from './entity-changes'; |
export * from './entity-changes'; |
||||
|
export * from './loggings'; |
||||
|
|||||
@ -0,0 +1,65 @@ |
|||||
|
interface LogExceptionDto { |
||||
|
class?: string; |
||||
|
depth?: number; |
||||
|
helpUrl?: string; |
||||
|
hResult?: number; |
||||
|
message?: string; |
||||
|
source?: string; |
||||
|
stackTrace?: string; |
||||
|
} |
||||
|
|
||||
|
interface LogFieldDto { |
||||
|
actionId?: string; |
||||
|
actionName?: string; |
||||
|
application?: string; |
||||
|
clientId?: string; |
||||
|
connectionId?: string; |
||||
|
context?: string; |
||||
|
correlationId?: string; |
||||
|
environment?: string; |
||||
|
id: string; |
||||
|
machineName?: string; |
||||
|
processId?: number; |
||||
|
requestId?: string; |
||||
|
requestPath?: string; |
||||
|
threadId?: number; |
||||
|
userId?: string; |
||||
|
} |
||||
|
|
||||
|
enum LogLevel { |
||||
|
Critical = 5, |
||||
|
Debug = 1, |
||||
|
Error = 4, |
||||
|
Information = 2, |
||||
|
None = 6, |
||||
|
Trace = 0, |
||||
|
Warning = 3, |
||||
|
} |
||||
|
|
||||
|
interface LogDto { |
||||
|
exceptions: LogExceptionDto[]; |
||||
|
fields: LogFieldDto; |
||||
|
level: LogLevel; |
||||
|
message: string; |
||||
|
timeStamp: Date; |
||||
|
} |
||||
|
|
||||
|
interface LogGetListInput { |
||||
|
application?: string; |
||||
|
context?: string; |
||||
|
correlationId?: string; |
||||
|
endTime?: Date; |
||||
|
environment?: string; |
||||
|
hasException?: boolean; |
||||
|
level?: LogLevel; |
||||
|
machineName?: string; |
||||
|
processId?: number; |
||||
|
requestId?: string; |
||||
|
requestPath?: string; |
||||
|
startTime?: Date; |
||||
|
threadId?: number; |
||||
|
} |
||||
|
|
||||
|
export type { LogDto, LogExceptionDto, LogFieldDto, LogGetListInput }; |
||||
|
|
||||
|
export { LogLevel }; |
||||
@ -0,0 +1,82 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { |
||||
|
computed, |
||||
|
nextTick, |
||||
|
onBeforeUnmount, |
||||
|
onDeactivated, |
||||
|
ref, |
||||
|
unref, |
||||
|
watch, |
||||
|
} from 'vue'; |
||||
|
|
||||
|
import { preferences } from '@vben/preferences'; |
||||
|
|
||||
|
import VditorPreview from 'vditor'; |
||||
|
|
||||
|
const props = defineProps<{ |
||||
|
class?: string; |
||||
|
value: string; |
||||
|
}>(); |
||||
|
const viewerRef = ref<HTMLDivElement>(); |
||||
|
const vditorPreviewRef = ref<VditorPreview>(); |
||||
|
|
||||
|
const skinName = computed(() => { |
||||
|
return preferences.theme.mode === 'light' ? 'light' : 'dark'; |
||||
|
}); |
||||
|
|
||||
|
function init() { |
||||
|
const viewerEl = unref(viewerRef) as HTMLDivElement; |
||||
|
const isDark = skinName.value === 'dark'; |
||||
|
VditorPreview.preview(viewerEl, props.value, { |
||||
|
hljs: { |
||||
|
style: isDark ? 'dracula' : 'github', |
||||
|
}, |
||||
|
mode: isDark ? 'dark' : 'light', |
||||
|
theme: { |
||||
|
current: isDark ? 'dark' : 'light', |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
watch( |
||||
|
() => skinName.value, |
||||
|
(val) => { |
||||
|
const isDark = val === 'dark'; |
||||
|
VditorPreview.setContentTheme(isDark ? 'dark' : 'light', ''); |
||||
|
VditorPreview.setCodeTheme(isDark ? 'dracula' : 'github'); |
||||
|
init(); |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
watch( |
||||
|
() => props.value, |
||||
|
(v, oldValue) => { |
||||
|
v !== oldValue && nextTick(init); |
||||
|
}, |
||||
|
{ |
||||
|
immediate: true, |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
function destroy() { |
||||
|
const vditorInstance = unref(vditorPreviewRef); |
||||
|
if (!vditorInstance) return; |
||||
|
try { |
||||
|
vditorInstance?.destroy?.(); |
||||
|
} catch {} |
||||
|
vditorPreviewRef.value = undefined; |
||||
|
} |
||||
|
|
||||
|
onBeforeUnmount(destroy); |
||||
|
onDeactivated(destroy); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div ref="viewerRef" id="markdownViewer" :class="$props.class"></div> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped> |
||||
|
.markdown-viewer { |
||||
|
width: 100%; |
||||
|
} |
||||
|
</style> |
||||
@ -1 +1,2 @@ |
|||||
export { default as MarkdownEditor } from './Editor.vue'; |
export { default as MarkdownEditor } from './Editor.vue'; |
||||
|
export { default as MarkdownViewer } from './Viewer.vue'; |
||||
|
|||||
@ -0,0 +1,44 @@ |
|||||
|
{ |
||||
|
"name": "@abp/oss", |
||||
|
"version": "9.1.3", |
||||
|
"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/oss" |
||||
|
}, |
||||
|
"license": "MIT", |
||||
|
"type": "module", |
||||
|
"sideEffects": [ |
||||
|
"**/*.css" |
||||
|
], |
||||
|
"exports": { |
||||
|
".": { |
||||
|
"types": "./src/index.ts", |
||||
|
"default": "./src/index.ts" |
||||
|
}, |
||||
|
"./global": { |
||||
|
"types": "./global.d.ts" |
||||
|
} |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"@abp/components": "workspace:*", |
||||
|
"@abp/core": "workspace:*", |
||||
|
"@abp/features": "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/layouts": "workspace:*", |
||||
|
"@vben/locales": "workspace:*", |
||||
|
"@vben/stores": "workspace:*", |
||||
|
"ant-design-vue": "catalog:", |
||||
|
"vue": "catalog:*", |
||||
|
"vue-simple-uploader": "catalog:", |
||||
|
"vxe-table": "catalog:" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,2 @@ |
|||||
|
export { useContainesApi } from './useContainesApi'; |
||||
|
export { useObjectsApi } from './useObjectsApi'; |
||||
@ -0,0 +1,61 @@ |
|||||
|
import type { |
||||
|
GetOssContainersInput, |
||||
|
GetOssObjectsInput, |
||||
|
OssContainerDto, |
||||
|
OssContainersResultDto, |
||||
|
} from '../types/containes'; |
||||
|
import type { OssObjectsResultDto } from '../types/objects'; |
||||
|
|
||||
|
import { useRequest } from '@abp/request'; |
||||
|
|
||||
|
export function useContainesApi() { |
||||
|
const { cancel, request } = useRequest(); |
||||
|
|
||||
|
function deleteApi(name: string): Promise<void> { |
||||
|
return request(`/api/oss-management/containes/${name}`, { |
||||
|
method: 'DELETE', |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function getApi(name: string): Promise<OssContainerDto> { |
||||
|
return request<OssContainerDto>(`/api/oss-management/containes/${name}`, { |
||||
|
method: 'GET', |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function getListApi( |
||||
|
input?: GetOssContainersInput, |
||||
|
): Promise<OssContainersResultDto> { |
||||
|
return request<OssContainersResultDto>(`/api/oss-management/containes`, { |
||||
|
method: 'GET', |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function getObjectsApi( |
||||
|
input: GetOssObjectsInput, |
||||
|
): Promise<OssObjectsResultDto> { |
||||
|
return request<OssObjectsResultDto>( |
||||
|
`/api/oss-management/containes/objects`, |
||||
|
{ |
||||
|
method: 'GET', |
||||
|
params: input, |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
function createApi(name: string): Promise<OssContainerDto> { |
||||
|
return request<OssContainerDto>(`/api/oss-management/containes/${name}`, { |
||||
|
method: 'POST', |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
cancel, |
||||
|
createApi, |
||||
|
deleteApi, |
||||
|
getApi, |
||||
|
getListApi, |
||||
|
getObjectsApi, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
import type { |
||||
|
CreateOssObjectInput, |
||||
|
GetOssObjectInput, |
||||
|
OssObjectDto, |
||||
|
} from '../types/objects'; |
||||
|
|
||||
|
import { useRequest } from '@abp/request'; |
||||
|
|
||||
|
export function useObjectsApi() { |
||||
|
const { cancel, request } = useRequest(); |
||||
|
|
||||
|
function createApi(input: CreateOssObjectInput): Promise<OssObjectDto> { |
||||
|
const formData = new window.FormData(); |
||||
|
formData.append('bucket', input.bucket); |
||||
|
formData.append('fileName', input.fileName); |
||||
|
formData.append('overwrite', String(input.overwrite)); |
||||
|
input.expirationTime && |
||||
|
formData.append('expirationTime', input.expirationTime.toString()); |
||||
|
input.path && formData.append('path', input.path); |
||||
|
input.file && formData.append('file', input.file); |
||||
|
return request<OssObjectDto>(`/api/oss-management/objects`, { |
||||
|
data: formData, |
||||
|
headers: { |
||||
|
'Content-Type': 'multipart/form-data;charset=utf-8', |
||||
|
}, |
||||
|
method: 'POST', |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function generateUrlApi(input: GetOssObjectInput): Promise<string> { |
||||
|
return request<string>('/api/oss-management/objects/generate-url', { |
||||
|
method: 'GET', |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function deleteApi(input: GetOssObjectInput): Promise<void> { |
||||
|
return request('/api/oss-management/objects', { |
||||
|
method: 'DELETE', |
||||
|
params: input, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
cancel, |
||||
|
createApi, |
||||
|
deleteApi, |
||||
|
generateUrlApi, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { OssContainerDto } from '../../types'; |
||||
|
|
||||
|
import { useVbenForm, useVbenModal } from '@vben/common-ui'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { message } from 'ant-design-vue'; |
||||
|
|
||||
|
import { useContainesApi } from '../../api'; |
||||
|
|
||||
|
const emits = defineEmits<{ |
||||
|
(event: 'change', data: OssContainerDto): void; |
||||
|
}>(); |
||||
|
|
||||
|
const { cancel, createApi } = useContainesApi(); |
||||
|
|
||||
|
const [Form, formApi] = useVbenForm({ |
||||
|
handleSubmit: onSubmit, |
||||
|
schema: [ |
||||
|
{ |
||||
|
component: 'Input', |
||||
|
fieldName: 'name', |
||||
|
label: $t('AbpOssManagement.DisplayName:Name'), |
||||
|
rules: 'required', |
||||
|
}, |
||||
|
], |
||||
|
showDefaultActions: false, |
||||
|
}); |
||||
|
const [Modal, modalApi] = useVbenModal({ |
||||
|
onCancel: cancel, |
||||
|
onConfirm: async () => { |
||||
|
await formApi.validateAndSubmitForm(); |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
async function onSubmit(values: Record<string, any>) { |
||||
|
try { |
||||
|
modalApi.setState({ submitting: true }); |
||||
|
const dto = await createApi(values.name); |
||||
|
message.success($t('AbpUi.SavedSuccessfully')); |
||||
|
emits('change', dto); |
||||
|
modalApi.close(); |
||||
|
} finally { |
||||
|
modalApi.setState({ submitting: false }); |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Modal :title="$t('AbpOssManagement.Containers:Create')"> |
||||
|
<Form /> |
||||
|
</Modal> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped></style> |
||||
@ -0,0 +1,190 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { VxeGridListeners, VxeGridProps } from '@abp/ui'; |
||||
|
|
||||
|
import type { VbenFormProps } from '@vben/common-ui'; |
||||
|
|
||||
|
import type { OssContainerDto } from '../../types/containes'; |
||||
|
|
||||
|
import { defineAsyncComponent, h, ref } from 'vue'; |
||||
|
|
||||
|
import { useVbenModal } from '@vben/common-ui'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { formatToDateTime } from '@abp/core'; |
||||
|
import { useVbenVxeGrid } from '@abp/ui'; |
||||
|
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue'; |
||||
|
import { Button, message, Modal } from 'ant-design-vue'; |
||||
|
|
||||
|
import { useContainesApi } from '../../api/useContainesApi'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'ContainerTable', |
||||
|
}); |
||||
|
|
||||
|
const { cancel, deleteApi, getListApi } = useContainesApi(); |
||||
|
|
||||
|
const selectedKeys = ref<string[]>([]); |
||||
|
|
||||
|
const formOptions: VbenFormProps = { |
||||
|
collapsed: true, |
||||
|
collapsedRows: 2, |
||||
|
commonConfig: { |
||||
|
componentProps: { |
||||
|
class: 'w-full', |
||||
|
}, |
||||
|
}, |
||||
|
schema: [ |
||||
|
{ |
||||
|
component: 'Input', |
||||
|
componentProps: { |
||||
|
allowClear: true, |
||||
|
}, |
||||
|
fieldName: 'filter', |
||||
|
formItemClass: 'col-span-3 items-baseline', |
||||
|
label: $t('AbpUi.Search'), |
||||
|
}, |
||||
|
], |
||||
|
// 控制表单是否显示折叠按钮 |
||||
|
showCollapseButton: true, |
||||
|
// 按下回车时是否提交表单 |
||||
|
submitOnEnter: true, |
||||
|
}; |
||||
|
|
||||
|
const gridOptions: VxeGridProps<OssContainerDto> = { |
||||
|
columns: [ |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'name', |
||||
|
minWidth: 150, |
||||
|
title: $t('AbpOssManagement.DisplayName:Name'), |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'creationDate', |
||||
|
formatter({ cellValue }) { |
||||
|
return cellValue ? formatToDateTime(cellValue) : ''; |
||||
|
}, |
||||
|
minWidth: 120, |
||||
|
title: $t('AbpOssManagement.DisplayName:CreationDate'), |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'lastModifiedDate', |
||||
|
formatter({ cellValue }) { |
||||
|
return cellValue ? formatToDateTime(cellValue) : ''; |
||||
|
}, |
||||
|
minWidth: 120, |
||||
|
title: $t('AbpOssManagement.DisplayName:LastModifiedDate'), |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'size', |
||||
|
minWidth: 150, |
||||
|
title: $t('AbpOssManagement.DisplayName:Size'), |
||||
|
}, |
||||
|
{ |
||||
|
field: 'action', |
||||
|
fixed: 'right', |
||||
|
slots: { default: 'action' }, |
||||
|
title: $t('AbpUi.Actions'), |
||||
|
width: 150, |
||||
|
}, |
||||
|
], |
||||
|
exportConfig: {}, |
||||
|
keepSource: true, |
||||
|
proxyConfig: { |
||||
|
ajax: { |
||||
|
query: async ({ page }, formValues) => { |
||||
|
const res = await getListApi({ |
||||
|
maxResultCount: page.pageSize, |
||||
|
skipCount: (page.currentPage - 1) * page.pageSize, |
||||
|
...formValues, |
||||
|
}); |
||||
|
return { |
||||
|
totalCount: res.maxKeys, |
||||
|
items: res.containers, |
||||
|
}; |
||||
|
}, |
||||
|
}, |
||||
|
response: { |
||||
|
total: 'totalCount', |
||||
|
list: 'items', |
||||
|
}, |
||||
|
}, |
||||
|
toolbarConfig: { |
||||
|
custom: true, |
||||
|
export: true, |
||||
|
// import: true, |
||||
|
refresh: true, |
||||
|
zoom: true, |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
const gridEvents: VxeGridListeners<OssContainerDto> = { |
||||
|
checkboxAll: (params) => { |
||||
|
selectedKeys.value = params.records.map((record) => record.name); |
||||
|
}, |
||||
|
checkboxChange: (params) => { |
||||
|
selectedKeys.value = params.records.map((record) => record.name); |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
const [Grid, gridApi] = useVbenVxeGrid({ |
||||
|
formOptions, |
||||
|
gridEvents, |
||||
|
gridOptions, |
||||
|
}); |
||||
|
|
||||
|
const [ContainerModal, modalApi] = useVbenModal({ |
||||
|
connectedComponent: defineAsyncComponent( |
||||
|
() => import('./ContainerModal.vue'), |
||||
|
), |
||||
|
}); |
||||
|
|
||||
|
function onCreate() { |
||||
|
modalApi.setData({}); |
||||
|
modalApi.open(); |
||||
|
} |
||||
|
|
||||
|
function onDelete(row: OssContainerDto) { |
||||
|
Modal.confirm({ |
||||
|
centered: true, |
||||
|
content: $t('AbpUi.ItemWillBeDeletedMessageWithFormat', [row.name]), |
||||
|
onCancel: () => { |
||||
|
cancel(); |
||||
|
}, |
||||
|
onOk: async () => { |
||||
|
await deleteApi(row.name); |
||||
|
message.success($t('AbpUi.DeletedSuccessfully')); |
||||
|
await gridApi.query(); |
||||
|
}, |
||||
|
title: $t('AbpUi.AreYouSure'), |
||||
|
}); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Grid :table-title="$t('AbpOssManagement.Containers')"> |
||||
|
<template #toolbar-tools> |
||||
|
<Button :icon="h(PlusOutlined)" type="primary" @click="onCreate"> |
||||
|
{{ $t('AbpOssManagement.Containers:Create') }} |
||||
|
</Button> |
||||
|
</template> |
||||
|
<template #action="{ row }"> |
||||
|
<div class="flex flex-row"> |
||||
|
<Button |
||||
|
:icon="h(DeleteOutlined)" |
||||
|
danger |
||||
|
block |
||||
|
type="link" |
||||
|
@click="onDelete(row)" |
||||
|
> |
||||
|
{{ $t('AbpUi.Delete') }} |
||||
|
</Button> |
||||
|
</div> |
||||
|
</template> |
||||
|
</Grid> |
||||
|
<ContainerModal @change="() => gridApi.query()" /> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped></style> |
||||
@ -0,0 +1,2 @@ |
|||||
|
export { default as ContainerTable } from './containers/ContainerTable.vue'; |
||||
|
export { default as ObjectPage } from './objects/ObjectPage.vue'; |
||||
@ -0,0 +1,282 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { VxeGridListeners, VxeGridProps } from '@abp/ui'; |
||||
|
|
||||
|
import type { OssContainerDto } from '../../types/containes'; |
||||
|
import type { OssObjectDto } from '../../types/objects'; |
||||
|
|
||||
|
import { defineAsyncComponent, h, nextTick, ref, watch } from 'vue'; |
||||
|
|
||||
|
import { useAccess } from '@vben/access'; |
||||
|
import { useVbenModal } from '@vben/common-ui'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { formatToDateTime, isNullOrWhiteSpace } from '@abp/core'; |
||||
|
import { useVbenVxeGrid } from '@abp/ui'; |
||||
|
import { |
||||
|
DeleteOutlined, |
||||
|
DownloadOutlined, |
||||
|
UploadOutlined, |
||||
|
} from '@ant-design/icons-vue'; |
||||
|
import { Button, message, Modal } from 'ant-design-vue'; |
||||
|
|
||||
|
import { useObjectsApi } from '../../api'; |
||||
|
import { useContainesApi } from '../../api/useContainesApi'; |
||||
|
import { OssObjectPermissions } from '../../constants/permissions'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'FileList', |
||||
|
}); |
||||
|
|
||||
|
const props = defineProps<{ |
||||
|
bucket: string; |
||||
|
path: string; |
||||
|
}>(); |
||||
|
|
||||
|
const kbUnit = 1 * 1024; |
||||
|
const mbUnit = kbUnit * 1024; |
||||
|
const gbUnit = mbUnit * 1024; |
||||
|
|
||||
|
const { hasAccessByCodes } = useAccess(); |
||||
|
const { cancel, getObjectsApi } = useContainesApi(); |
||||
|
const { deleteApi, generateUrlApi } = useObjectsApi(); |
||||
|
|
||||
|
const selectedKeys = ref<string[]>([]); |
||||
|
|
||||
|
const gridOptions: VxeGridProps<OssContainerDto> = { |
||||
|
columns: [ |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'name', |
||||
|
minWidth: 150, |
||||
|
title: $t('AbpOssManagement.DisplayName:Name'), |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'isFolder', |
||||
|
formatter: ({ cellValue }) => { |
||||
|
return cellValue |
||||
|
? $t('AbpOssManagement.DisplayName:Folder') |
||||
|
: $t('AbpOssManagement.DisplayName:Standard'); |
||||
|
}, |
||||
|
minWidth: 150, |
||||
|
title: $t('AbpOssManagement.DisplayName:FileType'), |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'size', |
||||
|
formatter: ({ cellValue }) => { |
||||
|
const size = Number(cellValue); |
||||
|
if (size > gbUnit) { |
||||
|
let gb = Math.round(size / gbUnit); |
||||
|
if (gb < 1) { |
||||
|
gb = 1; |
||||
|
} |
||||
|
return `${gb} GB`; |
||||
|
} |
||||
|
if (size > mbUnit) { |
||||
|
let mb = Math.round(size / mbUnit); |
||||
|
if (mb < 1) { |
||||
|
mb = 1; |
||||
|
} |
||||
|
return `${mb} MB`; |
||||
|
} |
||||
|
let kb = Math.round(size / kbUnit); |
||||
|
if (kb < 1) { |
||||
|
kb = 1; |
||||
|
} |
||||
|
return `${kb} KB`; |
||||
|
}, |
||||
|
minWidth: 150, |
||||
|
title: $t('AbpOssManagement.DisplayName:Size'), |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'creationDate', |
||||
|
formatter({ cellValue }) { |
||||
|
return cellValue ? formatToDateTime(cellValue) : ''; |
||||
|
}, |
||||
|
minWidth: 120, |
||||
|
title: $t('AbpOssManagement.DisplayName:CreationDate'), |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'lastModifiedDate', |
||||
|
formatter({ cellValue }) { |
||||
|
return cellValue ? formatToDateTime(cellValue) : ''; |
||||
|
}, |
||||
|
minWidth: 120, |
||||
|
title: $t('AbpOssManagement.DisplayName:LastModifiedDate'), |
||||
|
}, |
||||
|
{ |
||||
|
field: 'action', |
||||
|
fixed: 'right', |
||||
|
slots: { default: 'action' }, |
||||
|
title: $t('AbpUi.Actions'), |
||||
|
width: 200, |
||||
|
}, |
||||
|
], |
||||
|
exportConfig: {}, |
||||
|
keepSource: true, |
||||
|
proxyConfig: { |
||||
|
ajax: { |
||||
|
query: async ({ page }) => { |
||||
|
const res = await getObjectsApi({ |
||||
|
bucket: props.bucket, |
||||
|
maxResultCount: page.pageSize, |
||||
|
prefix: props.path, |
||||
|
skipCount: (page.currentPage - 1) * page.pageSize, |
||||
|
}); |
||||
|
return { |
||||
|
totalCount: res.maxKeys, |
||||
|
items: res.objects, |
||||
|
}; |
||||
|
}, |
||||
|
}, |
||||
|
autoLoad: false, |
||||
|
response: { |
||||
|
total: 'totalCount', |
||||
|
list: 'items', |
||||
|
}, |
||||
|
}, |
||||
|
toolbarConfig: { |
||||
|
custom: true, |
||||
|
export: false, |
||||
|
refresh: false, |
||||
|
zoom: true, |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
const gridEvents: VxeGridListeners<OssContainerDto> = { |
||||
|
checkboxAll: (params) => { |
||||
|
selectedKeys.value = params.records.map((record) => record.name); |
||||
|
}, |
||||
|
checkboxChange: (params) => { |
||||
|
selectedKeys.value = params.records.map((record) => record.name); |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
const [Grid, gridApi] = useVbenVxeGrid({ |
||||
|
gridEvents, |
||||
|
gridOptions, |
||||
|
}); |
||||
|
|
||||
|
const [FileUploadModal, modalApi] = useVbenModal({ |
||||
|
connectedComponent: defineAsyncComponent( |
||||
|
() => import('./FileUploadModal.vue'), |
||||
|
), |
||||
|
}); |
||||
|
|
||||
|
function onUpload() { |
||||
|
modalApi.setData({ |
||||
|
bucket: props.bucket, |
||||
|
path: props.path, |
||||
|
}); |
||||
|
modalApi.open(); |
||||
|
} |
||||
|
|
||||
|
function onDelete(row: OssObjectDto) { |
||||
|
Modal.confirm({ |
||||
|
centered: true, |
||||
|
content: $t('AbpUi.ItemWillBeDeletedMessageWithFormat', [row.name]), |
||||
|
onCancel: () => { |
||||
|
cancel(); |
||||
|
}, |
||||
|
onOk: async () => { |
||||
|
await deleteApi({ |
||||
|
bucket: props.bucket, |
||||
|
mD5: false, |
||||
|
object: row.name, |
||||
|
path: row.path, |
||||
|
}); |
||||
|
message.success($t('AbpUi.DeletedSuccessfully')); |
||||
|
await gridApi.query(); |
||||
|
}, |
||||
|
title: $t('AbpUi.AreYouSure'), |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
async function onDownload(row: OssObjectDto) { |
||||
|
const downloadUrl = await generateUrlApi({ |
||||
|
bucket: props.bucket, |
||||
|
mD5: false, |
||||
|
object: row.name, |
||||
|
path: row.path, |
||||
|
}); |
||||
|
const link = document.createElement('a'); |
||||
|
link.style.display = 'none'; |
||||
|
link.href = downloadUrl; |
||||
|
link.setAttribute('download', row.name); |
||||
|
document.body.append(link); |
||||
|
link.click(); |
||||
|
} |
||||
|
|
||||
|
watch( |
||||
|
() => props.bucket, |
||||
|
(bucket) => { |
||||
|
nextTick(() => { |
||||
|
gridApi.setGridOptions({ |
||||
|
toolbarConfig: { |
||||
|
refresh: !isNullOrWhiteSpace(bucket), |
||||
|
}, |
||||
|
}); |
||||
|
if (!isNullOrWhiteSpace(bucket)) { |
||||
|
gridApi.query(); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
watch( |
||||
|
() => props.path, |
||||
|
(newVal, oldVal) => { |
||||
|
if (newVal !== oldVal) { |
||||
|
nextTick(() => { |
||||
|
gridApi.query(); |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Grid :table-title="$t('AbpOssManagement.FileList')"> |
||||
|
<template #toolbar-tools> |
||||
|
<Button |
||||
|
v-if="props.path" |
||||
|
:icon="h(UploadOutlined)" |
||||
|
type="primary" |
||||
|
@click="onUpload" |
||||
|
> |
||||
|
{{ $t('AbpOssManagement.Objects:UploadFile') }} |
||||
|
</Button> |
||||
|
</template> |
||||
|
<template #action="{ row }"> |
||||
|
<div class="flex flex-row"> |
||||
|
<Button |
||||
|
v-if=" |
||||
|
!row.isFolder && hasAccessByCodes([OssObjectPermissions.Download]) |
||||
|
" |
||||
|
:icon="h(DownloadOutlined)" |
||||
|
block |
||||
|
type="link" |
||||
|
@click="onDownload(row)" |
||||
|
> |
||||
|
{{ $t('AbpOssManagement.Objects:Download') }} |
||||
|
</Button> |
||||
|
<Button |
||||
|
v-if="hasAccessByCodes([OssObjectPermissions.Delete])" |
||||
|
:icon="h(DeleteOutlined)" |
||||
|
danger |
||||
|
block |
||||
|
type="link" |
||||
|
@click="onDelete(row)" |
||||
|
> |
||||
|
{{ $t('AbpUi.Delete') }} |
||||
|
</Button> |
||||
|
</div> |
||||
|
</template> |
||||
|
</Grid> |
||||
|
<FileUploadModal @file-uploaded="() => gridApi.query()" /> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped></style> |
||||
@ -0,0 +1,286 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { VxeComponentStyleType } from 'vxe-table'; |
||||
|
|
||||
|
import { h, ref, useTemplateRef, watch } from 'vue'; |
||||
|
import uploader from 'vue-simple-uploader'; |
||||
|
import 'vue-simple-uploader/dist/style.css'; |
||||
|
|
||||
|
import { useVbenModal } from '@vben/common-ui'; |
||||
|
import { useRefresh } from '@vben/hooks'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
import { useAccessStore } from '@vben/stores'; |
||||
|
|
||||
|
import { isNullOrWhiteSpace } from '@abp/core'; |
||||
|
import { |
||||
|
CaretRightOutlined, |
||||
|
DeleteOutlined, |
||||
|
PauseOutlined, |
||||
|
} from '@ant-design/icons-vue'; |
||||
|
import { Button, Tag, Tooltip } from 'ant-design-vue'; |
||||
|
import { VxeColumn, VxeTable } from 'vxe-table'; |
||||
|
|
||||
|
const emits = defineEmits<{ |
||||
|
(event: 'fileUploaded', file: any): void; |
||||
|
}>(); |
||||
|
const Uploader = uploader.Uploader; |
||||
|
const UploaderDrop = uploader.UploaderDrop; |
||||
|
const UploaderList = uploader.UploaderList; |
||||
|
const UploaderUnsupport = uploader.UploaderUnsupport; |
||||
|
|
||||
|
interface ModalState { |
||||
|
bucket: string; |
||||
|
path: string; |
||||
|
} |
||||
|
|
||||
|
const selectBtn = useTemplateRef<any>('selectBtn'); |
||||
|
const uploaderWrap = useTemplateRef<any>('uploaderWrap'); |
||||
|
|
||||
|
const { refresh } = useRefresh(); |
||||
|
const accessStore = useAccessStore(); |
||||
|
|
||||
|
const [Modal, modalApi] = useVbenModal({ |
||||
|
class: 'w-1/2', |
||||
|
closeOnClickModal: false, |
||||
|
closeOnPressEscape: false, |
||||
|
draggable: true, |
||||
|
footer: false, |
||||
|
onCancel: () => { |
||||
|
uploaderWrap.value?.uploader.cancel(); |
||||
|
}, |
||||
|
onOpenChange: (isOpen) => { |
||||
|
if (isOpen) { |
||||
|
onInit(); |
||||
|
} |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const options = ref({ |
||||
|
chunkRetryInterval: null, |
||||
|
headers: {}, |
||||
|
initialPaused: true, |
||||
|
maxChunkRetries: 3, |
||||
|
permanentErrors: [400, 401, 403, 404, 415, 500, 501], |
||||
|
processParams: (params: any) => params, |
||||
|
processResponse: (response: any, cb: any) => { |
||||
|
if (!isNullOrWhiteSpace(response)) { |
||||
|
const error = JSON.parse(response); |
||||
|
if (error.code !== '0') { |
||||
|
cb(true, error.message); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
cb(null, response); |
||||
|
}, |
||||
|
successStatuses: [200, 201, 202, 204, 205], |
||||
|
target: '/api/oss-management/objects/upload', |
||||
|
testChunks: false, |
||||
|
}); |
||||
|
|
||||
|
function onInit() { |
||||
|
const state = modalApi.getData<ModalState>(); |
||||
|
options.value = { |
||||
|
...options.value, |
||||
|
headers: { |
||||
|
Authorization: accessStore.accessToken, |
||||
|
}, |
||||
|
processParams: (params: any) => { |
||||
|
params.bucket = state.bucket; |
||||
|
params.path = state.path; |
||||
|
return params; |
||||
|
}, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function onSelectFiles() { |
||||
|
selectBtn.value?.click(); |
||||
|
} |
||||
|
|
||||
|
function onResume(file: any) { |
||||
|
if (file.error) { |
||||
|
file.errorMsg = ''; |
||||
|
file.retry(); |
||||
|
} else { |
||||
|
file.resume(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function onPause(file: any) { |
||||
|
file.pause(); |
||||
|
} |
||||
|
|
||||
|
function onDelete(file: any) { |
||||
|
file.cancel(); |
||||
|
} |
||||
|
|
||||
|
function onFileSubmitted(_: any, files: any[]) { |
||||
|
files.forEach((f) => { |
||||
|
f.paused = true; |
||||
|
f.completed = false; |
||||
|
f.progress = 0; |
||||
|
f.progressText = '0 %'; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function onUploadProgress(_: any, file: any) { |
||||
|
if (file._prevUploadedSize) { |
||||
|
const progress = Math.floor((file._prevUploadedSize / file.size) * 100); |
||||
|
file.progress = progress; |
||||
|
file.progressText = `${progress} %`; |
||||
|
file.completed = progress === 100; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function onUploadError(_rootFile: any, file: any, message: any, chunk: any) { |
||||
|
if (chunk?.xhr?.status === 401) { |
||||
|
// 401 错误代码刷新页面, 由axios拦截器刷新token |
||||
|
refresh(); |
||||
|
} else { |
||||
|
file.errorMsg = message; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function onUploadSuccess(_rootFile: any, file: any) { |
||||
|
emits('fileUploaded', file); |
||||
|
} |
||||
|
|
||||
|
function formatSize(size: number) { |
||||
|
if (size < 1024) { |
||||
|
return `${size.toFixed(0)} bytes`; |
||||
|
} else if (size < 1024 * 1024) { |
||||
|
return `${(size / 1024).toFixed(0)} KB`; |
||||
|
} else if (size < 1024 * 1024 * 1024) { |
||||
|
return `${(size / 1024 / 1024).toFixed(1)} MB`; |
||||
|
} else { |
||||
|
return `${(size / 1024 / 1024 / 1024).toFixed(1)} GB`; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function rowStyle(params: any): VxeComponentStyleType { |
||||
|
// console.log('上传状态 --->', params.row.status); |
||||
|
if (params.row.error) { |
||||
|
return { |
||||
|
background: '#ffe0e0', |
||||
|
}; |
||||
|
} |
||||
|
const startColor = 'rgba(255, 200, 200, 0.7)'; |
||||
|
const endColor = 'rgba(200, 255, 200, 0.7)'; |
||||
|
return { |
||||
|
background: `linear-gradient( |
||||
|
to right, |
||||
|
${startColor} 0%, |
||||
|
${endColor} ${params.row.progress}%, |
||||
|
transparent ${params.row.progress}%, |
||||
|
transparent 100% |
||||
|
)`, |
||||
|
backgroundSize: '100% 100%', |
||||
|
transition: 'background 0.5s ease', |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
watch( |
||||
|
() => [selectBtn.value, uploaderWrap.value], |
||||
|
([button, wrap]) => { |
||||
|
if (button && wrap) { |
||||
|
wrap.uploader.assignBrowse(button); |
||||
|
} |
||||
|
}, |
||||
|
); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Modal :title="$t('AbpOssManagement.Objects:UploadFile')"> |
||||
|
<Uploader |
||||
|
:options="options" |
||||
|
ref="uploaderWrap" |
||||
|
@file-error="onUploadError" |
||||
|
@file-progress="onUploadProgress" |
||||
|
@file-success="onUploadSuccess" |
||||
|
@files-submitted="onFileSubmitted" |
||||
|
> |
||||
|
<UploaderUnsupport /> |
||||
|
<UploaderDrop> |
||||
|
<div class="flex flex-row gap-2"> |
||||
|
<input ref="selectBtn" style="display: none" /> |
||||
|
<Button type="primary" @click="onSelectFiles"> |
||||
|
{{ $t('AbpOssManagement.Upload:SelectFile') }} |
||||
|
</Button> |
||||
|
</div> |
||||
|
</UploaderDrop> |
||||
|
<UploaderList> |
||||
|
<template #default="{ fileList }"> |
||||
|
<VxeTable :data="fileList" :row-style="rowStyle"> |
||||
|
<VxeColumn type="seq" width="70" /> |
||||
|
<VxeColumn |
||||
|
field="name" |
||||
|
:title="$t('AbpOssManagement.DisplayName:Name')" |
||||
|
/> |
||||
|
<VxeColumn |
||||
|
field="size" |
||||
|
:title="$t('AbpOssManagement.DisplayName:Size')" |
||||
|
width="100" |
||||
|
> |
||||
|
<template #default="{ row }"> |
||||
|
<span>{{ formatSize(row.size) }}</span> |
||||
|
</template> |
||||
|
</VxeColumn> |
||||
|
<VxeColumn |
||||
|
field="status" |
||||
|
:title="$t('AbpOssManagement.DisplayName:Status')" |
||||
|
width="180" |
||||
|
> |
||||
|
<template #default="{ row }"> |
||||
|
<Tag v-if="row.completed" color="green"> |
||||
|
{{ $t('AbpOssManagement.Upload:Completed') }} |
||||
|
</Tag> |
||||
|
<Tooltip v-else-if="row.error" :title="row.errorMsg"> |
||||
|
<Tag color="red"> |
||||
|
{{ $t('AbpOssManagement.Upload:Error') }} |
||||
|
</Tag> |
||||
|
</Tooltip> |
||||
|
<Tag v-else-if="row.paused" color="orange"> |
||||
|
{{ $t('AbpOssManagement.Upload:Pause') }} |
||||
|
</Tag> |
||||
|
<span v-else>{{ |
||||
|
`${row.progressText} ${formatSize(row.averageSpeed)}/s` |
||||
|
}}</span> |
||||
|
</template> |
||||
|
</VxeColumn> |
||||
|
<VxeColumn |
||||
|
fixed="right" |
||||
|
field="action" |
||||
|
:title="$t('AbpUi.Actions')" |
||||
|
width="100" |
||||
|
> |
||||
|
<template #default="{ row }"> |
||||
|
<div class="flex flex-row"> |
||||
|
<div v-if="!row.completed"> |
||||
|
<Button |
||||
|
v-if="row.paused || row.error" |
||||
|
:icon="h(CaretRightOutlined)" |
||||
|
@click="onResume(row)" |
||||
|
type="link" |
||||
|
/> |
||||
|
<Button |
||||
|
v-else |
||||
|
:icon="h(PauseOutlined)" |
||||
|
@click="onPause(row)" |
||||
|
type="link" |
||||
|
/> |
||||
|
</div> |
||||
|
<Button |
||||
|
:icon="h(DeleteOutlined)" |
||||
|
@click="onDelete(row)" |
||||
|
type="link" |
||||
|
danger |
||||
|
/> |
||||
|
</div> |
||||
|
</template> |
||||
|
</VxeColumn> |
||||
|
</VxeTable> |
||||
|
</template> |
||||
|
</UploaderList> |
||||
|
</Uploader> |
||||
|
</Modal> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped></style> |
||||
@ -0,0 +1,66 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { OssObjectDto } from '../../types/objects'; |
||||
|
|
||||
|
import { useVbenForm, useVbenModal } from '@vben/common-ui'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { message } from 'ant-design-vue'; |
||||
|
|
||||
|
import { useObjectsApi } from '../../api'; |
||||
|
|
||||
|
interface ModalState { |
||||
|
bucket: string; |
||||
|
path?: string; |
||||
|
} |
||||
|
|
||||
|
const emits = defineEmits<{ |
||||
|
(event: 'change', data: OssObjectDto): void; |
||||
|
}>(); |
||||
|
|
||||
|
const { createApi } = useObjectsApi(); |
||||
|
|
||||
|
const [Form, formApi] = useVbenForm({ |
||||
|
handleSubmit: onSubmit, |
||||
|
schema: [ |
||||
|
{ |
||||
|
component: 'Input', |
||||
|
fieldName: 'name', |
||||
|
label: $t('AbpOssManagement.DisplayName:Name'), |
||||
|
rules: 'required', |
||||
|
}, |
||||
|
], |
||||
|
showDefaultActions: false, |
||||
|
}); |
||||
|
|
||||
|
const [Modal, modalApi] = useVbenModal({ |
||||
|
onConfirm: async () => { |
||||
|
await formApi.validateAndSubmitForm(); |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
async function onSubmit(values: Record<string, any>) { |
||||
|
try { |
||||
|
const state = modalApi.getData<ModalState>(); |
||||
|
modalApi.setState({ submitting: true }); |
||||
|
const dto = await createApi({ |
||||
|
bucket: state.bucket, |
||||
|
fileName: values.name, |
||||
|
overwrite: false, |
||||
|
path: state.path, |
||||
|
}); |
||||
|
message.success($t('AbpUi.SavedSuccessfully')); |
||||
|
emits('change', dto); |
||||
|
modalApi.close(); |
||||
|
} finally { |
||||
|
modalApi.setState({ submitting: false }); |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Modal :title="$t('AbpOssManagement.Objects:CreateFolder')"> |
||||
|
<Form /> |
||||
|
</Modal> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped></style> |
||||
@ -0,0 +1,192 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { EventDataNode, Key } from 'ant-design-vue/es/vc-tree/interface'; |
||||
|
import type { TreeProps } from 'ant-design-vue/es/vc-tree/props'; |
||||
|
|
||||
|
import type { OssContainerDto } from '../../types'; |
||||
|
import type { OssObjectDto } from '../../types/objects'; |
||||
|
|
||||
|
import { defineAsyncComponent, onMounted, ref } from 'vue'; |
||||
|
|
||||
|
import { useVbenModal } from '@vben/common-ui'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { Button, Card, DirectoryTree, Empty, Select } from 'ant-design-vue'; |
||||
|
|
||||
|
import { useContainesApi } from '../../api'; |
||||
|
|
||||
|
const emits = defineEmits<{ |
||||
|
(event: 'bucketChange', data: string): void; |
||||
|
(event: 'folderChange', data: string): void; |
||||
|
}>(); |
||||
|
|
||||
|
interface Folder { |
||||
|
children?: Folder[]; |
||||
|
isLeaf?: boolean; |
||||
|
key: string; |
||||
|
name: string; |
||||
|
path?: string; |
||||
|
title: string; |
||||
|
} |
||||
|
|
||||
|
const { getListApi: getContainersApi, getObjectsApi } = useContainesApi(); |
||||
|
|
||||
|
const [FolderModal, modalApi] = useVbenModal({ |
||||
|
connectedComponent: defineAsyncComponent(() => import('./FolderModal.vue')), |
||||
|
}); |
||||
|
|
||||
|
const rootFolder: Folder = { |
||||
|
isLeaf: false, |
||||
|
key: './', |
||||
|
name: './', |
||||
|
path: '', |
||||
|
title: $t('AbpOssManagement.Objects:Root'), |
||||
|
children: [], |
||||
|
}; |
||||
|
|
||||
|
const bucket = ref<string>(''); |
||||
|
const loadedFolders = ref<string[]>([]); |
||||
|
const expandedFolders = ref<string[]>([]); |
||||
|
const selectedFolders = ref<string[]>([]); |
||||
|
const containers = ref<OssContainerDto[]>([]); |
||||
|
const folders = ref<Folder[]>([ |
||||
|
{ |
||||
|
...rootFolder, |
||||
|
}, |
||||
|
]); |
||||
|
|
||||
|
const onLoadChildFolders: TreeProps['loadData'] = async (treeNode) => { |
||||
|
let path = ''; |
||||
|
if (treeNode.dataRef?.path) { |
||||
|
path = path + treeNode.dataRef?.path; |
||||
|
} |
||||
|
if (treeNode.dataRef?.name) { |
||||
|
path = path + treeNode.dataRef?.name; |
||||
|
} |
||||
|
try { |
||||
|
treeNode.dataRef!.children = await getFolders(bucket.value!, path); |
||||
|
} catch { |
||||
|
treeNode.dataRef!.children = []; |
||||
|
} |
||||
|
folders.value = [...folders.value]; |
||||
|
loadedFolders.value = [...loadedFolders.value, treeNode.key.toString()]; |
||||
|
}; |
||||
|
|
||||
|
async function onInit() { |
||||
|
const getContainersRes = await getContainersApi({ |
||||
|
maxResultCount: 1000, |
||||
|
}); |
||||
|
containers.value = getContainersRes.containers; |
||||
|
} |
||||
|
|
||||
|
async function getFolders(bucket: string, path?: string) { |
||||
|
const { objects } = await getObjectsApi({ |
||||
|
bucket, |
||||
|
delimiter: '/', |
||||
|
maxResultCount: 1000, |
||||
|
prefix: path ?? '', |
||||
|
}); |
||||
|
return objects |
||||
|
.filter((f) => f.isFolder) |
||||
|
.map((folder) => { |
||||
|
return { |
||||
|
isLeaf: false, |
||||
|
key: `${folder.path ?? ''}${folder.name}`, |
||||
|
name: folder.name, |
||||
|
path: folder.path, |
||||
|
title: folder.name, |
||||
|
children: [], |
||||
|
}; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function onFolderExpand( |
||||
|
_: Key[], |
||||
|
info: { |
||||
|
expanded: boolean; |
||||
|
node: EventDataNode; |
||||
|
}, |
||||
|
) { |
||||
|
if (!info.expanded) { |
||||
|
const keys = loadedFolders.value; |
||||
|
const findIndex = keys.lastIndexOf(info.node.key.toString()); |
||||
|
findIndex !== -1 && keys.splice(findIndex); |
||||
|
loadedFolders.value = keys; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function onFolderChange(selectedKeys: Key[]) { |
||||
|
if (selectedKeys.length === 1) { |
||||
|
emits('folderChange', selectedKeys[0]!.toString()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function onBucketChange(bucket: string) { |
||||
|
emits('bucketChange', bucket); |
||||
|
expandedFolders.value = []; |
||||
|
loadedFolders.value = []; |
||||
|
folders.value = [ |
||||
|
{ |
||||
|
...rootFolder, |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
function onCreate() { |
||||
|
modalApi.setData({ |
||||
|
bucket: bucket.value, |
||||
|
path: selectedFolders.value[0], |
||||
|
}); |
||||
|
modalApi.open(); |
||||
|
} |
||||
|
|
||||
|
function onFolderCreated(ossObject: OssObjectDto) { |
||||
|
const keys = expandedFolders.value; |
||||
|
const findIndex = keys.lastIndexOf(ossObject.path); |
||||
|
if (findIndex !== -1) { |
||||
|
keys.splice(findIndex); |
||||
|
expandedFolders.value = keys; |
||||
|
loadedFolders.value = []; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onMounted(onInit); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Card :title="$t('AbpOssManagement.Containers')"> |
||||
|
<div class="flex flex-col gap-2"> |
||||
|
<Select |
||||
|
:placeholder="$t('AbpOssManagement.Containers:Select')" |
||||
|
:options="containers" |
||||
|
:field-names="{ label: 'name', value: 'name' }" |
||||
|
v-model:value="bucket" |
||||
|
@change="(e) => onBucketChange(e!.toString())" |
||||
|
/> |
||||
|
<Button v-if="bucket" block type="primary" ghost @click="onCreate"> |
||||
|
{{ $t('AbpOssManagement.Objects:CreateFolder') }} |
||||
|
</Button> |
||||
|
<DirectoryTree |
||||
|
v-if="bucket" |
||||
|
block-node |
||||
|
v-model:expanded-keys="expandedFolders" |
||||
|
v-model:selected-keys="selectedFolders" |
||||
|
:loaded-keys="loadedFolders" |
||||
|
:tree-data="folders" |
||||
|
:load-data="onLoadChildFolders" |
||||
|
@select="onFolderChange" |
||||
|
@expand="onFolderExpand" |
||||
|
/> |
||||
|
<Empty v-else /> |
||||
|
</div> |
||||
|
</Card> |
||||
|
<FolderModal @change="onFolderCreated" /> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
:deep(.ant-tree) { |
||||
|
.ant-tree-title { |
||||
|
word-break: break-word; |
||||
|
white-space: normal; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,34 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { defineAsyncComponent, ref } from 'vue'; |
||||
|
|
||||
|
const FolderTree = defineAsyncComponent(() => import('./FolderTree.vue')); |
||||
|
const FileList = defineAsyncComponent(() => import('./FileList.vue')); |
||||
|
|
||||
|
const bucket = ref(''); |
||||
|
const path = ref(''); |
||||
|
|
||||
|
function onBucketChange(val: string) { |
||||
|
bucket.value = val; |
||||
|
path.value = ''; |
||||
|
} |
||||
|
|
||||
|
function onFolderChange(val: string) { |
||||
|
path.value = val; |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div class="flex flex-row gap-2"> |
||||
|
<div style="width: 30%"> |
||||
|
<FolderTree |
||||
|
@bucket-change="onBucketChange" |
||||
|
@folder-change="onFolderChange" |
||||
|
/> |
||||
|
</div> |
||||
|
<div style="width: 70%"> |
||||
|
<FileList :bucket="bucket" :path="path" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped></style> |
||||
@ -0,0 +1,18 @@ |
|||||
|
/** 容器权限 */ |
||||
|
export const ContainerPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: 'AbpOssManagement.Container.Create', |
||||
|
Default: 'AbpOssManagement.Container', |
||||
|
/** 删除 */ |
||||
|
Delete: 'AbpOssManagement.Container.Delete', |
||||
|
}; |
||||
|
/** 容器权限 */ |
||||
|
export const OssObjectPermissions = { |
||||
|
/** 新增 */ |
||||
|
Create: 'AbpOssManagement.OssObject.Create', |
||||
|
Default: 'AbpOssManagement.OssObject', |
||||
|
/** 删除 */ |
||||
|
Delete: 'AbpOssManagement.OssObject.Delete', |
||||
|
/** 下载 */ |
||||
|
Download: 'AbpOssManagement.OssObject.Download', |
||||
|
}; |
||||
@ -0,0 +1 @@ |
|||||
|
declare module 'vue-simple-uploader'; |
||||
@ -0,0 +1,3 @@ |
|||||
|
export * from './api'; |
||||
|
export * from './components'; |
||||
|
export * from './types'; |
||||
@ -0,0 +1,38 @@ |
|||||
|
import type { PagedAndSortedResultRequestDto } from '@abp/core'; |
||||
|
|
||||
|
interface OssContainerDto { |
||||
|
creationDate: Date; |
||||
|
lastModifiedDate?: Date; |
||||
|
metadata: Record<string, string>; |
||||
|
name: string; |
||||
|
size: number; |
||||
|
} |
||||
|
|
||||
|
interface OssContainersResultDto { |
||||
|
containers: OssContainerDto[]; |
||||
|
marker?: string; |
||||
|
maxKeys?: number; |
||||
|
nextMarker?: string; |
||||
|
prefix?: string; |
||||
|
} |
||||
|
|
||||
|
interface GetOssContainersInput extends PagedAndSortedResultRequestDto { |
||||
|
marker?: string; |
||||
|
prefix?: string; |
||||
|
} |
||||
|
|
||||
|
interface GetOssObjectsInput extends PagedAndSortedResultRequestDto { |
||||
|
bucket?: string; |
||||
|
delimiter?: string; |
||||
|
encodingType?: string; |
||||
|
marker?: string; |
||||
|
mD5?: string; |
||||
|
prefix?: string; |
||||
|
} |
||||
|
|
||||
|
export type { |
||||
|
GetOssContainersInput, |
||||
|
GetOssObjectsInput, |
||||
|
OssContainerDto, |
||||
|
OssContainersResultDto, |
||||
|
}; |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue