Browse Source

feat(auditing): 增加审计日志模块.

pull/1054/head
colin 1 year ago
parent
commit
95e1805d17
  1. 1
      apps/vben5/apps/app-antd/package.json
  2. 3
      apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json
  3. 3
      apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json
  4. 9
      apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts
  5. 15
      apps/vben5/apps/app-antd/src/views/auditing/audit-logs/index.vue
  6. 37
      apps/vben5/packages/@abp/auditing/package.json
  7. 36
      apps/vben5/packages/@abp/auditing/src/api/audit-logs.ts
  8. 23
      apps/vben5/packages/@abp/auditing/src/api/entity-changes.ts
  9. 240
      apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogDrawer.vue
  10. 326
      apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogTable.vue
  11. 57
      apps/vben5/packages/@abp/auditing/src/components/audit-logs/mapping.ts
  12. 45
      apps/vben5/packages/@abp/auditing/src/components/entity-changes/EntityChangeDrawer.vue
  13. 172
      apps/vben5/packages/@abp/auditing/src/components/entity-changes/EntityChangeTable.vue
  14. 2
      apps/vben5/packages/@abp/auditing/src/components/index.ts
  15. 1
      apps/vben5/packages/@abp/auditing/src/constants/index.ts
  16. 6
      apps/vben5/packages/@abp/auditing/src/constants/permissions.ts
  17. 62
      apps/vben5/packages/@abp/auditing/src/hooks/useAuditlogs.ts
  18. 3
      apps/vben5/packages/@abp/auditing/src/index.ts
  19. 62
      apps/vben5/packages/@abp/auditing/src/types/audit-logs.ts
  20. 63
      apps/vben5/packages/@abp/auditing/src/types/entity-changes.ts
  21. 2
      apps/vben5/packages/@abp/auditing/src/types/index.ts
  22. 6
      apps/vben5/packages/@abp/auditing/tsconfig.json
  23. 1
      apps/vben5/packages/@abp/core/package.json
  24. 1
      apps/vben5/packages/@abp/core/src/hooks/index.ts
  25. 45
      apps/vben5/packages/@abp/core/src/hooks/useWindowSizeFn.ts
  26. 1
      apps/vben5/packages/@abp/core/src/types/index.ts
  27. 3
      apps/vben5/packages/@abp/core/src/types/table.ts
  28. 9
      apps/vben5/packages/@abp/ui/package.json
  29. 4
      apps/vben5/packages/@abp/ui/src/adapter/vxe-table.ts
  30. 61
      apps/vben5/packages/@abp/ui/src/components/CodeEditor.vue
  31. 135
      apps/vben5/packages/@abp/ui/src/components/codemirror/CodeMirror.vue
  32. 525
      apps/vben5/packages/@abp/ui/src/components/codemirror/codemirror.css
  33. 21
      apps/vben5/packages/@abp/ui/src/components/codemirror/codemirror.ts
  34. 2
      apps/vben5/packages/@abp/ui/src/components/codemirror/index.ts
  35. 5
      apps/vben5/packages/@abp/ui/src/components/codemirror/types.ts
  36. 2
      apps/vben5/packages/@abp/ui/src/components/index.ts
  37. 1
      apps/vben5/packages/@abp/ui/src/index.ts
  38. 2
      apps/vben5/pnpm-workspace.yaml

1
apps/vben5/apps/app-antd/package.json

@ -27,6 +27,7 @@
},
"dependencies": {
"@abp/account": "workspace:*",
"@abp/auditing": "workspace:*",
"@abp/core": "workspace:*",
"@abp/identity": "workspace:*",
"@abp/request": "workspace:*",

3
apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json

@ -8,7 +8,8 @@
"role": "Role",
"claimTypes": "Claim Types",
"securityLogs": "Security Logs",
"organizationUnits": "Organization Units"
"organizationUnits": "Organization Units",
"auditLogs": "Audit Logs"
}
}
}

3
apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json

@ -8,7 +8,8 @@
"role": "角色",
"claimTypes": "身份标识",
"securityLogs": "安全日志",
"organizationUnits": "组织机构"
"organizationUnits": "组织机构",
"auditLogs": "审计日志"
}
}
}

9
apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts

@ -81,6 +81,15 @@ const routes: RouteRecordRaw[] = [
},
],
},
{
meta: {
title: $t('abp.manage.identity.auditLogs'),
icon: 'fluent-mdl2:compliance-audit',
},
name: 'AuditingAuditLogs',
path: '/manage/audit-logs',
component: () => import('#/views/auditing/audit-logs/index.vue'),
},
],
},
],

15
apps/vben5/apps/app-antd/src/views/auditing/audit-logs/index.vue

@ -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>

37
apps/vben5/packages/@abp/auditing/package.json

@ -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:"
}
}

36
apps/vben5/packages/@abp/auditing/src/api/audit-logs.ts

