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 }); + }); +});