From a4884035cd6814d54f113263a8d4f99507d35095 Mon Sep 17 00:00:00 2001 From: hongawen <83944980@qq.com> Date: Sat, 27 Jun 2026 08:55:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(projects):=20=E5=A2=9E=E5=8A=A0=E6=84=8F?= =?UTF-8?q?=E8=A7=81=E5=8F=8D=E9=A6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/plugins/router.ts | 7 +- docs/frontend-page-resource-manifest.json | 2 +- .../custom/business-attachment-uploader.vue | 18 +- .../custom/business-rich-text-editor.vue | 4 +- src/constants/dict.ts | 16 + src/constants/status-tag.ts | 17 +- src/locales/langs/en-us.ts | 1 + src/locales/langs/zh-cn.ts | 1 + src/router/elegant/imports.ts | 1 + src/router/elegant/routes.ts | 13 + src/router/elegant/transform.ts | 1 + src/service/api/feedback-normalize.ts | 34 ++ src/service/api/feedback.ts | 185 ++++++++++ src/service/api/index.ts | 1 + src/typings/api/feedback.d.ts | 82 +++++ src/typings/api/project.d.ts | 4 + src/typings/components.d.ts | 2 + src/typings/elegant-router.d.ts | 3 + src/views/feedback/index.vue | 328 ++++++++++++++++++ .../modules/feedback-detail-dialog.vue | 115 ++++++ src/views/feedback/modules/feedback-facet.vue | 303 ++++++++++++++++ .../modules/feedback-operate-dialog.vue | 252 ++++++++++++++ .../feedback/modules/feedback-search.vue | 31 ++ .../modules/feedback-status-dialog.vue | 74 ++++ tests/feedback-stat-normalization.test.ts | 48 +++ 25 files changed, 1536 insertions(+), 7 deletions(-) create mode 100644 src/service/api/feedback-normalize.ts create mode 100644 src/service/api/feedback.ts create mode 100644 src/typings/api/feedback.d.ts create mode 100644 src/views/feedback/index.vue create mode 100644 src/views/feedback/modules/feedback-detail-dialog.vue create mode 100644 src/views/feedback/modules/feedback-facet.vue create mode 100644 src/views/feedback/modules/feedback-operate-dialog.vue create mode 100644 src/views/feedback/modules/feedback-search.vue create mode 100644 src/views/feedback/modules/feedback-status-dialog.vue create mode 100644 tests/feedback-stat-normalization.test.ts diff --git a/build/plugins/router.ts b/build/plugins/router.ts index 5fd990e..93eae69 100644 --- a/build/plugins/router.ts +++ b/build/plugins/router.ts @@ -27,7 +27,7 @@ export function setupElegantRouter() { onRouteMetaGen(routeName) { const key = routeName as RouteKey; - const constantRoutes: RouteKey[] = ['login', '403', '404', '500', 'workbench']; + const constantRoutes: RouteKey[] = ['login', '403', '404', '500', 'workbench', 'feedback']; const routeMetaMap: Partial>> = { workbench: { icon: 'mdi:view-dashboard-outline', @@ -200,6 +200,11 @@ export function setupElegantRouter() { roles: ['R_ADMIN'], activeMenu: 'system_user' }, + feedback: { + icon: 'mdi:message-alert-outline', + order: 10, + keepAlive: true + }, infra: { icon: 'ep:monitor', order: 20 diff --git a/docs/frontend-page-resource-manifest.json b/docs/frontend-page-resource-manifest.json index 706ece0..75770a3 100644 --- a/docs/frontend-page-resource-manifest.json +++ b/docs/frontend-page-resource-manifest.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-06-25T05:22:43.905Z", + "generatedAt": "2026-06-27T00:49:28.085Z", "description": "Frontend visible page resource whitelist for backend route/menu configuration.", "rules": { "directoryComponent": "layout.base", diff --git a/src/components/custom/business-attachment-uploader.vue b/src/components/custom/business-attachment-uploader.vue index 8c1caa0..fd40a11 100644 --- a/src/components/custom/business-attachment-uploader.vue +++ b/src/components/custom/business-attachment-uploader.vue @@ -1,7 +1,7 @@ + + + + diff --git a/src/views/feedback/modules/feedback-detail-dialog.vue b/src/views/feedback/modules/feedback-detail-dialog.vue new file mode 100644 index 0000000..c2e9ae3 --- /dev/null +++ b/src/views/feedback/modules/feedback-detail-dialog.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/src/views/feedback/modules/feedback-facet.vue b/src/views/feedback/modules/feedback-facet.vue new file mode 100644 index 0000000..35b94a4 --- /dev/null +++ b/src/views/feedback/modules/feedback-facet.vue @@ -0,0 +1,303 @@ + + + + + diff --git a/src/views/feedback/modules/feedback-operate-dialog.vue b/src/views/feedback/modules/feedback-operate-dialog.vue new file mode 100644 index 0000000..4f14b46 --- /dev/null +++ b/src/views/feedback/modules/feedback-operate-dialog.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/src/views/feedback/modules/feedback-search.vue b/src/views/feedback/modules/feedback-search.vue new file mode 100644 index 0000000..431bb03 --- /dev/null +++ b/src/views/feedback/modules/feedback-search.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/src/views/feedback/modules/feedback-status-dialog.vue b/src/views/feedback/modules/feedback-status-dialog.vue new file mode 100644 index 0000000..e12c835 --- /dev/null +++ b/src/views/feedback/modules/feedback-status-dialog.vue @@ -0,0 +1,74 @@ + + + diff --git a/tests/feedback-stat-normalization.test.ts b/tests/feedback-stat-normalization.test.ts new file mode 100644 index 0000000..a6e6ce7 --- /dev/null +++ b/tests/feedback-stat-normalization.test.ts @@ -0,0 +1,48 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import { normalizeFeedbackStat } from '../src/service/api/feedback-normalize'; + +describe('normalizeFeedbackStat', () => { + it('maps backend type/status count arrays to code-keyed records', () => { + const result = normalizeFeedbackStat({ + total: 128, + typeCounts: [ + { type: 1, count: 52 }, + { type: 2, count: 31 }, + { type: 3, count: 45 } + ], + statusCounts: [ + { status: 1, count: 40 }, + { status: 2, count: 33 }, + { status: 3, count: 50 }, + { status: 4, count: 5 } + ] + }); + + assert.deepEqual(result, { + total: 128, + typeCounts: { + '1': 52, + '2': 31, + '3': 45 + }, + statusCounts: { + '1': 40, + '2': 33, + '3': 50, + '4': 5 + } + }); + }); + + it('keeps zero-count dictionary codes visible to the facet panel', () => { + const result = normalizeFeedbackStat({ + total: 0, + typeCounts: [{ type: '1', count: 0 }], + statusCounts: [{ status: '4', count: 0 }] + }); + + assert.deepEqual(result.typeCounts, { '1': 0 }); + assert.deepEqual(result.statusCounts, { '4': 0 }); + }); +});