From 5740bd26e7600a95e15e38d3cc50f4050c87d230 Mon Sep 17 00:00:00 2001 From: Nelson <1475262689@qq.com> Date: Tue, 25 Aug 2026 18:49:56 +0800 Subject: [PATCH] feat: improve audit exports and admin analytics --- .../apps/admin-web/src/App.vue | 4 +- .../src/components/AgentBatchTestPanel.vue | 7 +- .../src/components/AgentManagementView.vue | 12 +- .../src/components/AiLogDetailDrawer.vue | 3 +- .../components/AttentionManagementView.vue | 80 ++++++- .../components/AttentionRuleConfigPanel.vue | 3 +- .../src/components/ChatDetailDrawer.vue | 88 +++++++- .../ContentGenerationConfigView.vue | 12 +- .../src/components/DashboardView.vue | 3 +- .../src/components/FeedbackManagementView.vue | 5 +- .../components/KnowledgeManagementView.vue | 3 +- .../src/components/RecordAuditView.vue | 50 ++--- .../src/components/RetrievalLogView.vue | 11 +- .../src/components/SsoIntegrationView.vue | 14 +- .../components/UserBehaviorAnalysisView.vue | 81 +++++-- .../src/components/UserDetailDrawer.vue | 27 +-- .../src/components/UserManagementView.vue | 19 +- .../apps/admin-web/src/services/api.ts | 13 +- .../apps/admin-web/src/types/api.ts | 2 + .../apps/admin-web/src/utils/dateTime.ts | 81 +++++++ .../versions/0040_human_attention_filters.py | 25 +++ .../versions/0041_utc_time_contract.py | 87 ++++++++ .../versions/0042_record_export_permission.py | 48 +++++ .../backend/app/api/admin_agent_records.py | 44 +++- .../apps/backend/app/api/admin_dashboard.py | 9 +- .../apps/backend/app/api/admin_records.py | 85 +++++++- .../backend/app/api/admin_user_behavior.py | 37 ++++ .../apps/backend/app/api/admin_users.py | 14 +- .../apps/backend/app/api/feedback.py | 11 +- .../apps/backend/app/core/database.py | 12 +- .../apps/backend/app/core/dependencies.py | 4 +- .../apps/backend/app/core/responses.py | 6 +- .../apps/backend/app/core/time_utils.py | 64 ++++++ .../apps/backend/app/models/knowledge.py | 3 + .../app/services/admin_permission_service.py | 4 +- .../backend/app/services/admin_service.py | 8 +- .../app/services/chat_export_service.py | 131 +++++++++++ .../app/services/entitlement_service.py | 5 +- .../app/services/maintenance_service.py | 10 +- .../app/services/periodic_report_service.py | 2 +- .../question_insight_export_service.py | 7 +- .../app/services/question_insight_service.py | 13 +- .../services/user_behavior_export_service.py | 203 ++++++++++++++++++ .../app/services/user_behavior_service.py | 141 ++++++++---- .../backend/tests/test_admin_pagination.py | 109 +++++++++- .../backend/tests/test_admin_permissions.py | 58 ++++- .../apps/backend/tests/test_chat_export.py | 63 ++++++ .../backend/tests/test_message_feedback.py | 4 +- .../apps/backend/tests/test_time_contract.py | 27 +++ .../tests/test_user_behavior_analytics.py | 58 ++++- .../src/components/ChatMessage.vue | 6 +- .../src/components/PersonalCenterDialog.vue | 10 +- .../src/components/SessionDrawer.vue | 15 +- .../src/components/SessionQuota.vue | 6 +- .../apps/user-client/src/utils/dateTime.ts | 76 +++++++ 55 files changed, 1654 insertions(+), 269 deletions(-) create mode 100644 ai_knowledge_base_v2/apps/admin-web/src/utils/dateTime.ts create mode 100644 ai_knowledge_base_v2/apps/backend/alembic/versions/0040_human_attention_filters.py create mode 100644 ai_knowledge_base_v2/apps/backend/alembic/versions/0041_utc_time_contract.py create mode 100644 ai_knowledge_base_v2/apps/backend/alembic/versions/0042_record_export_permission.py create mode 100644 ai_knowledge_base_v2/apps/backend/app/core/time_utils.py create mode 100644 ai_knowledge_base_v2/apps/backend/app/services/chat_export_service.py create mode 100644 ai_knowledge_base_v2/apps/backend/app/services/user_behavior_export_service.py create mode 100644 ai_knowledge_base_v2/apps/backend/tests/test_chat_export.py create mode 100644 ai_knowledge_base_v2/apps/backend/tests/test_time_contract.py create mode 100644 ai_knowledge_base_v2/apps/user-client/src/utils/dateTime.ts diff --git a/ai_knowledge_base_v2/apps/admin-web/src/App.vue b/ai_knowledge_base_v2/apps/admin-web/src/App.vue index 8ba1598..fadb300 100644 --- a/ai_knowledge_base_v2/apps/admin-web/src/App.vue +++ b/ai_knowledge_base_v2/apps/admin-web/src/App.vue @@ -1137,8 +1137,8 @@ async function clearFeishuCache() { :can-detail="can('feedback.detail')" :can-export="can('feedback.export')" /> - - + + diff --git a/ai_knowledge_base_v2/apps/admin-web/src/components/AgentBatchTestPanel.vue b/ai_knowledge_base_v2/apps/admin-web/src/components/AgentBatchTestPanel.vue index 961c4d4..0437d19 100644 --- a/ai_knowledge_base_v2/apps/admin-web/src/components/AgentBatchTestPanel.vue +++ b/ai_knowledge_base_v2/apps/admin-web/src/components/AgentBatchTestPanel.vue @@ -5,6 +5,7 @@ import { computed, onBeforeUnmount, onMounted, reactive, ref } from "vue"; import { api } from "../services/api"; import type { AgentBatchItem, AgentBatchJob, AgentBatchJobDetail } from "../types/api"; import AdminPagination from "./AdminPagination.vue"; +import { formatDateTime } from "../utils/dateTime"; const props = defineProps<{ config: Record; @@ -165,10 +166,6 @@ function executionLabel(job: AgentBatchJob) { return job.executionMode === "concurrent" ? `并发 · ${job.concurrencyLimit} 路` : "逐条串行"; } -function formatDate(value?: string | null) { - return value ? new Date(value).toLocaleString("zh-CN", { hour12: false }) : "—"; -} - function itemDuration(item: AgentBatchItem) { return item.durationMs == null ? "—" : `${(item.durationMs / 1000).toFixed(1)} 秒`; } @@ -213,7 +210,7 @@ function errorMessage(error: unknown, fallback: string) {

测试记录

任务在后台执行,离开页面不会中断。

刷新
- + diff --git a/ai_knowledge_base_v2/apps/admin-web/src/components/AgentManagementView.vue b/ai_knowledge_base_v2/apps/admin-web/src/components/AgentManagementView.vue index a8284ac..9848d6f 100644 --- a/ai_knowledge_base_v2/apps/admin-web/src/components/AgentManagementView.vue +++ b/ai_knowledge_base_v2/apps/admin-web/src/components/AgentManagementView.vue @@ -9,6 +9,7 @@ import AgentBatchTestPanel from "./AgentBatchTestPanel.vue"; import AgentResponseDepthControl from "./AgentResponseDepthControl.vue"; import AdminPagination from "./AdminPagination.vue"; import StreamingMarkdownMessage from "./StreamingMarkdownMessage.vue"; +import { formatDateTime } from "../utils/dateTime"; const props = withDefaults(defineProps<{ previewKnowledgeId?: number | null; canBatch?: boolean }>(), { canBatch: false }); const emit = defineEmits<{ consumedPreview: [] }>(); @@ -479,11 +480,6 @@ function changeTypeTag(value?: string) { return "info"; } -function formatDate(value?: string | null) { - if (!value) return "尚未保存"; - return new Date(value).toLocaleString("zh-CN", { hour12: false }); -} - function errorMessage(error: unknown, fallback: string) { return error instanceof Error ? error.message : fallback; } @@ -497,7 +493,7 @@ function errorMessage(error: unknown, fallback: string) {
当前版本 {{ prompt?.id ? `#${prompt.id}` : "系统默认" }} - {{ prompt?.updatedByName || "系统默认" }} · {{ formatDate(prompt?.updatedAt) }} + {{ prompt?.updatedByName || "系统默认" }} · {{ formatDateTime(prompt?.updatedAt, "尚未保存") }}
@@ -697,7 +693,7 @@ function errorMessage(error: unknown, fallback: string) { 版本 #{{ item.id }} 当前 {{ changeTypeLabel(item.changeType) }} - {{ item.updatedByName }} · {{ formatDate(item.updatedAt) }} + {{ item.updatedByName }} · {{ formatDateTime(item.updatedAt, "尚未保存") }}

{{ item.preview || "空内容" }}

@@ -759,7 +755,7 @@ function errorMessage(error: unknown, fallback: string) {
diff --git a/ai_knowledge_base_v2/apps/admin-web/src/components/AiLogDetailDrawer.vue b/ai_knowledge_base_v2/apps/admin-web/src/components/AiLogDetailDrawer.vue index 9b0b50b..4e6c19a 100644 --- a/ai_knowledge_base_v2/apps/admin-web/src/components/AiLogDetailDrawer.vue +++ b/ai_knowledge_base_v2/apps/admin-web/src/components/AiLogDetailDrawer.vue @@ -3,6 +3,7 @@ import { ElMessage } from "element-plus"; import { ref, watch } from "vue"; import { api } from "../services/api"; import type { AiLogRecord } from "../types/api"; +import { formatDateTime } from "../utils/dateTime"; const props = defineProps<{ modelValue: boolean; logId: number | null }>(); const emit = defineEmits<{ "update:modelValue": [value: boolean] }>(); @@ -77,7 +78,7 @@ function formatMoney(value?: number | null, currency = "CNY") { >知识命中:{{ detail.knowledgeHit ? "是" : "否" }}路由原因:{{ detail.routeReason || "-" }}耗时:{{ detail.costMs || "-" }}ms时间:{{ detail.createdAt }} + >时间:{{ formatDateTime(detail.createdAt) }}

{{ detail.errorMessage }} diff --git a/ai_knowledge_base_v2/apps/admin-web/src/components/AttentionManagementView.vue b/ai_knowledge_base_v2/apps/admin-web/src/components/AttentionManagementView.vue index f07ba28..faf2224 100644 --- a/ai_knowledge_base_v2/apps/admin-web/src/components/AttentionManagementView.vue +++ b/ai_knowledge_base_v2/apps/admin-web/src/components/AttentionManagementView.vue @@ -6,19 +6,28 @@ import type { AttentionItem } from "../types/api"; import AdminPagination from "./AdminPagination.vue"; import AttentionRuleConfigPanel from "./AttentionRuleConfigPanel.vue"; import ChatDetailDrawer from "./ChatDetailDrawer.vue"; +import { formatDateTime, toUtcIsoFromBusinessDate } from "../utils/dateTime"; defineProps<{ canConfig: boolean; canPreview: boolean }>(); const loading = ref(false); const records = ref([]); const pager = reactive({ page: 1, pageSize: 20, total: 0 }); +const filters = reactive({ priority: "", dateFrom: "", dateTo: "" }); const chatDetailOpen = ref(false); const sessionId = ref(null); +const focusMessageId = ref(null); onMounted(() => loadRecords()); async function loadRecords(page = 1, pageSize = pager.pageSize) { loading.value = true; try { - const result = await api.attentionList({ page, pageSize }); + const result = await api.attentionList({ + priority: filters.priority || undefined, + dateFrom: toUtcIsoFromBusinessDate(filters.dateFrom, "start"), + dateTo: toUtcIsoFromBusinessDate(filters.dateTo, "end"), + page, + pageSize, + }); records.value = result.items; Object.assign(pager, { page: result.page, @@ -29,6 +38,17 @@ async function loadRecords(page = 1, pageSize = pager.pageSize) { loading.value = false; } } +function applyFilters() { + if (filters.dateFrom && filters.dateTo && filters.dateFrom > filters.dateTo) { + ElMessage.warning("开始日期不能晚于结束日期"); + return; + } + void loadRecords(1, pager.pageSize); +} +function resetFilters() { + Object.assign(filters, { priority: "", dateFrom: "", dateTo: "" }); + void loadRecords(1, pager.pageSize); +} async function updateStatus(row: AttentionItem, status: string) { await api.updateAttention(row.id, status); ElMessage.success("人工关注状态已更新"); @@ -52,8 +72,9 @@ async function deleteRecord(row: AttentionItem) { ElMessage.success("人工关注记录已删除"); await loadRecords(pager.page, pager.pageSize); } -function openChat(id: number) { - sessionId.value = id; +function openChat(row: AttentionItem) { + sessionId.value = row.sessionId; + focusMessageId.value = row.messageId; chatDetailOpen.value = true; } function priorityLabel(priority: string) { @@ -73,6 +94,22 @@ function priorityLabel(priority: string) {

+
+ + + + + +
+ + + +
+
+ 查询 + 重置 +
+