@ -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}`);
}

23
apps/vben5/packages/@abp/auditing/src/api/entity-changes.ts

@ -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,
},
);
}

240
apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogDrawer.vue

@ -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>

326
apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogTable.vue

@ -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>

57
apps/vben5/packages/@abp/auditing/src/components/audit-logs/mapping.ts

@ -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 };

45
apps/vben5/packages/@abp/auditing/src/components/entity-changes/EntityChangeDrawer.vue

@ -0,0 +1,45 @@
<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>
<EntityChangeTable :data="entityChanges" show-user-name />
</Drawer>
</template>
<style scoped></style>

172
apps/vben5/packages/@abp/auditing/src/components/entity-changes/EntityChangeTable.vue

@ -0,0 +1,172 @@
<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,
trigger: 'row',
}"
: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>

2
apps/vben5/packages/@abp/auditing/src/components/index.ts

@ -0,0 +1,2 @@
export { default as AuditLogTable } from './audit-logs/AuditLogTable.vue';
export { default as EntityChangeDrawer } from './entity-changes/EntityChangeDrawer.vue';

1
apps/vben5/packages/@abp/auditing/src/constants/index.ts

@ -0,0 +1 @@
export * from './permissions';

6
apps/vben5/packages/@abp/auditing/src/constants/permissions.ts

@ -0,0 +1,6 @@
/** 审计日志权限 */
export const AuditLogPermissions = {
Default: 'AbpAuditing.AuditLog',
/** 删除 */
Delete: 'AbpAuditing.AuditLog.Delete',
};

62
apps/vben5/packages/@abp/auditing/src/hooks/useAuditlogs.ts

@ -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,
};
}

3
apps/vben5/packages/@abp/auditing/src/index.ts

@ -0,0 +1,3 @@
export * from './components';
export * from './constants';
export * from './types';

62
apps/vben5/packages/@abp/auditing/src/types/audit-logs.ts

@ -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 };

63
apps/vben5/packages/@abp/auditing/src/types/entity-changes.ts

@ -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,
};

2
apps/vben5/packages/@abp/auditing/src/types/index.ts

@ -0,0 +1,2 @@
export * from './audit-logs';
export * from './entity-changes';

6
apps/vben5/packages/@abp/auditing/tsconfig.json

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web.json",
"include": ["src"],
"exclude": ["node_modules"]
}

1
apps/vben5/packages/@abp/core/package.json

@ -35,6 +35,7 @@
}
},
"dependencies": {
"@vueuse/core": "catalog:",
"dayjs": "catalog:",
"lodash": "catalog:",
"pinia": "catalog:",

1
apps/vben5/packages/@abp/core/src/hooks/index.ts

@ -1,3 +1,4 @@
export * from './useLocalization';
export * from './useSettings';
export * from './useValidation';
export * from './useWindowSizeFn';

45
apps/vben5/packages/@abp/core/src/hooks/useWindowSizeFn.ts

@ -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];
}

1
apps/vben5/packages/@abp/core/src/types/index.ts

@ -3,4 +3,5 @@ export * from './global';
export * from './localization';
export * from './rules';
export * from './settings';
export * from './table';
export * from './validations';

3
apps/vben5/packages/@abp/core/src/types/table.ts

@ -0,0 +1,3 @@
type SortOrder = '' | 'asc' | 'desc' | null;
export type { SortOrder };

9
apps/vben5/packages/@abp/ui/package.json

@ -35,10 +35,19 @@
}
},
"dependencies": {
"@abp/core": "workspace:*",
"@vben-core/preferences": "workspace:*",
"@vben-core/shared": "workspace:*",
"@vben-core/typings": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/plugins": "workspace:*",
"@vueuse/core": "catalog:",
"ant-design-vue": "catalog:",
"codemirror": "catalog:",
"vue": "catalog:*"
},
"devDependencies": {
"@types/codemirror": "catalog:"
}
}

4
apps/vben5/packages/@abp/ui/src/adapter/vxe-table.ts

@ -20,6 +20,10 @@ setupVbenVxeTable({
enabled: false,
},
minHeight: 180,
pagerConfig: {
pageSize: 10,
pageSizes: [10, 15, 25, 50, 100],
},
proxyConfig: {
autoLoad: true,
response: {

61
apps/vben5/packages/@abp/ui/src/components/CodeEditor.vue

@ -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>

135
apps/vben5/packages/@abp/ui/src/components/codemirror/CodeMirror.vue

@ -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>

525
apps/vben5/packages/@abp/ui/src/components/codemirror/codemirror.css

@ -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;
}

21
apps/vben5/packages/@abp/ui/src/components/codemirror/codemirror.ts

@ -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';

2
apps/vben5/packages/@abp/ui/src/components/codemirror/index.ts

@ -0,0 +1,2 @@
export { default as CodeMirror } from './CodeMirror.vue';
export * from './types';

5
apps/vben5/packages/@abp/ui/src/components/codemirror/types.ts

@ -0,0 +1,5 @@
export enum MODE {
HTML = 'htmlmixed',
JS = 'javascript',
JSON = 'application/json',
}

2
apps/vben5/packages/@abp/ui/src/components/index.ts

@ -0,0 +1,2 @@
export { default as CodeEditor } from './CodeEditor.vue';
export * from './codemirror';

1
apps/vben5/packages/@abp/ui/src/index.ts

@ -1,4 +1,5 @@
export * from './adapter/component';
export * from './adapter/form';
export * from './adapter/vxe-table';
export * from './components';
export type * from '@vben/plugins/vxe-table';

2
apps/vben5/pnpm-workspace.yaml

@ -41,6 +41,7 @@ catalog:
'@tanstack/vue-query': ^5.62.0
'@tanstack/vue-store': ^0.6.0
'@types/archiver': ^6.0.3
'@types/codemirror': ^5.60.15
'@types/eslint': ^9.6.1
'@types/html-minifier-terser': ^7.0.2
'@types/jsonwebtoken': ^9.0.7
@ -73,6 +74,7 @@ catalog:
circular-dependency-scanner: ^2.3.0
class-variance-authority: ^0.7.1
clsx: ^2.1.1
codemirror: ^5.65.3
commitlint-plugin-function-rules: ^4.0.1
consola: ^3.2.3
cross-env: ^7.0.3

Loading…
Cancel
Save