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