committed by
GitHub
41 changed files with 2037 additions and 5 deletions
@ -0,0 +1,15 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
|
||||
|
import { AuditLogTable } from '@abp/auditing'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'AuditingAuditLogs', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page> |
||||
|
<AuditLogTable /> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -0,0 +1,37 @@ |
|||||
|
{ |
||||
|
"name": "@abp/auditing", |
||||
|
"version": "8.3.2", |
||||
|
"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/auditing" |
||||
|
}, |
||||
|
"license": "MIT", |
||||
|
"type": "module", |
||||
|
"sideEffects": [ |
||||
|
"**/*.css" |
||||
|
], |
||||
|
"exports": { |
||||
|
".": { |
||||
|
"types": "./src/index.ts", |
||||
|
"default": "./src/index.ts" |
||||
|
} |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"@abp/core": "workspace:*", |
||||
|
"@abp/request": "workspace:*", |
||||
|
"@abp/ui": "workspace:*", |
||||
|
"@ant-design/icons-vue": "catalog:", |
||||
|
"@vben/access": "workspace:*", |
||||
|
"@vben/common-ui": "workspace:*", |
||||
|
"@vben/hooks": "workspace:*", |
||||
|
"@vben/icons": "workspace:*", |
||||
|
"@vben/layouts": "workspace:*", |
||||
|
"@vben/locales": "workspace:*", |
||||
|
"ant-design-vue": "catalog:", |
||||
|
"vue": "catalog:*", |
||||
|
"vxe-table": "catalog:" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
import type { PagedResultDto } from '@abp/core'; |
||||
|
|
||||
|
import type { AuditLogDto, AuditLogGetListInput } from '../types/audit-logs'; |
||||
|
|
||||
|
import { requestClient } from '@abp/request'; |
||||
|
|
||||
|
/** |
||||
|
* 获取审计日志 |
||||
|
* @param id 日志id |
||||
|
*/ |
||||
|
export function getApi(id: string): Promise<AuditLogDto> { |
||||
|
return requestClient.get<AuditLogDto>(`/api/auditing/audit-log/${id}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取审计日志分页列表 |
||||
|
* @param input 参数 |
||||
|
*/ |
||||
|
export function getPagedListApi( |
||||
|
input: AuditLogGetListInput, |
||||
|
): Promise<PagedResultDto<AuditLogDto>> { |
||||
|
return requestClient.get<PagedResultDto<AuditLogDto>>( |
||||
|
'/api/auditing/audit-log', |
||||
|
{ |
||||
|
params: input, |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除审计日志 |
||||
|
* @param id 日志id |
||||
|
*/ |
||||
|
export function deleteApi(id: string): Promise<void> { |
||||
|
return requestClient.delete(`/api/auditing/audit-log/${id}`); |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
import type { ListResultDto } from '@abp/core'; |
||||
|
|
||||
|
import type { |
||||
|
EntityChangeGetWithUsernameInput, |
||||
|
EntityChangeWithUsernameDto, |
||||
|
} from '../types/entity-changes'; |
||||
|
|
||||
|
import { requestClient } from '@abp/request'; |
||||
|
|
||||
|
/** |
||||
|
* 获取包含用户名称的实体变更列表 |
||||
|
* @param input 参数 |
||||
|
*/ |
||||
|
export function getListWithUsernameApi( |
||||
|
input: EntityChangeGetWithUsernameInput, |
||||
|
): Promise<ListResultDto<EntityChangeWithUsernameDto>> { |
||||
|
return requestClient.get<ListResultDto<EntityChangeWithUsernameDto>>( |
||||
|
'/api/auditing/entity-changes/with-username', |
||||
|
{ |
||||
|
params: input, |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,240 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { VxeGridProps } from 'vxe-table'; |
||||
|
|
||||
|
import type { Action, AuditLogDto } from '../../types/audit-logs'; |
||||
|
|
||||
|
import { ref } from 'vue'; |
||||
|
|
||||
|
import { useVbenDrawer } from '@vben/common-ui'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { formatToDateTime } from '@abp/core'; |
||||
|
import { CodeEditor, MODE, useVbenVxeGrid } from '@abp/ui'; |
||||
|
import { Descriptions, Tabs, Tag } from 'ant-design-vue'; |
||||
|
|
||||
|
import { getApi } from '../../api/audit-logs'; |
||||
|
import { useAuditlogs } from '../../hooks/useAuditlogs'; |
||||
|
import EntityChangeTable from '../entity-changes/EntityChangeTable.vue'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'AuditLogDrawer', |
||||
|
}); |
||||
|
|
||||
|
const TabPane = Tabs.TabPane; |
||||
|
const DescriptionsItem = Descriptions.Item; |
||||
|
|
||||
|
const activedTab = ref('basic'); |
||||
|
const auditLogModel = ref<AuditLogDto>({} as AuditLogDto); |
||||
|
|
||||
|
const { getHttpMethodColor, getHttpStatusCodeColor } = useAuditlogs(); |
||||
|
const [Drawer, drawerApi] = useVbenDrawer({ |
||||
|
class: 'w-auto', |
||||
|
onCancel() { |
||||
|
drawerApi.close(); |
||||
|
}, |
||||
|
onConfirm: async () => {}, |
||||
|
onOpenChange: async (isOpen: boolean) => { |
||||
|
if (isOpen) { |
||||
|
try { |
||||
|
auditLogModel.value = {} as AuditLogDto; |
||||
|
drawerApi.setState({ loading: true }); |
||||
|
const dto = drawerApi.getData<AuditLogDto>(); |
||||
|
await onGet(dto.id); |
||||
|
} finally { |
||||
|
drawerApi.setState({ loading: false }); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
title: $t('AbpAuditLogging.AuditLog'), |
||||
|
}); |
||||
|
/** 调用方法表格配置 */ |
||||
|
const actionsGridOptions: VxeGridProps<Action> = { |
||||
|
border: true, |
||||
|
columns: [ |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'parameters', |
||||
|
slots: { |
||||
|
content: 'parameters', |
||||
|
}, |
||||
|
type: 'expand', |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'serviceName', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.ServiceName'), |
||||
|
width: 'auto', |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'methodName', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.MethodName'), |
||||
|
width: 150, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'executionTime', |
||||
|
formatter: ({ cellValue }) => { |
||||
|
return cellValue ? formatToDateTime(cellValue) : cellValue; |
||||
|
}, |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.ExecutionTime'), |
||||
|
width: 200, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'executionDuration', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.ExecutionDuration'), |
||||
|
width: 150, |
||||
|
}, |
||||
|
], |
||||
|
expandConfig: { |
||||
|
padding: true, |
||||
|
trigger: 'row', |
||||
|
}, |
||||
|
exportConfig: {}, |
||||
|
keepSource: true, |
||||
|
pagerConfig: { |
||||
|
enabled: false, |
||||
|
}, |
||||
|
proxyConfig: { |
||||
|
ajax: { |
||||
|
query: () => { |
||||
|
return Promise.resolve(auditLogModel.value.actions); |
||||
|
}, |
||||
|
}, |
||||
|
response: { |
||||
|
list: ({ data }) => { |
||||
|
return data; |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
toolbarConfig: { |
||||
|
enabled: false, |
||||
|
}, |
||||
|
}; |
||||
|
/** 调用方法表格 */ |
||||
|
const [ActionsGrid] = useVbenVxeGrid({ |
||||
|
gridOptions: actionsGridOptions, |
||||
|
}); |
||||
|
/** 查询审计日志 */ |
||||
|
async function onGet(id: string) { |
||||
|
const dto = await getApi(id); |
||||
|
auditLogModel.value = dto; |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Drawer> |
||||
|
<div style="width: 800px"> |
||||
|
<Tabs v-model="activedTab"> |
||||
|
<TabPane key="basic" :tab="$t('AbpAuditLogging.Operation')"> |
||||
|
<Descriptions :colon="false" :column="2" bordered size="small"> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.ApplicationName')"> |
||||
|
{{ auditLogModel.applicationName }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.ExecutionTime')"> |
||||
|
{{ formatToDateTime(auditLogModel.executionTime) }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.UserName')"> |
||||
|
{{ auditLogModel.userName }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.HttpMethod')"> |
||||
|
<Tag :color="getHttpMethodColor(auditLogModel.httpMethod)"> |
||||
|
{{ auditLogModel.httpMethod }} |
||||
|
</Tag> |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem |
||||
|
:label="$t('AbpAuditLogging.RequestUrl')" |
||||
|
:span="2" |
||||
|
> |
||||
|
{{ auditLogModel.url }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.HttpStatusCode')"> |
||||
|
<Tag |
||||
|
:color="getHttpStatusCodeColor(auditLogModel.httpStatusCode)" |
||||
|
> |
||||
|
{{ auditLogModel.httpStatusCode }} |
||||
|
</Tag> |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.ExecutionDuration')"> |
||||
|
{{ auditLogModel.executionDuration }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.ClientId')"> |
||||
|
{{ auditLogModel.clientId }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.ClientIpAddress')"> |
||||
|
{{ auditLogModel.clientIpAddress }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.ClientName')"> |
||||
|
{{ auditLogModel.clientName }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.CorrelationId')"> |
||||
|
{{ auditLogModel.correlationId }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem |
||||
|
:label="$t('AbpAuditLogging.BrowserInfo')" |
||||
|
:label-style="{ width: '110px' }" |
||||
|
:span="2" |
||||
|
> |
||||
|
{{ auditLogModel.browserInfo }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.Comments')" :span="2"> |
||||
|
{{ auditLogModel.comments }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem |
||||
|
:label="$t('AbpAuditLogging.Exception')" |
||||
|
:span="2" |
||||
|
> |
||||
|
{{ auditLogModel.exceptions }} |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem |
||||
|
:label="$t('AbpAuditLogging.Additional')" |
||||
|
:span="2" |
||||
|
> |
||||
|
{{ auditLogModel.extraProperties }} |
||||
|
</DescriptionsItem> |
||||
|
</Descriptions> |
||||
|
</TabPane> |
||||
|
<TabPane |
||||
|
v-if="auditLogModel.actions?.length" |
||||
|
key="opera" |
||||
|
:tab="`${$t('AbpAuditLogging.InvokeMethod')}(${auditLogModel.actions?.length})`" |
||||
|
> |
||||
|
<ActionsGrid> |
||||
|
<template #parameters="{ row }"> |
||||
|
<Descriptions :colon="false" :column="1" bordered size="small"> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.Parameters')"> |
||||
|
<CodeEditor |
||||
|
:mode="MODE.JSON" |
||||
|
:value="row.parameters" |
||||
|
readonly |
||||
|
/> |
||||
|
</DescriptionsItem> |
||||
|
<DescriptionsItem :label="$t('AbpAuditLogging.Additional')"> |
||||
|
<CodeEditor |
||||
|
:mode="MODE.JSON" |
||||
|
:value="row.extraProperties" |
||||
|
readonly |
||||
|
/> |
||||
|
</DescriptionsItem> |
||||
|
</Descriptions> |
||||
|
</template> |
||||
|
</ActionsGrid> |
||||
|
</TabPane> |
||||
|
<TabPane |
||||
|
v-if="auditLogModel.entityChanges?.length" |
||||
|
key="changes" |
||||
|
:tab="`${$t('AbpAuditLogging.EntitiesChanged')}(${auditLogModel.entityChanges?.length})`" |
||||
|
> |
||||
|
<EntityChangeTable :data="auditLogModel.entityChanges" /> |
||||
|
</TabPane> |
||||
|
</Tabs> |
||||
|
</div> |
||||
|
</Drawer> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped></style> |
||||
@ -0,0 +1,326 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { SortOrder } from '@abp/core'; |
||||
|
import type { VbenFormProps, VxeGridListeners, VxeGridProps } from '@abp/ui'; |
||||
|
|
||||
|
import type { AuditLogDto } from '../../types/audit-logs'; |
||||
|
|
||||
|
import { defineAsyncComponent, h, nextTick } from 'vue'; |
||||
|
|
||||
|
import { useVbenDrawer } from '@vben/common-ui'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { formatToDateTime } from '@abp/core'; |
||||
|
import { useVbenVxeGrid } from '@abp/ui'; |
||||
|
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue'; |
||||
|
import { Button, Popconfirm, Tag } from 'ant-design-vue'; |
||||
|
|
||||
|
import { deleteApi, getPagedListApi } from '../../api/audit-logs'; |
||||
|
import { AuditLogPermissions } from '../../constants/permissions'; |
||||
|
import { useAuditlogs } from '../../hooks/useAuditlogs'; |
||||
|
import { httpMethodOptions, httpStatusCodeOptions } from './mapping'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'AuditLogTable', |
||||
|
}); |
||||
|
|
||||
|
const formOptions: VbenFormProps = { |
||||
|
// 默认展开 |
||||
|
collapsed: true, |
||||
|
collapsedRows: 2, |
||||
|
fieldMappingTime: [ |
||||
|
[ |
||||
|
'executionTime', |
||||
|
['startTime', 'endTime'], |
||||
|
['YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss'], |
||||
|
], |
||||
|
], |
||||
|
schema: [ |
||||
|
{ |
||||
|
component: 'RangePicker', |
||||
|
fieldName: 'executionTime', |
||||
|
formItemClass: 'col-span-2 items-baseline', |
||||
|
label: $t('AbpAuditLogging.ExecutionTime'), |
||||
|
}, |
||||
|
{ |
||||
|
component: 'Input', |
||||
|
fieldName: 'url', |
||||
|
formItemClass: 'col-span-2 items-baseline', |
||||
|
label: $t('AbpAuditLogging.RequestUrl'), |
||||
|
}, |
||||
|
{ |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: httpStatusCodeOptions, |
||||
|
}, |
||||
|
fieldName: 'httpStatusCode', |
||||
|
label: $t('AbpAuditLogging.HttpStatusCode'), |
||||
|
}, |
||||
|
{ |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: httpMethodOptions, |
||||
|
}, |
||||
|
fieldName: 'httpMethod', |
||||
|
label: $t('AbpAuditLogging.HttpMethod'), |
||||
|
}, |
||||
|
{ |
||||
|
component: 'Input', |
||||
|
fieldName: 'applicationName', |
||||
|
label: $t('AbpAuditLogging.ApplicationName'), |
||||
|
}, |
||||
|
{ |
||||
|
component: 'Input', |
||||
|
fieldName: 'userName', |
||||
|
label: $t('AbpAuditLogging.UserName'), |
||||
|
}, |
||||
|
{ |
||||
|
component: 'Input', |
||||
|
fieldName: 'clientId', |
||||
|
label: $t('AbpAuditLogging.ClientId'), |
||||
|
}, |
||||
|
{ |
||||
|
component: 'Input', |
||||
|
fieldName: 'clientIpAddress', |
||||
|
label: $t('AbpAuditLogging.ClientIpAddress'), |
||||
|
}, |
||||
|
{ |
||||
|
component: 'InputNumber', |
||||
|
fieldName: 'minExecutionDuration', |
||||
|
label: $t('AbpAuditLogging.MinExecutionDuration'), |
||||
|
labelWidth: 150, |
||||
|
}, |
||||
|
{ |
||||
|
component: 'InputNumber', |
||||
|
fieldName: 'maxExecutionDuration', |
||||
|
label: $t('AbpAuditLogging.MaxExecutionDuration'), |
||||
|
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<AuditLogDto> = { |
||||
|
columns: [ |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'url', |
||||
|
slots: { default: 'url' }, |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.RequestUrl'), |
||||
|
width: 500, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'userName', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.UserName'), |
||||
|
width: 120, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'executionTime', |
||||
|
formatter: ({ cellValue }) => { |
||||
|
return cellValue ? formatToDateTime(cellValue) : cellValue; |
||||
|
}, |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.ExecutionTime'), |
||||
|
width: 150, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'executionDuration', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.ExecutionDuration'), |
||||
|
width: 140, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'clientId', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.ClientId'), |
||||
|
width: 150, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'clientIpAddress', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.ClientIpAddress'), |
||||
|
width: 150, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'applicationName', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.ApplicationName'), |
||||
|
width: 160, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'correlationId', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.CorrelationId'), |
||||
|
width: 160, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'tenantName', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.TenantName'), |
||||
|
width: 100, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'browserInfo', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.BrowserInfo'), |
||||
|
width: 300, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'action', |
||||
|
fixed: 'right', |
||||
|
slots: { default: 'action' }, |
||||
|
title: $t('AbpUi.Actions'), |
||||
|
width: 220, |
||||
|
}, |
||||
|
], |
||||
|
exportConfig: {}, |
||||
|
keepSource: true, |
||||
|
proxyConfig: { |
||||
|
ajax: { |
||||
|
query: async ({ page }, formValues) => { |
||||
|
return await getPagedListApi({ |
||||
|
maxResultCount: page.pageSize, |
||||
|
skipCount: (page.currentPage - 1) * page.pageSize, |
||||
|
...formValues, |
||||
|
}); |
||||
|
}, |
||||
|
}, |
||||
|
response: { |
||||
|
total: 'totalCount', |
||||
|
list: 'items', |
||||
|
}, |
||||
|
}, |
||||
|
sortConfig: { |
||||
|
remote: true, |
||||
|
}, |
||||
|
toolbarConfig: { |
||||
|
custom: true, |
||||
|
export: true, |
||||
|
// import: true, |
||||
|
refresh: true, |
||||
|
zoom: true, |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
const gridEvents: VxeGridListeners<AuditLogDto> = { |
||||
|
sortChange: onSort, |
||||
|
}; |
||||
|
const [Grid, { formApi, query }] = useVbenVxeGrid({ |
||||
|
formOptions, |
||||
|
gridEvents, |
||||
|
gridOptions, |
||||
|
}); |
||||
|
const { getHttpMethodColor, getHttpStatusCodeColor } = useAuditlogs(); |
||||
|
const [AuditLogDrawer, logDrawerApi] = useVbenDrawer({ |
||||
|
connectedComponent: defineAsyncComponent( |
||||
|
() => import('./AuditLogDrawer.vue'), |
||||
|
), |
||||
|
}); |
||||
|
|
||||
|
function onUpdate(row: AuditLogDto) { |
||||
|
logDrawerApi.setData(row); |
||||
|
logDrawerApi.open(); |
||||
|
} |
||||
|
|
||||
|
async function onDelete(row: AuditLogDto) { |
||||
|
await deleteApi(row.id).then(() => query()); |
||||
|
} |
||||
|
|
||||
|
function onSort(params: { field: string; order: SortOrder }) { |
||||
|
const sorting = params.order ? `${params.field} ${params.order}` : undefined; |
||||
|
query({ sorting }); |
||||
|
} |
||||
|
|
||||
|
function onFilter(field: string, value: any) { |
||||
|
nextTick(() => { |
||||
|
formApi.setFieldValue(field, value); |
||||
|
formApi.validateAndSubmitForm(); |
||||
|
}); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Grid :table-title="$t('AbpAuditLogging.AuditLog')"> |
||||
|
<template #url="{ row }"> |
||||
|
<Tag |
||||
|
:color="getHttpStatusCodeColor(row.httpStatusCode)" |
||||
|
@click="onFilter('httpStatusCode', row.httpStatusCode)" |
||||
|
> |
||||
|
{{ row.httpStatusCode }} |
||||
|
</Tag> |
||||
|
<Tag |
||||
|
:color="getHttpMethodColor(row.httpMethod)" |
||||
|
style="margin-left: 5px" |
||||
|
@click="onFilter('httpMethod', row.httpMethod)" |
||||
|
> |
||||
|
{{ row.httpMethod }} |
||||
|
</Tag> |
||||
|
<a |
||||
|
class="link" |
||||
|
href="javaScript:void(0);" |
||||
|
@click="onFilter('url', row.url)" |
||||
|
>{{ row.url }} |
||||
|
</a> |
||||
|
</template> |
||||
|
<template #action="{ row }"> |
||||
|
<div class="flex flex-row"> |
||||
|
<Button |
||||
|
:icon="h(EditOutlined)" |
||||
|
block |
||||
|
type="link" |
||||
|
v-access:code="[AuditLogPermissions.Default]" |
||||
|
@click="onUpdate(row)" |
||||
|
> |
||||
|
{{ $t('AbpAuditLogging.ShowLogDialog') }} |
||||
|
</Button> |
||||
|
<Popconfirm |
||||
|
:title="$t('AbpUi.ItemWillBeDeletedMessage')" |
||||
|
@confirm="onDelete(row)" |
||||
|
> |
||||
|
<Button |
||||
|
:icon="h(DeleteOutlined)" |
||||
|
block |
||||
|
danger |
||||
|
type="link" |
||||
|
v-access:code="[AuditLogPermissions.Delete]" |
||||
|
> |
||||
|
{{ $t('AbpUi.Delete') }} |
||||
|
</Button> |
||||
|
</Popconfirm> |
||||
|
</div> |
||||
|
</template> |
||||
|
</Grid> |
||||
|
<AuditLogDrawer /> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped></style> |
||||
@ -0,0 +1,57 @@ |
|||||
|
const httpMethodOptions = [ |
||||
|
{ label: 'GET', value: 'GET' }, |
||||
|
{ label: 'PUT', value: 'PUT' }, |
||||
|
{ label: 'POST', value: 'POST' }, |
||||
|
{ label: 'PATCH', value: 'PATCH' }, |
||||
|
{ label: 'DELETE', value: 'DELETE' }, |
||||
|
{ label: 'OPTIONS', value: 'OPTIONS' }, |
||||
|
{ label: 'HEAD', value: 'HEAD' }, |
||||
|
]; |
||||
|
|
||||
|
const httpStatusCodeOptions = [ |
||||
|
{ label: '100 - Continue', value: 100 }, |
||||
|
{ label: '101 - Switching Protocols', value: 101 }, |
||||
|
{ label: '200 - OK', value: 200 }, |
||||
|
{ label: '201 - Created', value: 201 }, |
||||
|
{ label: '202 - Accepted', value: 202 }, |
||||
|
{ label: '203 - Non Authoritative Information', value: 203 }, |
||||
|
{ label: '204 - No Content', value: 204 }, |
||||
|
{ label: '205 - Reset Content', value: 205 }, |
||||
|
{ label: '206 - Partial Content', value: 206 }, |
||||
|
{ label: '300 - Multiple Choices', value: 300 }, |
||||
|
{ label: '301 - Moved Permanently', value: 301 }, |
||||
|
{ label: '302 - Found & Redirect', value: 302 }, |
||||
|
{ label: '303 - See Other', value: 303 }, |
||||
|
{ label: '304 - Not Modified', value: 304 }, |
||||
|
{ label: '305 - Use Proxy', value: 305 }, |
||||
|
{ label: '306 - Switch Proxy', value: 306 }, |
||||
|
{ label: '307 - Temporary Redirect', value: 307 }, |
||||
|
{ label: '308 - Permanent Redirect', value: 308 }, |
||||
|
{ label: '400 - Bad Request', value: 400 }, |
||||
|
{ label: '401 - Unauthorized', value: 401 }, |
||||
|
{ label: '402 - Payment Required', value: 402 }, |
||||
|
{ label: '403 - Forbidden', value: 403 }, |
||||
|
{ label: '404 - Not Found', value: 404 }, |
||||
|
{ label: '405 - Method Not Allowed', value: 405 }, |
||||
|
{ label: '406 - Not Acceptable', value: 406 }, |
||||
|
{ label: '407 - Proxy Authentication Required', value: 407 }, |
||||
|
{ label: '408 - Request Timeout', value: 408 }, |
||||
|
{ label: '409 - Conflict', value: 409 }, |
||||
|
{ label: '410 - Gone', value: 410 }, |
||||
|
{ label: '411 - Length Required', value: 411 }, |
||||
|
{ label: '412 - Precondition Failed', value: 412 }, |
||||
|
{ label: '413 - Request Entity Too Large', value: 413 }, |
||||
|
{ label: '414 - Request Uri Too Long', value: 414 }, |
||||
|
{ label: '415 - Unsupported Media Type', value: 415 }, |
||||
|
{ label: '416 - Requested Range Not Satisfiable', value: 416 }, |
||||
|
{ label: '417 - Expectation Failed', value: 417 }, |
||||
|
{ label: '426 - Upgrade Required', value: 426 }, |
||||
|
{ label: '500 - Internal Server Error', value: 500 }, |
||||
|
{ label: '501 - Not mplemented', value: 501 }, |
||||
|
{ label: '502 - Bad Gateway', value: 502 }, |
||||
|
{ label: '503 - Service Unavailable', value: 503 }, |
||||
|
{ label: '504 - Gateway Timeout', value: 504 }, |
||||
|
{ label: '505 - Http Version Not Supported', value: 505 }, |
||||
|
]; |
||||
|
|
||||
|
export { httpMethodOptions, httpStatusCodeOptions }; |
||||
@ -0,0 +1,47 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { |
||||
|
EntityChangeDto, |
||||
|
EntityChangeGetWithUsernameInput, |
||||
|
} from '../../types/entity-changes'; |
||||
|
|
||||
|
import { ref } from 'vue'; |
||||
|
|
||||
|
import { useVbenDrawer } from '@vben/common-ui'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { getListWithUsernameApi } from '../../api/entity-changes'; |
||||
|
import EntityChangeTable from '../entity-changes/EntityChangeTable.vue'; |
||||
|
|
||||
|
const entityChanges = ref<EntityChangeDto[]>([]); |
||||
|
|
||||
|
const [Drawer, drawerApi] = useVbenDrawer({ |
||||
|
class: 'w-auto', |
||||
|
onCancel() { |
||||
|
drawerApi.close(); |
||||
|
}, |
||||
|
onConfirm: async () => {}, |
||||
|
onOpenChange: async (isOpen: boolean) => { |
||||
|
if (isOpen) { |
||||
|
const input = drawerApi.getData<EntityChangeGetWithUsernameInput>(); |
||||
|
const { items } = await getListWithUsernameApi(input); |
||||
|
entityChanges.value = items.map((item) => { |
||||
|
return { |
||||
|
...item.entityChange, |
||||
|
userName: item.userName, |
||||
|
}; |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
title: $t('AbpAuditLogging.EntitiesChanged'), |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Drawer> |
||||
|
<div style="max-width: 800px"> |
||||
|
<EntityChangeTable :data="entityChanges" show-user-name /> |
||||
|
</div> |
||||
|
</Drawer> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped></style> |
||||
@ -0,0 +1,171 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { VxeGridDefines, VxeGridPropTypes } from 'vxe-table'; |
||||
|
|
||||
|
import type { |
||||
|
EntityChangeDto, |
||||
|
PropertyChange, |
||||
|
} from '../../types/entity-changes'; |
||||
|
|
||||
|
import { computed, reactive, watchEffect } from 'vue'; |
||||
|
|
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { formatToDateTime } from '@abp/core'; |
||||
|
import { Tag } from 'ant-design-vue'; |
||||
|
import { VxeGrid } from 'vxe-table'; |
||||
|
|
||||
|
import { useAuditlogs } from '../../hooks/useAuditlogs'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'EntityChangeTable', |
||||
|
}); |
||||
|
|
||||
|
const props = defineProps<{ |
||||
|
data: EntityChangeDto[]; |
||||
|
showUserName?: boolean; |
||||
|
}>(); |
||||
|
|
||||
|
const { getChangeTypeColor, getChangeTypeValue } = useAuditlogs(); |
||||
|
|
||||
|
/** 实体变更表格分页配置 */ |
||||
|
const pagerConfig = reactive<VxeGridPropTypes.PagerConfig>({ |
||||
|
currentPage: 1, |
||||
|
pageSize: 10, |
||||
|
pageSizes: [10, 25, 50, 100], |
||||
|
total: 0, |
||||
|
}); |
||||
|
/** 实体变更表格数据列配置 */ |
||||
|
const columnsConfig = reactive<VxeGridPropTypes.Columns<EntityChangeDto>>([ |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'propertyChanges', |
||||
|
slots: { |
||||
|
content: 'changes', |
||||
|
}, |
||||
|
type: 'expand', |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'userName', |
||||
|
title: $t('AbpAuditLogging.UserName'), |
||||
|
visible: props.showUserName, |
||||
|
width: 100, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'center', |
||||
|
field: 'changeType', |
||||
|
slots: { default: 'type' }, |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.ChangeType'), |
||||
|
width: 100, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'changeTime', |
||||
|
formatter: ({ cellValue }) => { |
||||
|
return cellValue ? formatToDateTime(cellValue) : cellValue; |
||||
|
}, |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.StartTime'), |
||||
|
width: 200, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'entityTypeFullName', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.EntityTypeFullName'), |
||||
|
width: 'auto', |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'entityId', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.EntityId'), |
||||
|
width: 280, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'entityTenantId', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.TenantId'), |
||||
|
width: 280, |
||||
|
}, |
||||
|
]); |
||||
|
/** 属性变更表格数据列配置 */ |
||||
|
const subColumnsConfig = reactive<VxeGridPropTypes.Columns<PropertyChange>>([ |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'propertyName', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.PropertyName'), |
||||
|
width: 120, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
className: 'font-medium text-green-600', |
||||
|
field: 'newValue', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.NewValue'), |
||||
|
width: 200, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
className: 'font-medium text-red-600', |
||||
|
field: 'originalValue', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.OriginalValue'), |
||||
|
width: 200, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'propertyTypeFullName', |
||||
|
sortable: true, |
||||
|
title: $t('AbpAuditLogging.PropertyTypeFullName'), |
||||
|
width: 220, |
||||
|
}, |
||||
|
]); |
||||
|
/** 实体变更表格数据源(计算分页) */ |
||||
|
const getEntityChanges = computed(() => { |
||||
|
const entityChanges = props.data ?? []; |
||||
|
const current = pagerConfig.currentPage ?? 1; |
||||
|
const pageSize = pagerConfig.pageSize ?? 10; |
||||
|
return entityChanges.slice((current - 1) * pageSize, pageSize * current); |
||||
|
}); |
||||
|
/** 页码变更事件 */ |
||||
|
function onPageChange( |
||||
|
params: VxeGridDefines.PageChangeEventParams<EntityChangeDto>, |
||||
|
) { |
||||
|
pagerConfig.currentPage = params.currentPage; |
||||
|
pagerConfig.pageSize = params.pageSize; |
||||
|
} |
||||
|
watchEffect(() => { |
||||
|
pagerConfig.total = props.data.length; |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<VxeGrid |
||||
|
:columns="columnsConfig" |
||||
|
:data="getEntityChanges" |
||||
|
:expand-config="{ |
||||
|
padding: true, |
||||
|
}" |
||||
|
:pager-config="pagerConfig" |
||||
|
@page-change="onPageChange" |
||||
|
> |
||||
|
<template #type="{ row }"> |
||||
|
<Tag :color="getChangeTypeColor(row.changeType)"> |
||||
|
{{ getChangeTypeValue(row.changeType) }} |
||||
|
</Tag> |
||||
|
</template> |
||||
|
<template #changes="{ row }"> |
||||
|
<VxeGrid |
||||
|
:border="true" |
||||
|
:columns="subColumnsConfig" |
||||
|
:data="row.propertyChanges" |
||||
|
/> |
||||
|
</template> |
||||
|
</VxeGrid> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped></style> |
||||
@ -0,0 +1,2 @@ |
|||||
|
export { default as AuditLogTable } from './audit-logs/AuditLogTable.vue'; |
||||
|
export { default as EntityChangeDrawer } from './entity-changes/EntityChangeDrawer.vue'; |
||||
@ -0,0 +1 @@ |
|||||
|
export * from './permissions'; |
||||
@ -0,0 +1,6 @@ |
|||||
|
/** 审计日志权限 */ |
||||
|
export const AuditLogPermissions = { |
||||
|
Default: 'AbpAuditing.AuditLog', |
||||
|
/** 删除 */ |
||||
|
Delete: 'AbpAuditing.AuditLog.Delete', |
||||
|
}; |
||||
@ -0,0 +1,62 @@ |
|||||
|
import { computed } from 'vue'; |
||||
|
|
||||
|
import { useLocalization } from '@abp/core'; |
||||
|
|
||||
|
import { ChangeType } from '../types/entity-changes'; |
||||
|
|
||||
|
export function useAuditlogs() { |
||||
|
const { L } = useLocalization(['AbpAuditLogging', 'AbpUi']); |
||||
|
|
||||
|
const changeTypeColorMap = { |
||||
|
[ChangeType.Created]: { color: '#87d068', value: L('Created') }, |
||||
|
[ChangeType.Deleted]: { color: 'red', value: L('Deleted') }, |
||||
|
[ChangeType.Updated]: { color: '#108ee9', value: L('Updated') }, |
||||
|
}; |
||||
|
|
||||
|
const methodColorMap: { [key: string]: string } = { |
||||
|
DELETE: 'red', |
||||
|
GET: 'blue', |
||||
|
OPTIONS: 'cyan', |
||||
|
PATCH: 'pink', |
||||
|
POST: 'green', |
||||
|
PUT: 'orange', |
||||
|
}; |
||||
|
const getChangeTypeColor = computed(() => { |
||||
|
return (changeType: ChangeType) => changeTypeColorMap[changeType].color; |
||||
|
}); |
||||
|
const getChangeTypeValue = computed(() => { |
||||
|
return (changeType: ChangeType) => changeTypeColorMap[changeType].value; |
||||
|
}); |
||||
|
const getHttpMethodColor = computed(() => { |
||||
|
return (method?: string) => { |
||||
|
return method ? methodColorMap[method] : ''; |
||||
|
}; |
||||
|
}); |
||||
|
const getHttpStatusCodeColor = computed(() => { |
||||
|
return (statusCode?: number) => { |
||||
|
if (!statusCode) { |
||||
|
return ''; |
||||
|
} |
||||
|
if (statusCode >= 200 && statusCode < 300) { |
||||
|
return '#87d068'; |
||||
|
} |
||||
|
if (statusCode >= 300 && statusCode < 400) { |
||||
|
return '#108ee9'; |
||||
|
} |
||||
|
if (statusCode >= 400 && statusCode < 500) { |
||||
|
return 'orange'; |
||||
|
} |
||||
|
if (statusCode >= 500) { |
||||
|
return 'red'; |
||||
|
} |
||||
|
return 'cyan'; |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
return { |
||||
|
getChangeTypeColor, |
||||
|
getChangeTypeValue, |
||||
|
getHttpMethodColor, |
||||
|
getHttpStatusCodeColor, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
export * from './components'; |
||||
|
export * from './constants'; |
||||
|
export * from './types'; |
||||
@ -0,0 +1,62 @@ |
|||||
|
import type { |
||||
|
ExtraPropertyDictionary, |
||||
|
PagedAndSortedResultRequestDto, |
||||
|
} from '@abp/core'; |
||||
|
|
||||
|
import type { EntityChangeDto } from './entity-changes'; |
||||
|
|
||||
|
interface Action { |
||||
|
[key: string]: any; |
||||
|
executionDuration?: number; |
||||
|
executionTime: Date; |
||||
|
extraProperties?: ExtraPropertyDictionary; |
||||
|
id: string; |
||||
|
methodName?: string; |
||||
|
parameters?: string; |
||||
|
serviceName?: string; |
||||
|
} |
||||
|
|
||||
|
interface AuditLogDto { |
||||
|
[key: string]: any; |
||||
|
actions?: Action[]; |
||||
|
applicationName?: string; |
||||
|
browserInfo?: string; |
||||
|
clientId?: string; |
||||
|
clientIpAddress?: string; |
||||
|
clientName?: string; |
||||
|
comments?: string; |
||||
|
correlationId?: string; |
||||
|
entityChanges?: EntityChangeDto[]; |
||||
|
exceptions?: string; |
||||
|
executionDuration?: number; |
||||
|
executionTime?: Date; |
||||
|
extraProperties?: ExtraPropertyDictionary; |
||||
|
httpMethod?: string; |
||||
|
httpStatusCode?: number; |
||||
|
id: string; |
||||
|
impersonatorTenantId?: string; |
||||
|
impersonatorUserId?: string; |
||||
|
tenantId?: string; |
||||
|
tenantName?: string; |
||||
|
url?: string; |
||||
|
userId?: string; |
||||
|
userName?: string; |
||||
|
} |
||||
|
interface AuditLogGetListInput extends PagedAndSortedResultRequestDto { |
||||
|
applicationName?: string; |
||||
|
clientId?: string; |
||||
|
clientIpAddress?: string; |
||||
|
correlationId?: string; |
||||
|
endTime?: Date; |
||||
|
hasException?: boolean; |
||||
|
httpMethod?: string; |
||||
|
httpStatusCode?: number; |
||||
|
maxExecutionDuration?: number; |
||||
|
minExecutionDuration?: number; |
||||
|
startTime?: Date; |
||||
|
url?: string; |
||||
|
userId?: string; |
||||
|
userName?: string; |
||||
|
} |
||||
|
|
||||
|
export type { Action, AuditLogDto, AuditLogGetListInput }; |
||||
@ -0,0 +1,63 @@ |
|||||
|
import type { |
||||
|
ExtraPropertyDictionary, |
||||
|
PagedAndSortedResultRequestDto, |
||||
|
} from '@abp/core'; |
||||
|
|
||||
|
export enum ChangeType { |
||||
|
Created = 0, |
||||
|
Deleted = 2, |
||||
|
Updated = 1, |
||||
|
} |
||||
|
|
||||
|
interface PropertyChange { |
||||
|
id: string; |
||||
|
newValue?: string; |
||||
|
originalValue?: string; |
||||
|
propertyName?: string; |
||||
|
propertyTypeFullName?: string; |
||||
|
} |
||||
|
|
||||
|
interface EntityChangeDto { |
||||
|
[key: string]: any; |
||||
|
changeTime?: Date; |
||||
|
changeType: ChangeType; |
||||
|
entityId?: string; |
||||
|
entityTenantId?: string; |
||||
|
entityTypeFullName?: string; |
||||
|
extraProperties?: ExtraPropertyDictionary; |
||||
|
id: string; |
||||
|
propertyChanges?: PropertyChange[]; |
||||
|
} |
||||
|
|
||||
|
interface EntityChangeWithUsernameDto { |
||||
|
entityChange: EntityChangeDto; |
||||
|
userName?: string; |
||||
|
} |
||||
|
|
||||
|
interface EntityChangeGetListInput extends PagedAndSortedResultRequestDto { |
||||
|
auditLogId?: string; |
||||
|
changeType?: ChangeType; |
||||
|
endTime?: Date; |
||||
|
entityId?: string; |
||||
|
entityTypeFullName?: string; |
||||
|
startTime?: Date; |
||||
|
} |
||||
|
|
||||
|
interface EntityChangeGetWithUsernameInput { |
||||
|
entityId?: string; |
||||
|
entityTypeFullName?: string; |
||||
|
} |
||||
|
|
||||
|
interface RestoreEntityInput { |
||||
|
entityChangeId?: string; |
||||
|
entityId: string; |
||||
|
} |
||||
|
|
||||
|
export type { |
||||
|
EntityChangeDto, |
||||
|
EntityChangeGetListInput, |
||||
|
EntityChangeGetWithUsernameInput, |
||||
|
EntityChangeWithUsernameDto, |
||||
|
PropertyChange, |
||||
|
RestoreEntityInput, |
||||
|
}; |
||||
@ -0,0 +1,2 @@ |
|||||
|
export * from './audit-logs'; |
||||
|
export * from './entity-changes'; |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"$schema": "https://json.schemastore.org/tsconfig", |
||||
|
"extends": "@vben/tsconfig/web.json", |
||||
|
"include": ["src"], |
||||
|
"exclude": ["node_modules"] |
||||
|
} |
||||
@ -1,3 +1,4 @@ |
|||||
export * from './useLocalization'; |
export * from './useLocalization'; |
||||
export * from './useSettings'; |
export * from './useSettings'; |
||||
export * from './useValidation'; |
export * from './useValidation'; |
||||
|
export * from './useWindowSizeFn'; |
||||
|
|||||
@ -0,0 +1,45 @@ |
|||||
|
import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core'; |
||||
|
|
||||
|
interface WindowSizeOptions { |
||||
|
immediate?: boolean; |
||||
|
listenerOptions?: AddEventListenerOptions | boolean; |
||||
|
once?: boolean; |
||||
|
} |
||||
|
|
||||
|
interface Fn<T = any, R = T> { |
||||
|
(...arg: T[]): R; |
||||
|
} |
||||
|
|
||||
|
export function useWindowSizeFn<T>( |
||||
|
fn: Fn<T>, |
||||
|
wait = 150, |
||||
|
options?: WindowSizeOptions, |
||||
|
) { |
||||
|
let handler = () => { |
||||
|
fn(); |
||||
|
}; |
||||
|
const handleSize = useDebounceFn(handler, wait); |
||||
|
handler = handleSize; |
||||
|
|
||||
|
const start = () => { |
||||
|
if (options && options.immediate) { |
||||
|
handler(); |
||||
|
} |
||||
|
window.addEventListener('resize', handler, { |
||||
|
passive: true, |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const stop = () => { |
||||
|
window.removeEventListener('resize', handler); |
||||
|
}; |
||||
|
|
||||
|
tryOnMounted(() => { |
||||
|
start(); |
||||
|
}); |
||||
|
|
||||
|
tryOnUnmounted(() => { |
||||
|
stop(); |
||||
|
}); |
||||
|
return [start, stop]; |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
type SortOrder = '' | 'asc' | 'desc' | null; |
||||
|
|
||||
|
export type { SortOrder }; |
||||
@ -0,0 +1,61 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import type { PropType } from 'vue'; |
||||
|
import { computed } from 'vue'; |
||||
|
|
||||
|
import { isString } from '@vben-core/shared/utils'; |
||||
|
|
||||
|
import CodeMirror from './codemirror/CodeMirror.vue'; |
||||
|
import { MODE } from './codemirror/types'; |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
autoFormat: { default: true, type: Boolean }, |
||||
|
mode: { |
||||
|
default: MODE.JSON, |
||||
|
type: String as PropType<MODE>, |
||||
|
validator(value: any) { |
||||
|
// 这个值必须匹配下列字符串中的一个 |
||||
|
return Object.values(MODE).includes(value); |
||||
|
}, |
||||
|
}, |
||||
|
readonly: { type: Boolean }, |
||||
|
value: { default: '', type: [String, Object] }, |
||||
|
}); |
||||
|
|
||||
|
const emit = defineEmits<{ |
||||
|
(event: 'change', value: string): void; |
||||
|
(event: 'formatError', error: string): void; |
||||
|
(event: 'update:value', value: string): void; |
||||
|
}>(); |
||||
|
|
||||
|
const getValue = computed(() => { |
||||
|
const { autoFormat, mode, value } = props; |
||||
|
if (!autoFormat || mode !== MODE.JSON) { |
||||
|
return value as string; |
||||
|
} |
||||
|
let result = value; |
||||
|
if (isString(value)) { |
||||
|
try { |
||||
|
result = JSON.parse(value); |
||||
|
} catch { |
||||
|
emit('formatError', value); |
||||
|
return value as string; |
||||
|
} |
||||
|
} |
||||
|
return JSON.stringify(result, null, 2); |
||||
|
}); |
||||
|
|
||||
|
function handleValueChange(v: string) { |
||||
|
emit('update:value', v); |
||||
|
emit('change', v); |
||||
|
} |
||||
|
</script> |
||||
|
<template> |
||||
|
<div class="h-full"> |
||||
|
<CodeMirror |
||||
|
:mode="mode" |
||||
|
:readonly="readonly" |
||||
|
:value="getValue" |
||||
|
@change="handleValueChange" |
||||
|
/> |
||||
|
</div> |
||||
|
</template> |
||||
@ -0,0 +1,135 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import type { Nullable } from '@vben-core/typings'; |
||||
|
|
||||
|
import type { PropType } from 'vue'; |
||||
|
import { |
||||
|
nextTick, |
||||
|
onMounted, |
||||
|
onUnmounted, |
||||
|
ref, |
||||
|
unref, |
||||
|
watch, |
||||
|
watchEffect, |
||||
|
} from 'vue'; |
||||
|
|
||||
|
import { usePreferences } from '@vben-core/preferences'; |
||||
|
|
||||
|
import { useWindowSizeFn } from '@abp/core'; |
||||
|
import { useDebounceFn } from '@vueuse/core'; |
||||
|
import CodeMirror from 'codemirror'; |
||||
|
|
||||
|
import { MODE } from './types'; |
||||
|
|
||||
|
// css |
||||
|
import './codemirror.css'; |
||||
|
import 'codemirror/theme/idea.css'; |
||||
|
import 'codemirror/theme/material-palenight.css'; |
||||
|
|
||||
|
// modes |
||||
|
import 'codemirror/mode/javascript/javascript'; |
||||
|
import 'codemirror/mode/css/css'; |
||||
|
import 'codemirror/mode/htmlmixed/htmlmixed'; |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
mode: { |
||||
|
default: MODE.JSON, |
||||
|
type: String as PropType<MODE>, |
||||
|
validator(value: any) { |
||||
|
// 这个值必须匹配下列字符串中的一个 |
||||
|
return Object.values(MODE).includes(value); |
||||
|
}, |
||||
|
}, |
||||
|
readonly: { default: false, type: Boolean }, |
||||
|
value: { default: '', type: String }, |
||||
|
}); |
||||
|
|
||||
|
const emit = defineEmits(['change']); |
||||
|
|
||||
|
const el = ref(); |
||||
|
let editor: Nullable<CodeMirror.Editor>; |
||||
|
|
||||
|
const debounceRefresh = useDebounceFn(refresh, 100); |
||||
|
|
||||
|
const { theme } = usePreferences(); |
||||
|
|
||||
|
watch( |
||||
|
() => props.value, |
||||
|
async (value) => { |
||||
|
await nextTick(); |
||||
|
const oldValue = editor?.getValue(); |
||||
|
if (value !== oldValue) { |
||||
|
const jsonVal = value ? JSON.stringify(JSON.parse(value), null, 2) : ''; |
||||
|
editor?.setValue(jsonVal || ''); |
||||
|
setTimeout(refresh, 50); |
||||
|
} |
||||
|
}, |
||||
|
{ flush: 'post' }, |
||||
|
); |
||||
|
|
||||
|
watchEffect(() => { |
||||
|
editor?.setOption('mode', props.mode); |
||||
|
}); |
||||
|
|
||||
|
watch( |
||||
|
() => theme.value, |
||||
|
async () => { |
||||
|
setTheme(); |
||||
|
}, |
||||
|
{ |
||||
|
deep: true, |
||||
|
immediate: true, |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
function setTheme() { |
||||
|
unref(editor)?.setOption( |
||||
|
'theme', |
||||
|
theme.value === 'light' ? 'idea' : 'material-palenight', |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
function refresh() { |
||||
|
editor?.refresh(); |
||||
|
} |
||||
|
|
||||
|
async function init() { |
||||
|
const addonOptions = { |
||||
|
autoCloseBrackets: true, |
||||
|
autoCloseTags: true, |
||||
|
foldGutter: true, |
||||
|
gutters: ['CodeMirror-linenumbers'], |
||||
|
}; |
||||
|
|
||||
|
editor = CodeMirror(el.value!, { |
||||
|
fixedGutter: true, |
||||
|
lineNumbers: true, |
||||
|
lineWrapping: true, |
||||
|
mode: props.mode, |
||||
|
readOnly: props.readonly, |
||||
|
smartIndent: true, |
||||
|
tabSize: 2, |
||||
|
theme: 'material-palenight', |
||||
|
value: '', |
||||
|
...addonOptions, |
||||
|
}); |
||||
|
editor?.setValue(props.value); |
||||
|
setTheme(); |
||||
|
editor?.on('change', () => { |
||||
|
emit('change', editor?.getValue()); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
onMounted(async () => { |
||||
|
await nextTick(); |
||||
|
init(); |
||||
|
useWindowSizeFn<void>(debounceRefresh); |
||||
|
}); |
||||
|
|
||||
|
onUnmounted(() => { |
||||
|
editor = null; |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div ref="el" class="relative !h-full w-full overflow-hidden"></div> |
||||
|
</template> |
||||
@ -0,0 +1,525 @@ |
|||||
|
/* BASICS */ |
||||
|
|
||||
|
.CodeMirror { |
||||
|
--base: #545281; |
||||
|
--comment: hsl(210deg 25% 60%); |
||||
|
--keyword: #af4ab1; |
||||
|
--variable: #0055d1; |
||||
|
--function: #c25205; |
||||
|
--string: #2ba46d; |
||||
|
--number: #c25205; |
||||
|
--tags: #d00; |
||||
|
--qualifier: #ff6032; |
||||
|
--important: var(--string); |
||||
|
|
||||
|
position: relative; |
||||
|
height: auto; |
||||
|
height: 100%; |
||||
|
overflow: hidden; |
||||
|
font-family: var(--font-code); |
||||
|
background: white; |
||||
|
direction: ltr; |
||||
|
} |
||||
|
|
||||
|
/* PADDING */ |
||||
|
|
||||
|
.CodeMirror-lines { |
||||
|
min-height: 1px; /* prevents collapsing before first draw */ |
||||
|
padding: 4px 0; /* Vertical padding around content */ |
||||
|
cursor: text; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-scrollbar-filler, |
||||
|
.CodeMirror-gutter-filler { |
||||
|
background-color: white; /* The little square between H and V scrollbars */ |
||||
|
} |
||||
|
|
||||
|
/* GUTTER */ |
||||
|
|
||||
|
.CodeMirror-gutters { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 5 !important; |
||||
|
z-index: 3; |
||||
|
min-height: 100%; |
||||
|
white-space: nowrap; |
||||
|
background-color: transparent; |
||||
|
border-right: 1px solid #ddd; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-linenumber { |
||||
|
min-width: 20px; |
||||
|
padding: 1 8px 0 5px; |
||||
|
color: var(--comment); |
||||
|
text-align: left !important; |
||||
|
white-space: nowrap; |
||||
|
opacity: 0.6; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-guttermarker { |
||||
|
color: black; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-guttermarker-subtle { |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
/* FOLD GUTTER */ |
||||
|
|
||||
|
.CodeMirror-foldmarker { |
||||
|
font-family: arial; |
||||
|
line-height: 0.3; |
||||
|
color: #414141; |
||||
|
text-shadow: #f96 1px 1px 2px, #f96 -1px -1px 2px, #f96 1px -1px 2px, #f96 -1px 1px 2px; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-foldgutter { |
||||
|
width: 0.7em; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-foldgutter-open, |
||||
|
.CodeMirror-foldgutter-folded { |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-foldgutter-open::after, |
||||
|
.CodeMirror-foldgutter-folded::after { |
||||
|
position: relative; |
||||
|
top: -0.1em; |
||||
|
display: inline-block; |
||||
|
font-size: 0.8em; |
||||
|
content: '>'; |
||||
|
opacity: 0.8; |
||||
|
transform: rotate(90deg); |
||||
|
transition: transform 0.2s; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-foldgutter-folded::after { |
||||
|
transform: none; |
||||
|
} |
||||
|
|
||||
|
/* CURSOR */ |
||||
|
|
||||
|
.CodeMirror-cursor { |
||||
|
position: absolute; |
||||
|
width: 0; |
||||
|
pointer-events: none; |
||||
|
border-right: none; |
||||
|
border-left: 1px solid black; |
||||
|
} |
||||
|
|
||||
|
/* Shown when moving in bi-directional text */ |
||||
|
.CodeMirror div.CodeMirror-secondarycursor { |
||||
|
border-left: 1px solid silver; |
||||
|
} |
||||
|
|
||||
|
.cm-fat-cursor .CodeMirror-cursor { |
||||
|
width: auto; |
||||
|
background: #7e7; |
||||
|
border: 0 !important; |
||||
|
} |
||||
|
|
||||
|
.cm-fat-cursor div.CodeMirror-cursors { |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
.cm-fat-cursor-mark { |
||||
|
background-color: rgb(20 255 20 / 50%); |
||||
|
animation: blink 1.06s steps(1) infinite; |
||||
|
} |
||||
|
|
||||
|
.cm-animate-fat-cursor { |
||||
|
width: auto; |
||||
|
background-color: #7e7; |
||||
|
border: 0; |
||||
|
animation: blink 1.06s steps(1) infinite; |
||||
|
} |
||||
|
@keyframes blink { |
||||
|
50% { |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
} |
||||
|
@keyframes blink { |
||||
|
50% { |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
} |
||||
|
@keyframes blink { |
||||
|
50% { |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.cm-tab { |
||||
|
display: inline-block; |
||||
|
text-decoration: inherit; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-rulers { |
||||
|
position: absolute; |
||||
|
top: -50px; |
||||
|
right: 0; |
||||
|
bottom: -20px; |
||||
|
left: 0; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-ruler { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
border-left: 1px solid #ccc; |
||||
|
} |
||||
|
|
||||
|
/* DEFAULT THEME */ |
||||
|
.cm-s-default.CodeMirror { |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
|
||||
|
.cm-s-default .cm-header { |
||||
|
color: blue; |
||||
|
} |
||||
|
|
||||
|
.cm-s-default .cm-quote { |
||||
|
color: #090; |
||||
|
} |
||||
|
|
||||
|
.cm-negative { |
||||
|
color: #d44; |
||||
|
} |
||||
|
|
||||
|
.cm-positive { |
||||
|
color: #292; |
||||
|
} |
||||
|
|
||||
|
.cm-header, |
||||
|
.cm-strong { |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.cm-em { |
||||
|
font-style: italic; |
||||
|
} |
||||
|
|
||||
|
.cm-link { |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
|
||||
|
.cm-strikethrough { |
||||
|
text-decoration: line-through; |
||||
|
} |
||||
|
|
||||
|
.cm-s-default .cm-atom, |
||||
|
.cm-s-default .cm-def, |
||||
|
.cm-s-default .cm-property, |
||||
|
.cm-s-default .cm-variable-2, |
||||
|
.cm-s-default .cm-variable-3, |
||||
|
.cm-s-default .cm-punctuation { |
||||
|
color: var(--base); |
||||
|
} |
||||
|
|
||||
|
.cm-s-default .cm-hr, |
||||
|
.cm-s-default .cm-comment { |
||||
|
color: var(--comment); |
||||
|
} |
||||
|
|
||||
|
.cm-s-default .cm-attribute, |
||||
|
.cm-s-default .cm-keyword { |
||||
|
color: var(--keyword); |
||||
|
} |
||||
|
|
||||
|
.cm-s-default .cm-variable { |
||||
|
color: var(--variable); |
||||
|
} |
||||
|
|
||||
|
.cm-s-default .cm-bracket, |
||||
|
.cm-s-default .cm-tag { |
||||
|
color: var(--tags); |
||||
|
} |
||||
|
|
||||
|
.cm-s-default .cm-number { |
||||
|
color: var(--number); |
||||
|
} |
||||
|
|
||||
|
.cm-s-default .cm-string, |
||||
|
.cm-s-default .cm-string-2 { |
||||
|
color: var(--string); |
||||
|
} |
||||
|
|
||||
|
.cm-s-default .cm-type { |
||||
|
color: #085; |
||||
|
} |
||||
|
|
||||
|
.cm-s-default .cm-meta { |
||||
|
color: #555; |
||||
|
} |
||||
|
|
||||
|
.cm-s-default .cm-qualifier { |
||||
|
color: var(--qualifier); |
||||
|
} |
||||
|
|
||||
|
.cm-s-default .cm-builtin { |
||||
|
color: #7539ff; |
||||
|
} |
||||
|
|
||||
|
.cm-s-default .cm-link { |
||||
|
color: var(--flash); |
||||
|
} |
||||
|
|
||||
|
.cm-s-default .cm-error { |
||||
|
color: #ff008c; |
||||
|
} |
||||
|
|
||||
|
.cm-invalidchar { |
||||
|
color: #ff008c; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-composing { |
||||
|
border-bottom: 2px solid; |
||||
|
} |
||||
|
|
||||
|
/* Default styles for common addons */ |
||||
|
|
||||
|
div.CodeMirror span.CodeMirror-matchingbracket { |
||||
|
color: #0b0; |
||||
|
} |
||||
|
|
||||
|
div.CodeMirror span.CodeMirror-nonmatchingbracket { |
||||
|
color: #a22; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-matchingtag { |
||||
|
background: rgb(255 150 0 / 30%); |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-activeline-background { |
||||
|
background: #e8f2ff; |
||||
|
} |
||||
|
|
||||
|
/* STOP */ |
||||
|
|
||||
|
/* The rest of this file contains styles related to the mechanics of |
||||
|
the editor. You probably shouldn't touch them. */ |
||||
|
|
||||
|
.CodeMirror-scroll { |
||||
|
position: relative; |
||||
|
height: 100%; |
||||
|
padding-bottom: 30px; |
||||
|
margin-right: -30px; |
||||
|
|
||||
|
/* 30px is the magic margin used to hide the element's real scrollbars */ |
||||
|
|
||||
|
/* See overflow: hidden in .CodeMirror */ |
||||
|
margin-bottom: -30px; |
||||
|
overflow: scroll !important; /* Things will break if this is overridden */ |
||||
|
outline: none; /* Prevent dragging from highlighting the element */ |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-sizer { |
||||
|
position: relative; |
||||
|
margin-bottom: 20px !important; |
||||
|
border-right: 30px solid transparent; |
||||
|
} |
||||
|
|
||||
|
/* The fake, visible scrollbars. Used to force redraw during scrolling |
||||
|
before actual scrolling happens, thus preventing shaking and |
||||
|
flickering artifacts. */ |
||||
|
.CodeMirror-vscrollbar, |
||||
|
.CodeMirror-hscrollbar, |
||||
|
.CodeMirror-scrollbar-filler, |
||||
|
.CodeMirror-gutter-filler { |
||||
|
position: absolute; |
||||
|
z-index: 6; |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-vscrollbar { |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
overflow-x: hidden; |
||||
|
overflow-y: scroll; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-hscrollbar { |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
overflow-x: scroll; |
||||
|
overflow-y: hidden; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-scrollbar-filler { |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-gutter-filler { |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-gutter { |
||||
|
display: inline-block; |
||||
|
height: 100%; |
||||
|
margin-bottom: -30px; |
||||
|
white-space: normal; |
||||
|
vertical-align: top; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-gutter-wrapper { |
||||
|
position: absolute; |
||||
|
z-index: 4; |
||||
|
background: none !important; |
||||
|
border: none !important; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-gutter-background { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
z-index: 4; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-gutter-elt { |
||||
|
position: absolute; |
||||
|
z-index: 4; |
||||
|
cursor: default; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-gutter-wrapper ::selection { |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
|
||||
|
.CodeMirrorwrapper ::selection { |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror pre { |
||||
|
position: relative; |
||||
|
z-index: 2; |
||||
|
padding: 0 4px; /* Horizontal padding of content */ |
||||
|
margin: 0; |
||||
|
overflow: visible; |
||||
|
font-family: inherit; |
||||
|
font-size: inherit; |
||||
|
line-height: inherit; |
||||
|
color: inherit; |
||||
|
word-wrap: normal; |
||||
|
white-space: pre; |
||||
|
background: transparent; |
||||
|
border-width: 0; |
||||
|
|
||||
|
/* Reset some styles that the rest of the page might have set */ |
||||
|
border-radius: 0; |
||||
|
-webkit-tap-highlight-color: transparent; |
||||
|
font-variant-ligatures: contextual; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-wrap pre { |
||||
|
word-break: normal; |
||||
|
word-wrap: break-word; |
||||
|
white-space: pre-wrap; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-linebackground { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
z-index: 0; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-linewidget { |
||||
|
position: relative; |
||||
|
z-index: 2; |
||||
|
padding: 0.1px; /* Force widget margins to stay inside of the container */ |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-rtl pre { |
||||
|
direction: rtl; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-code { |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
/* Force content-box sizing for the elements where we expect it */ |
||||
|
.CodeMirror-scroll, |
||||
|
.CodeMirror-sizer, |
||||
|
.CodeMirror-gutter, |
||||
|
.CodeMirror-gutters, |
||||
|
.CodeMirror-linenumber { |
||||
|
box-sizing: content-box; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-measure { |
||||
|
position: absolute; |
||||
|
width: 100%; |
||||
|
height: 0; |
||||
|
overflow: hidden; |
||||
|
visibility: hidden; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-measure pre { |
||||
|
position: static; |
||||
|
} |
||||
|
|
||||
|
div.CodeMirror-cursors { |
||||
|
position: relative; |
||||
|
z-index: 3; |
||||
|
visibility: hidden; |
||||
|
} |
||||
|
|
||||
|
div.CodeMirror-dragcursors { |
||||
|
visibility: visible; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-focused div.CodeMirror-cursors { |
||||
|
visibility: visible; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-selected { |
||||
|
background: #d9d9d9; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-focused .CodeMirror-selected { |
||||
|
background: #d7d4f0; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-crosshair { |
||||
|
cursor: crosshair; |
||||
|
} |
||||
|
|
||||
|
.CodeMirror-line::selection, |
||||
|
.CodeMirror-line > span::selection, |
||||
|
.CodeMirror-line > span > span::selection { |
||||
|
background: #d7d4f0; |
||||
|
} |
||||
|
|
||||
|
.cm-searching { |
||||
|
background-color: #ffa; |
||||
|
background-color: rgb(255 255 0 / 40%); |
||||
|
} |
||||
|
|
||||
|
/* Used to force a border model for a node */ |
||||
|
.cm-force-border { |
||||
|
padding-right: 0.1px; |
||||
|
} |
||||
|
|
||||
|
@media print { |
||||
|
/* Hide the cursor when printing */ |
||||
|
.CodeMirror div.CodeMirror-cursors { |
||||
|
visibility: hidden; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* See issue #2901 */ |
||||
|
.cm-tab-wrap-hack::after { |
||||
|
content: ''; |
||||
|
} |
||||
|
|
||||
|
/* Help users use markselection to safely style text background */ |
||||
|
span.CodeMirror-selectedtext { |
||||
|
background: none; |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
import './codemirror.css'; |
||||
|
import 'codemirror/theme/idea.css'; |
||||
|
import 'codemirror/theme/material-palenight.css'; |
||||
|
// import 'codemirror/addon/lint/lint.css';
|
||||
|
|
||||
|
// modes
|
||||
|
import 'codemirror/mode/javascript/javascript'; |
||||
|
import 'codemirror/mode/css/css'; |
||||
|
import 'codemirror/mode/htmlmixed/htmlmixed'; |
||||
|
// addons
|
||||
|
// import 'codemirror/addon/edit/closebrackets';
|
||||
|
// import 'codemirror/addon/edit/closetag';
|
||||
|
// import 'codemirror/addon/comment/comment';
|
||||
|
// import 'codemirror/addon/fold/foldcode';
|
||||
|
// import 'codemirror/addon/fold/foldgutter';
|
||||
|
// import 'codemirror/addon/fold/brace-fold';
|
||||
|
// import 'codemirror/addon/fold/indent-fold';
|
||||
|
// import 'codemirror/addon/lint/json-lint';
|
||||
|
// import 'codemirror/addon/fold/comment-fold';
|
||||
|
|
||||
|
export { default as CodeMirror } from 'codemirror'; |
||||
@ -0,0 +1,2 @@ |
|||||
|
export { default as CodeMirror } from './CodeMirror.vue'; |
||||
|
export * from './types'; |
||||
@ -0,0 +1,5 @@ |
|||||
|
export enum MODE { |
||||
|
HTML = 'htmlmixed', |
||||
|
JS = 'javascript', |
||||
|
JSON = 'application/json', |
||||
|
} |
||||
@ -0,0 +1,2 @@ |
|||||
|
export { default as CodeEditor } from './CodeEditor.vue'; |
||||
|
export * from './codemirror'; |
||||
@ -1,4 +1,5 @@ |
|||||
export * from './adapter/component'; |
export * from './adapter/component'; |
||||
export * from './adapter/form'; |
export * from './adapter/form'; |
||||
export * from './adapter/vxe-table'; |
export * from './adapter/vxe-table'; |
||||
|
export * from './components'; |
||||
export type * from '@vben/plugins/vxe-table'; |
export type * from '@vben/plugins/vxe-table'; |
||||
|
|||||
Loading…
Reference in new issue