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 { 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 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 './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 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