From c108d7aff3bf98771c842bf3dc39517c545afe26 Mon Sep 17 00:00:00 2001 From: yinlo Date: Tue, 10 Jun 2025 16:05:59 +0800 Subject: [PATCH 1/4] add entity audit feature with Vue support --- generators/client/generator.js | 2 + .../src/main/webapp/i18n/th/entity-audit.json | 49 ++++ .../main/webapp/i18n/zh-cn/entity-audit.json | 49 ++++ .../__snapshots__/generator.spec.js.snap | 30 +++ generators/vue-audit/command.js | 5 + generators/vue-audit/generator.js | 80 +++++++ generators/vue-audit/index.js | 2 + generators/vue-audit/resources/package.json | 8 + .../entity-audit-event.model.ts.ejs | 15 ++ .../entity-audit-modal.component.vue.ejs | 212 ++++++++++++++++++ .../entity-audit.component.vue.ejs | 202 +++++++++++++++++ .../entity-audit/entity-audit.service.ts.ejs | 19 ++ 12 files changed, 673 insertions(+) create mode 100644 generators/languages/templates/src/main/webapp/i18n/th/entity-audit.json create mode 100644 generators/languages/templates/src/main/webapp/i18n/zh-cn/entity-audit.json create mode 100644 generators/vue-audit/__snapshots__/generator.spec.js.snap create mode 100644 generators/vue-audit/command.js create mode 100644 generators/vue-audit/generator.js create mode 100644 generators/vue-audit/index.js create mode 100644 generators/vue-audit/resources/package.json create mode 100644 generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit-event.model.ts.ejs create mode 100644 generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit-modal.component.vue.ejs create mode 100644 generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit.component.vue.ejs create mode 100644 generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit.service.ts.ejs diff --git a/generators/client/generator.js b/generators/client/generator.js index 58c79625..88e041e7 100644 --- a/generators/client/generator.js +++ b/generators/client/generator.js @@ -14,6 +14,8 @@ export default class extends BaseApplicationGenerator { async composeTask() { if (this.blueprintConfig.auditPage && ['angularX', 'angular'].includes(this.jhipsterConfigWithDefaults.clientFramework)) { await this.composeWithJHipster('jhipster-entity-audit:angular-audit'); + } else if (this.blueprintConfig.auditPage && ['vue'].includes(this.jhipsterConfigWithDefaults.clientFramework)) { + await this.composeWithJHipster('jhipster-entity-audit:vue-audit'); } }, }); diff --git a/generators/languages/templates/src/main/webapp/i18n/th/entity-audit.json b/generators/languages/templates/src/main/webapp/i18n/th/entity-audit.json new file mode 100644 index 00000000..605bc78e --- /dev/null +++ b/generators/languages/templates/src/main/webapp/i18n/th/entity-audit.json @@ -0,0 +1,49 @@ +{ + "global": { + "menu": { + "admin": { + "entityAudit": "การตรวจสอบข้อมูล" + } + } + }, + "entityAudit": { + "home": { + "title": "รายการการตรวจสอบข้อมูล", + "filter": "ตัวกรอง", + "entityOrTable": "เอนทิตี/ตาราง", + "limitTo": "จำกัดจำนวน", + "loadChangeList": "โหลดรายการเปลี่ยนแปลง" + }, + "result": { + "showInfo": "แสดง {{ limit }} การเปลี่ยนแปลงล่าสุดของ {{ entity }}", + "searchFieldLabel": "ตัวกรอง:", + "globalFilter": "ตัวกรองทั่วไป", + "entityIdFilter": "ตัวกรอง ID ของเอนทิตี", + "tableHeader": { + "entityId": "ID ของเอนทิตี", + "action": "การกระทำ", + "version": "เวอร์ชัน", + "value": "ค่า", + "modifiedDate": "วันที่แก้ไข", + "modifiedBy": "ผู้แก้ไข" + }, + "tableBody": { + "field": "ฟิลด์", + "value": "ค่า", + "viewDetails": "ดูรายละเอียดการเปลี่ยนแปลง" + }, + "noDataFound": "ไม่พบข้อมูลตามเงื่อนไขที่กรอง", + "firstAuditEntry": "ไม่มีเวอร์ชันก่อนหน้าสำหรับรายการนี้\nเป็นรายการบันทึกการตรวจสอบครั้งแรกของวัตถุนี้" + }, + "detail": { + "close": "ปิด", + "title": "รายละเอียดการตรวจสอบ", + "action": "มีการดำเนินการ {{action}} กับข้อมูลด้านล่าง", + "old": "ค่าเก่า/ค่าที่ถูกลบ", + "new": "ค่าใหม่/ค่าที่เพิ่ม", + "changedFields": "ฟิลด์ที่เปลี่ยนแปลง", + "allFields": "ฟิลด์ทั้งหมด", + "done": "เสร็จสิ้น" + } + } +} diff --git a/generators/languages/templates/src/main/webapp/i18n/zh-cn/entity-audit.json b/generators/languages/templates/src/main/webapp/i18n/zh-cn/entity-audit.json new file mode 100644 index 00000000..220012a2 --- /dev/null +++ b/generators/languages/templates/src/main/webapp/i18n/zh-cn/entity-audit.json @@ -0,0 +1,49 @@ +{ + "global": { + "menu": { + "admin": { + "entityAudit": "实体审计" + } + } + }, + "entityAudit": { + "home": { + "title": "实体审计", + "filter": "筛选", + "entityOrTable": "实体/表", + "limitTo": "限制到", + "loadChangeList": "加载变更列表" + }, + "result": { + "showInfo": "{{ entity }} 的最近 {{ limit }} 个变更", + "searchFieldLabel": "筛选:", + "globalFilter": "全局筛选", + "entityIdFilter": "实体ID筛选", + "tableHeader": { + "entityId": "实体ID", + "action": "操作", + "version": "版本", + "value": "值", + "modifiedDate": "修改日期", + "modifiedBy": "修改人" + }, + "tableBody": { + "field": "字段", + "value": "值", + "viewDetails": "查看审计变更详情" + }, + "noDataFound": "未找到符合条件的数据", + "firstAuditEntry": "此条目没有之前的版本。\n这是为此对象捕获的第一个审计条目。" + }, + "detail": { + "close": "关闭", + "title": "审计详情", + "action": "以下数据执行了{{action}}操作", + "old": "旧值/移除的值", + "new": "新值/添加的值", + "changedFields": "更改字段", + "allFields": "所有字段", + "done": "完成" + } + } +} diff --git a/generators/vue-audit/__snapshots__/generator.spec.js.snap b/generators/vue-audit/__snapshots__/generator.spec.js.snap new file mode 100644 index 00000000..258b5d45 --- /dev/null +++ b/generators/vue-audit/__snapshots__/generator.spec.js.snap @@ -0,0 +1,30 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`SubGenerator angular-audit of entity-audit JHipster blueprint > run > should succeed 1`] = ` +{ + ".yo-rc.json": { + "stateCleared": "modified", + }, + "package.json": { + "stateCleared": "modified", + }, + "src/main/webapp/app/admin/entity-audit/entity-audit-event.model.ts": { + "stateCleared": "modified", + }, + "src/main/webapp/app/admin/entity-audit/entity-audit-modal.component.html": { + "stateCleared": "modified", + }, + "src/main/webapp/app/admin/entity-audit/entity-audit-modal.component.ts": { + "stateCleared": "modified", + }, + "src/main/webapp/app/admin/entity-audit/entity-audit.component.html": { + "stateCleared": "modified", + }, + "src/main/webapp/app/admin/entity-audit/entity-audit.component.ts": { + "stateCleared": "modified", + }, + "src/main/webapp/app/admin/entity-audit/entity-audit.service.ts": { + "stateCleared": "modified", + }, +} +`; diff --git a/generators/vue-audit/command.js b/generators/vue-audit/command.js new file mode 100644 index 00000000..6a3a3819 --- /dev/null +++ b/generators/vue-audit/command.js @@ -0,0 +1,5 @@ +import { asCommand } from 'generator-jhipster'; + +export default asCommand({ + configs: {}, +}); \ No newline at end of file diff --git a/generators/vue-audit/generator.js b/generators/vue-audit/generator.js new file mode 100644 index 00000000..09e0eecb --- /dev/null +++ b/generators/vue-audit/generator.js @@ -0,0 +1,80 @@ +import BaseApplicationGenerator from 'generator-jhipster/generators/base-application'; +import { clientApplicationTemplatesBlock } from 'generator-jhipster/generators/client/support'; + +export default class extends BaseApplicationGenerator { + vueVersion; + + constructor(args, opts, features) { + super(args, opts, { ...features, queueCommandTasks: true }); + } + + async beforeQueue() { + await this.dependsOnJHipster('bootstrap-application'); + } + + get [BaseApplicationGenerator.PREPARING]() { + return this.asPreparingTaskGroup({ + loadDependabot() { + const { + dependencies: { vue: vueVersion }, + } = this.fs.readJSON(this.templatePath('../resources/package.json')); + this.vueVersion = vueVersion; + }, + }); + } + + get [BaseApplicationGenerator.WRITING]() { + return this.asWritingTaskGroup({ + cleanup() { + // 可选清理逻辑 + }, + + writingTemplateTask({ application }) { + return this.writeFiles({ + sections: { + files: [ + { + ...clientApplicationTemplatesBlock('admin/entity-audit/'), + templates: [ + 'entity-audit-event.model.ts', + 'entity-audit-modal.component.vue', + 'entity-audit.component.vue', + 'entity-audit.service.ts', + ], + }, + ], + }, + context: application, + }); + }, + }); + } + + get [BaseApplicationGenerator.POST_WRITING]() { + return this.asPostWritingTaskGroup({ + postWritingTemplateTask({ source }) { + this.packageJson.merge({ + dependencies: { + vue: this.vueVersion, + }, + }); + + if (this.options.skipMenu) return; + + source.addAdminRoute?.({ + route: 'entity-audit', + modulePath: './entity-audit/entity-audit.component.vue', + title: 'entityAudit.home.title', + component: true, + }); + + source.addItemToAdminMenu?.({ + icon: 'list', + route: 'admin/entity-audit', + translationKey: 'global.menu.admin.entityAudit', + name: 'Entity Audit', + }); + }, + }); + } +} diff --git a/generators/vue-audit/index.js b/generators/vue-audit/index.js new file mode 100644 index 00000000..06a5ce4a --- /dev/null +++ b/generators/vue-audit/index.js @@ -0,0 +1,2 @@ +export { default } from './generator.js'; +export { default as command } from './command.js'; \ No newline at end of file diff --git a/generators/vue-audit/resources/package.json b/generators/vue-audit/resources/package.json new file mode 100644 index 00000000..df58c26f --- /dev/null +++ b/generators/vue-audit/resources/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "vue-diff": "1.2.4", + "dompurify": "3.2.6", + "deep-diff": "1.0.2", + "deepmerge": "4.3.1" + } +} diff --git a/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit-event.model.ts.ejs b/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit-event.model.ts.ejs new file mode 100644 index 00000000..cf17fc14 --- /dev/null +++ b/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit-event.model.ts.ejs @@ -0,0 +1,15 @@ +export type EntityAuditEntityChoice = { + name: string; + value: string; +}; + +export type EntityAuditEvent = { + id: number; + entityId: any; + entityType: string; + action: string; + entityValue?: string; + commitVersion?: number; + modifiedBy?: string; + modifiedDate: Date; +}; \ No newline at end of file diff --git a/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit-modal.component.vue.ejs b/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit-modal.component.vue.ejs new file mode 100644 index 00000000..40dc619a --- /dev/null +++ b/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit-modal.component.vue.ejs @@ -0,0 +1,212 @@ + + + + + diff --git a/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit.component.vue.ejs b/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit.component.vue.ejs new file mode 100644 index 00000000..a51bc3ac --- /dev/null +++ b/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit.component.vue.ejs @@ -0,0 +1,202 @@ + + + + + diff --git a/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit.service.ts.ejs b/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit.service.ts.ejs new file mode 100644 index 00000000..6d210169 --- /dev/null +++ b/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit.service.ts.ejs @@ -0,0 +1,19 @@ +import axios from 'axios'; + +class EntityAuditService { + getAllAudited() { + return axios.get('api/audits/entity/all').then(res => res.data); + } + + findByEntity(entity, limit) { + return axios.get(`api/audits/entity/changes?entityType=${entity}&limit=${limit}`).then(res => res.data); + } + + getPrevVersion(qualifiedName, entityId, commitVersion) { + return axios.get( + `api/audits/entity/changes/version/previous?qualifiedName=${qualifiedName}&entityId=${entityId}&commitVersion=${commitVersion}` + ).then(res => res.data); + } +} + +export default EntityAuditService; From d9e443f79f25d792394ab34f7bfc151c6ccfa802 Mon Sep 17 00:00:00 2001 From: yinlo Date: Mon, 16 Jun 2025 10:26:53 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat(.yo-rc.json):=20=E6=B7=BB=E5=8A=A0=20v?= =?UTF-8?q?ue-audit=20=E5=AD=90=E7=94=9F=E6=88=90=E5=99=A8=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AE=9E=E4=BD=93=E5=AE=A1=E8=AE=A1=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 .yo-rc.json 中添加 vue-audit 子生成器 -修复实体审计组件中的版本比较逻辑 - 优化实体审计数据的处理方式 --- .yo-rc.json | 2 +- .../app/admin/entity-audit/entity-audit.component.vue.ejs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.yo-rc.json b/.yo-rc.json index 00e18a79..cd6cf0da 100644 --- a/.yo-rc.json +++ b/.yo-rc.json @@ -1,6 +1,6 @@ { "generator-jhipster": { - "additionalSubGenerators": "angular-audit,java-audit,spring-boot-custom-audit,spring-boot-javers", + "additionalSubGenerators": "angular-audit,java-audit,spring-boot-custom-audit,spring-boot-javers,vue-audit", "baseName": "entity-audit", "caret": true, "cli": true, diff --git a/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit.component.vue.ejs b/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit.component.vue.ejs index a51bc3ac..432fc84c 100644 --- a/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit.component.vue.ejs +++ b/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit.component.vue.ejs @@ -180,10 +180,10 @@ async function openChange(audit) { if (!audit.commitVersion || audit.commitVersion < 2) { alert(t('entityAudit.result.firstAuditEntry')); } else { - const prev = await service.getPrevVersion(selectedEntity.value, audit.entityId, audit.commitVersion); + // const prev = await service.getPrevVersion(selectedEntity.value, audit.entityId, audit.commitVersion); // 增加给prev的entityType更新值 - prev.entityType = selectedEntity.value; - modal.value?.openChange(prev); + audit.entityType = selectedEntity.value; + modal.value?.openChange(audit); } } From 4946948bf36b3bff678f322fc596864e428c5d17 Mon Sep 17 00:00:00 2001 From: yinlo Date: Mon, 16 Jun 2025 10:32:32 +0800 Subject: [PATCH 3/4] =?UTF-8?q?refactor(generators):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20vue-audit=20=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84=E5=92=8C?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调整了 command.js 和 index.js 的导入顺序 - 更新了 package.json 中的依赖项顺序 - 统一了代码格式和结构 --- generators/vue-audit/command.js | 2 +- generators/vue-audit/index.js | 2 +- generators/vue-audit/resources/package.json | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/generators/vue-audit/command.js b/generators/vue-audit/command.js index 6a3a3819..ea17f84d 100644 --- a/generators/vue-audit/command.js +++ b/generators/vue-audit/command.js @@ -2,4 +2,4 @@ import { asCommand } from 'generator-jhipster'; export default asCommand({ configs: {}, -}); \ No newline at end of file +}); diff --git a/generators/vue-audit/index.js b/generators/vue-audit/index.js index 06a5ce4a..3eccd6e8 100644 --- a/generators/vue-audit/index.js +++ b/generators/vue-audit/index.js @@ -1,2 +1,2 @@ export { default } from './generator.js'; -export { default as command } from './command.js'; \ No newline at end of file +export { default as command } from './command.js'; diff --git a/generators/vue-audit/resources/package.json b/generators/vue-audit/resources/package.json index df58c26f..46f61659 100644 --- a/generators/vue-audit/resources/package.json +++ b/generators/vue-audit/resources/package.json @@ -1,8 +1,8 @@ { "dependencies": { - "vue-diff": "1.2.4", - "dompurify": "3.2.6", "deep-diff": "1.0.2", - "deepmerge": "4.3.1" + "deepmerge": "4.3.1", + "dompurify": "3.2.6", + "vue-diff": "1.2.4" } } From 1a4b55dd8748d5edd30801d2652f9e940caf5eea Mon Sep 17 00:00:00 2001 From: yinlo Date: Mon, 16 Jun 2025 22:56:19 +0800 Subject: [PATCH 4/4] =?UTF-8?q?refactor(entity-audit):=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E7=BF=BB=E8=AF=91=E5=92=8C=20HTML=20=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E9=80=BB=E8=BE=91-=20=E7=A7=BB=E9=99=A4=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=86=85=E7=9A=84=20renderTranslation=20=E5=92=8C=20s?= =?UTF-8?q?anitize=20=E5=87=BD=E6=95=B0=20-=20=E5=88=9B=E5=BB=BA=20Transla?= =?UTF-8?q?teHtml=20=E7=BB=84=E4=BB=B6=E7=94=A8=E4=BA=8E=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=92=8C=E5=AE=89=E5=85=A8=20HTML=20=E6=B8=B2=E6=9F=93=20-=20?= =?UTF-8?q?=E5=9C=A8=20entity-audit=20=E5=92=8C=20entity-audit-modal=20?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E4=B8=AD=E4=BD=BF=E7=94=A8=20TranslateHtml?= =?UTF-8?q?=20=E7=BB=84=E4=BB=B6=20-=20=E4=BC=98=E5=8C=96=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E9=80=BB=E8=BE=91=EF=BC=8C=E6=94=AF=E6=8C=81=E5=A4=9A?= =?UTF-8?q?=E7=A7=8D=E5=8D=A0=E4=BD=8D=E7=AC=A6=E6=A0=BC=E5=BC=8F=E5=92=8C?= =?UTF-8?q?=E6=9B=B4=E7=81=B5=E6=B4=BB=E7=9A=84=E7=BF=BB=E8=AF=91=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity-audit-modal.component.vue.ejs | 20 ++--- .../entity-audit.component.vue.ejs | 28 ++---- .../admin/entity-audit/translate-html.vue.ejs | 90 +++++++++++++++++++ 3 files changed, 100 insertions(+), 38 deletions(-) create mode 100644 generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/translate-html.vue.ejs diff --git a/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit-modal.component.vue.ejs b/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit-modal.component.vue.ejs index 40dc619a..31f99e8a 100644 --- a/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit-modal.component.vue.ejs +++ b/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/entity-audit-modal.component.vue.ejs @@ -12,7 +12,10 @@
-
+ path="entityAudit.result.showInfo" + :values="{ entity: selectedEntity, limit: selectedLimit }" + />
@@ -99,6 +100,7 @@ import { ref, computed, onMounted } from 'vue'; import { useI18n } from 'vue-i18n'; import EntityAuditModal from './entity-audit-modal.component.vue'; import EntityAuditService from './entity-audit.service'; +import TranslateHtml from "./entity-audit/translate-html.vue"; const { t } = useI18n(); @@ -132,26 +134,6 @@ const filteredAudits = computed(() => { return result; }); -function renderTranslation(text, params) { - let result = text; - // 匹配所有 {{ key }} 占位符 - const matches = text.match(/{{ *?\w+ *?}}/g) || []; - // 提取 key 并替换为对应值 - matches.forEach(match => { - // 提取 key(去掉 {{ 和 }}) - const key = match.replace(/{{ *?| *?}}/g, '').trim(); - // 如果 params 中有对应的 key,则替换 - if (params[key] !== undefined) { - result = result.replace(match, params[key]); - } - }); - // 使用 DOMPurify 清理输出,确保安全 - return this.sanitize(result); -} -function sanitize(html) { - // 假设已引入 DOMPurify - return window.DOMPurify ? window.DOMPurify.sanitize(html) : html; -} function orderBy(prop) { ascending.value = orderProp.value === prop ? !ascending.value : true; diff --git a/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/translate-html.vue.ejs b/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/translate-html.vue.ejs new file mode 100644 index 00000000..bd79db8c --- /dev/null +++ b/generators/vue-audit/templates/src/main/webapp/app/admin/entity-audit/translate-html.vue.ejs @@ -0,0 +1,90 @@ + + + +