diff --git a/apps/vben5/apps/app-antd/package.json b/apps/vben5/apps/app-antd/package.json
index f12c4b965..2a5979c5f 100644
--- a/apps/vben5/apps/app-antd/package.json
+++ b/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:*",
diff --git a/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json b/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json
index 199e139ab..f995ca518 100644
--- a/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json
+++ b/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"
}
}
}
diff --git a/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json b/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json
index fd9d57776..3f6b04aa2 100644
--- a/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json
+++ b/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json
@@ -8,7 +8,8 @@
"role": "角色",
"claimTypes": "身份标识",
"securityLogs": "安全日志",
- "organizationUnits": "组织机构"
+ "organizationUnits": "组织机构",
+ "auditLogs": "审计日志"
}
}
}
diff --git a/apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts b/apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts
index 6fd11549e..457a439c5 100644
--- a/apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts
+++ b/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'),
+ },
],
},
],
diff --git a/apps/vben5/apps/app-antd/src/views/auditing/audit-logs/index.vue b/apps/vben5/apps/app-antd/src/views/auditing/audit-logs/index.vue
new file mode 100644
index 000000000..2ee3b0880
--- /dev/null
+++ b/apps/vben5/apps/app-antd/src/views/auditing/audit-logs/index.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/apps/vben5/packages/@abp/auditing/package.json b/apps/vben5/packages/@abp/auditing/package.json
new file mode 100644
index 000000000..79f93552d
--- /dev/null
+++ b/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:"
+ }
+}
diff --git a/apps/vben5/packages/@abp/auditing/src/api/audit-logs.ts b/apps/vben5/packages/@abp/auditing/src/api/audit-logs.ts
new file mode 100644
index 000000000..d400b91a1
--- /dev/null
+++ b/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 {
+ return requestClient.get(`/api/auditing/audit-log/${id}`);
+}
+
+/**
+ * 获取审计日志分页列表
+ * @param input 参数
+ */
+export function getPagedListApi(
+ input: AuditLogGetListInput,
+): Promise> {
+ return requestClient.get>(
+ '/api/auditing/audit-log',
+ {
+ params: input,
+ },
+ );
+}
+
+/**
+ * 删除审计日志
+ * @param id 日志id
+ */
+export function deleteApi(id: string): Promise {
+ return requestClient.delete(`/api/auditing/audit-log/${id}`);
+}
diff --git a/apps/vben5/packages/@abp/auditing/src/api/entity-changes.ts b/apps/vben5/packages/@abp/auditing/src/api/entity-changes.ts
new file mode 100644
index 000000000..b80499d61
--- /dev/null
+++ b/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> {
+ return requestClient.get>(
+ '/api/auditing/entity-changes/with-username',
+ {
+ params: input,
+ },
+ );
+}
diff --git a/apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogDrawer.vue b/apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogDrawer.vue
new file mode 100644
index 000000000..80f441f92
--- /dev/null
+++ b/apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogDrawer.vue
@@ -0,0 +1,240 @@
+
+
+
+
+
+
+
+
+
+ {{ auditLogModel.applicationName }}
+
+
+ {{ formatToDateTime(auditLogModel.executionTime) }}
+
+
+ {{ auditLogModel.userName }}
+
+
+
+ {{ auditLogModel.httpMethod }}
+
+
+
+ {{ auditLogModel.url }}
+
+
+
+ {{ auditLogModel.httpStatusCode }}
+
+
+
+ {{ auditLogModel.executionDuration }}
+
+
+ {{ auditLogModel.clientId }}
+
+
+ {{ auditLogModel.clientIpAddress }}
+
+
+ {{ auditLogModel.clientName }}
+
+
+ {{ auditLogModel.correlationId }}
+
+
+ {{ auditLogModel.browserInfo }}
+
+
+ {{ auditLogModel.comments }}
+
+
+ {{ auditLogModel.exceptions }}
+
+
+ {{ auditLogModel.extraProperties }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogTable.vue b/apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogTable.vue
new file mode 100644
index 000000000..90a2848de
--- /dev/null
+++ b/apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogTable.vue
@@ -0,0 +1,326 @@
+
+
+
+
+
+
+ {{ row.httpStatusCode }}
+
+
+ {{ row.httpMethod }}
+
+ {{ row.url }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/vben5/packages/@abp/auditing/src/components/audit-logs/mapping.ts b/apps/vben5/packages/@abp/auditing/src/components/audit-logs/mapping.ts
new file mode 100644
index 000000000..01f6c8da0
--- /dev/null
+++ b/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 };
diff --git a/apps/vben5/packages/@abp/auditing/src/components/entity-changes/EntityChangeDrawer.vue b/apps/vben5/packages/@abp/auditing/src/components/entity-changes/EntityChangeDrawer.vue
new file mode 100644
index 000000000..2e7262a67
--- /dev/null
+++ b/apps/vben5/packages/@abp/auditing/src/components/entity-changes/EntityChangeDrawer.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
diff --git a/apps/vben5/packages/@abp/auditing/src/components/entity-changes/EntityChangeTable.vue b/apps/vben5/packages/@abp/auditing/src/components/entity-changes/EntityChangeTable.vue
new file mode 100644
index 000000000..bf7c6eb18
--- /dev/null
+++ b/apps/vben5/packages/@abp/auditing/src/components/entity-changes/EntityChangeTable.vue
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+ {{ getChangeTypeValue(row.changeType) }}
+
+
+
+
+
+
+
+
+
diff --git a/apps/vben5/packages/@abp/auditing/src/components/index.ts b/apps/vben5/packages/@abp/auditing/src/components/index.ts
new file mode 100644
index 000000000..1d99b4a46
--- /dev/null
+++ b/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';
diff --git a/apps/vben5/packages/@abp/auditing/src/constants/index.ts b/apps/vben5/packages/@abp/auditing/src/constants/index.ts
new file mode 100644
index 000000000..c85954d3e
--- /dev/null
+++ b/apps/vben5/packages/@abp/auditing/src/constants/index.ts
@@ -0,0 +1 @@
+export * from './permissions';
diff --git a/apps/vben5/packages/@abp/auditing/src/constants/permissions.ts b/apps/vben5/packages/@abp/auditing/src/constants/permissions.ts
new file mode 100644
index 000000000..00e8dc003
--- /dev/null
+++ b/apps/vben5/packages/@abp/auditing/src/constants/permissions.ts
@@ -0,0 +1,6 @@
+/** 审计日志权限 */
+export const AuditLogPermissions = {
+ Default: 'AbpAuditing.AuditLog',
+ /** 删除 */
+ Delete: 'AbpAuditing.AuditLog.Delete',
+};
diff --git a/apps/vben5/packages/@abp/auditing/src/hooks/useAuditlogs.ts b/apps/vben5/packages/@abp/auditing/src/hooks/useAuditlogs.ts
new file mode 100644
index 000000000..97dc83cdc
--- /dev/null
+++ b/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,
+ };
+}
diff --git a/apps/vben5/packages/@abp/auditing/src/index.ts b/apps/vben5/packages/@abp/auditing/src/index.ts
new file mode 100644
index 000000000..c688f3a89
--- /dev/null
+++ b/apps/vben5/packages/@abp/auditing/src/index.ts
@@ -0,0 +1,3 @@
+export * from './components';
+export * from './constants';
+export * from './types';
diff --git a/apps/vben5/packages/@abp/auditing/src/types/audit-logs.ts b/apps/vben5/packages/@abp/auditing/src/types/audit-logs.ts
new file mode 100644
index 000000000..58c1ab751
--- /dev/null
+++ b/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 };
diff --git a/apps/vben5/packages/@abp/auditing/src/types/entity-changes.ts b/apps/vben5/packages/@abp/auditing/src/types/entity-changes.ts
new file mode 100644
index 000000000..0ae5d4680
--- /dev/null
+++ b/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,
+};
diff --git a/apps/vben5/packages/@abp/auditing/src/types/index.ts b/apps/vben5/packages/@abp/auditing/src/types/index.ts
new file mode 100644
index 000000000..900570716
--- /dev/null
+++ b/apps/vben5/packages/@abp/auditing/src/types/index.ts
@@ -0,0 +1,2 @@
+export * from './audit-logs';
+export * from './entity-changes';
diff --git a/apps/vben5/packages/@abp/auditing/tsconfig.json b/apps/vben5/packages/@abp/auditing/tsconfig.json
new file mode 100644
index 000000000..ce1a891fb
--- /dev/null
+++ b/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"]
+}
diff --git a/apps/vben5/packages/@abp/core/package.json b/apps/vben5/packages/@abp/core/package.json
index 8beec67f0..b46de0539 100644
--- a/apps/vben5/packages/@abp/core/package.json
+++ b/apps/vben5/packages/@abp/core/package.json
@@ -35,6 +35,7 @@
}
},
"dependencies": {
+ "@vueuse/core": "catalog:",
"dayjs": "catalog:",
"lodash": "catalog:",
"pinia": "catalog:",
diff --git a/apps/vben5/packages/@abp/core/src/hooks/index.ts b/apps/vben5/packages/@abp/core/src/hooks/index.ts
index 1582c45c5..2d4c33225 100644
--- a/apps/vben5/packages/@abp/core/src/hooks/index.ts
+++ b/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';
diff --git a/apps/vben5/packages/@abp/core/src/hooks/useWindowSizeFn.ts b/apps/vben5/packages/@abp/core/src/hooks/useWindowSizeFn.ts
new file mode 100644
index 000000000..9a617221a
--- /dev/null
+++ b/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 {
+ (...arg: T[]): R;
+}
+
+export function useWindowSizeFn(
+ fn: Fn,
+ 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];
+}
diff --git a/apps/vben5/packages/@abp/core/src/types/index.ts b/apps/vben5/packages/@abp/core/src/types/index.ts
index 97fa56fd8..cde3fc4a5 100644
--- a/apps/vben5/packages/@abp/core/src/types/index.ts
+++ b/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';
diff --git a/apps/vben5/packages/@abp/core/src/types/table.ts b/apps/vben5/packages/@abp/core/src/types/table.ts
new file mode 100644
index 000000000..9d76b4da6
--- /dev/null
+++ b/apps/vben5/packages/@abp/core/src/types/table.ts
@@ -0,0 +1,3 @@
+type SortOrder = '' | 'asc' | 'desc' | null;
+
+export type { SortOrder };
diff --git a/apps/vben5/packages/@abp/ui/package.json b/apps/vben5/packages/@abp/ui/package.json
index 2316f47fe..e039de782 100644
--- a/apps/vben5/packages/@abp/ui/package.json
+++ b/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:"
}
}
diff --git a/apps/vben5/packages/@abp/ui/src/adapter/vxe-table.ts b/apps/vben5/packages/@abp/ui/src/adapter/vxe-table.ts
index 36d59ee55..8f959d53a 100644
--- a/apps/vben5/packages/@abp/ui/src/adapter/vxe-table.ts
+++ b/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: {
diff --git a/apps/vben5/packages/@abp/ui/src/components/CodeEditor.vue b/apps/vben5/packages/@abp/ui/src/components/CodeEditor.vue
new file mode 100644
index 000000000..829ccc7da
--- /dev/null
+++ b/apps/vben5/packages/@abp/ui/src/components/CodeEditor.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+
diff --git a/apps/vben5/packages/@abp/ui/src/components/codemirror/CodeMirror.vue b/apps/vben5/packages/@abp/ui/src/components/codemirror/CodeMirror.vue
new file mode 100644
index 000000000..3593f8193
--- /dev/null
+++ b/apps/vben5/packages/@abp/ui/src/components/codemirror/CodeMirror.vue
@@ -0,0 +1,135 @@
+
+
+
+
+
diff --git a/apps/vben5/packages/@abp/ui/src/components/codemirror/codemirror.css b/apps/vben5/packages/@abp/ui/src/components/codemirror/codemirror.css
new file mode 100644
index 000000000..cbcfa8764
--- /dev/null
+++ b/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;
+}
diff --git a/apps/vben5/packages/@abp/ui/src/components/codemirror/codemirror.ts b/apps/vben5/packages/@abp/ui/src/components/codemirror/codemirror.ts
new file mode 100644
index 000000000..ad87e70b7
--- /dev/null
+++ b/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';
diff --git a/apps/vben5/packages/@abp/ui/src/components/codemirror/index.ts b/apps/vben5/packages/@abp/ui/src/components/codemirror/index.ts
new file mode 100644
index 000000000..3898fc040
--- /dev/null
+++ b/apps/vben5/packages/@abp/ui/src/components/codemirror/index.ts
@@ -0,0 +1,2 @@
+export { default as CodeMirror } from './CodeMirror.vue';
+export * from './types';
diff --git a/apps/vben5/packages/@abp/ui/src/components/codemirror/types.ts b/apps/vben5/packages/@abp/ui/src/components/codemirror/types.ts
new file mode 100644
index 000000000..1b7e3e32a
--- /dev/null
+++ b/apps/vben5/packages/@abp/ui/src/components/codemirror/types.ts
@@ -0,0 +1,5 @@
+export enum MODE {
+ HTML = 'htmlmixed',
+ JS = 'javascript',
+ JSON = 'application/json',
+}
diff --git a/apps/vben5/packages/@abp/ui/src/components/index.ts b/apps/vben5/packages/@abp/ui/src/components/index.ts
new file mode 100644
index 000000000..a1891146b
--- /dev/null
+++ b/apps/vben5/packages/@abp/ui/src/components/index.ts
@@ -0,0 +1,2 @@
+export { default as CodeEditor } from './CodeEditor.vue';
+export * from './codemirror';
diff --git a/apps/vben5/packages/@abp/ui/src/index.ts b/apps/vben5/packages/@abp/ui/src/index.ts
index c114d443f..8a270df36 100644
--- a/apps/vben5/packages/@abp/ui/src/index.ts
+++ b/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';
diff --git a/apps/vben5/pnpm-workspace.yaml b/apps/vben5/pnpm-workspace.yaml
index f0564f794..9c2c6ed49 100644
--- a/apps/vben5/pnpm-workspace.yaml
+++ b/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