فهرست منبع

AI聊天,实现自定义报表分析

QuYueTing 1 ماه پیش
والد
کامیت
8ee9317f71

+ 15 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/analysis/api.ts

@@ -151,3 +151,18 @@ export interface ChatContent {
 export async function getLatestQuestionList(): Promise<LatestQuestionResponse> {
   return await post('/aiQuestion/getLatestQuestionList');
 }
+
+export interface CustomReport {
+  id: string;
+  storeName: string;
+  relateFormId: string;
+}
+
+export interface CustomReportResponse {
+  code: string;
+  data: CustomReport[];
+}
+
+export async function getCustomReports(): Promise<CustomReportResponse> {
+  return await post('/aiQuestion/getCusReportForm');
+}

+ 101 - 16
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/analysis/components/AIChat.vue

@@ -1,6 +1,9 @@
 <template>
   <div class="border-gray-200 border rounded p-3 h-full flex flex-col" style="min-height: 750px; max-height: 750px;">
-    <div class="text-sm font-medium mb-3">DeepSeek大模型CRM数据分析</div>
+    <div class="text-sm font-medium mb-3 flex justify-between">
+      <span style="color: #075985">DeepSeek大模型CRM数据分析</span>
+      <span class="text-gray-500">开会做PPT利器!</span>
+    </div>
     
     <!-- Chat messages container with fixed height and scrolling -->
     <div class="mb-3 border-gray-200 border rounded overflow-y-auto flex-grow" style="min-height: 0;">
@@ -11,6 +14,9 @@
               <el-icon><ChatLineRound /></el-icon>
             </el-avatar>
             <div class="border-gray-200 border rounded p-2 text-sm max-w-[80%] bg-gray-50 relative">
+              <div class="text-xs text-gray-500 mb-1" v-if="message.dataSource">
+                [{{ message.dataSource }}{{ message.dataName ? '-' + message.dataName : '' }}]
+              </div>
               <div class="markdown-body" v-html="renderMarkdown(message.content)"></div>
               <div v-if="message.loading" class="loading-dots">
                 <span></span>
@@ -18,7 +24,7 @@
                 <span></span>
               </div>
               <el-button 
-                v-if="!message.loading && message.role === 'assistant' && index > 0"
+                v-if="!message.loading && message.role === 'assistant' && index > 0 && '抱歉,请求处理失败,请稍后再试' != message.content && '无数据' != message.content"
                 @click="exportToWord(message.content)"
                 size="small" 
                 type="text" 
@@ -28,7 +34,11 @@
             </div>
           </div>
           <div v-if="message.role === 'user'" class="flex items-start gap-2 justify-end">
-            <div class="border-gray-200 border rounded p-2 text-sm max-w-[80%] bg-blue-50 markdown-body" v-html="renderMarkdown(message.content)">
+            <div class="border-gray-200 border rounded p-2 text-sm max-w-[80%] bg-blue-50">
+              <div class="text-xs text-gray-500 mb-1" v-if="message.dataSource">
+                [{{ message.dataSource }}{{ message.dataName ? '-' + message.dataName : '' }}]
+              </div>
+              <div class="markdown-body" v-html="renderMarkdown(message.content)"></div>
             </div>
             <el-avatar :size="24" class="bg-gray-200 flex items-center justify-center">
               <el-icon><User /></el-icon>
@@ -61,6 +71,23 @@
           <el-option label="产品" value="product" />
         </el-select>
       </div>
+
+      <div v-if="dataSource === 'custom'">
+        <el-select 
+          v-model="selectedReportId" 
+          size="small" 
+          style="width: 120px"
+          placeholder="选择报表"
+          @focus="loadCustomReports"
+        >
+          <el-option 
+            v-for="report in customReports"
+            :key="report.relateFormId"
+            :label="report.storeName"
+            :value="report.relateFormId"
+          />
+        </el-select>
+      </div>
       
       <div v-if="dataSource === 'upload'">
         <el-upload
@@ -96,6 +123,7 @@
         placeholder="请进行数据分析,给一个总结报告,不超过300字"
         class="flex-1"
         resize="none"
+        @keyup.enter="inputMessage.trim() && !loading ? sendMessage() : null"
       />
       <el-button
         type="primary"
@@ -119,11 +147,13 @@ import {
   askAIQuestion,
   uploadFileApi, 
   getLatestQuestionList,
+  getCustomReports,
   type AIQuestionParams,
   type UploadFileResponse,
   type ChatContent,
   type LatestQuestionResponse,
-  type AIQuestionResponse
+  type AIQuestionResponse,
+  type CustomReport
 } from '../api';
 import { ElMessage } from 'element-plus/es'
 import * as internal from 'stream';
@@ -271,11 +301,15 @@ interface ChatMessage {
   role: 'user' | 'assistant';
   content: string;
   loading?: boolean;
+  dataSource?: string;
+  dataName?: string;
 }
 
 // Data source selection
 const dataSource = ref<DataSourceType>('system');
 const systemTable = ref<SystemTableType>('clue');
+const customReports = ref<CustomReport[]>([]);
+const selectedReportId = ref<string>('');
 const getFirstDayOfMonth = () => {
   const date = new Date();
   return new Date(date.getFullYear(), date.getMonth(), 1);
@@ -313,6 +347,20 @@ const loading = ref(false);
 const messages = reactive<ChatMessage[]>([]);
 const questionId = ref<number | null>(null);
 
+const loadCustomReports = async () => {
+  if (customReports.value.length === 0) {
+    try {
+      const result = await getCustomReports();
+      if (result.code === 'ok') {
+        customReports.value = result.data;
+      }
+    } catch (error) {
+      console.error('Failed to load custom reports:', error);
+      ElMessage.error('加载自定义报表失败');
+    }
+  }
+};
+
 const isSameDay = (dateString: string, compareDate: Date) => {
   // Parse yyyy-MM-dd hh:mm:ss format
   const [datePart] = dateString.split(' ');
@@ -352,26 +400,63 @@ onMounted(async () => {
 
 const sendMessage = async () => {
   if (!inputMessage.value.trim() || loading.value) return;
-
+  type DataSourceType = 'system' | 'custom' | 'upload' | 'free';
+  const dataSourceNameMap: Record<DataSourceType, string> = {
+    'system': '系统表',
+    'custom': '自定义报表',
+    'upload': '本地上传', 
+    'free': '自由交流'
+  };
+  const dataSourceMap: Record<DataSourceType, number> = {
+    'system': 1,
+    'custom': 2,
+    'upload': 3, 
+    'free': 4
+  };
+  const sourceName = dataSourceNameMap[dataSource.value]
+  const dataName = dataSource.value === 'system' ? 
+              systemTable.value === 'clue' ? '线索' :
+              systemTable.value === 'business_opportunity' ? '商机' :
+              systemTable.value === 'custom' ? '客户' :
+              systemTable.value === 'contacts' ? '联系人' :
+              systemTable.value === 'contract' ? '合同' :
+              systemTable.value === 'sales_order' ? '销售订单' : '产品' :
+            dataSource.value === 'custom' ? 
+              customReports.value.find(r => r.relateFormId === selectedReportId.value)?.storeName || '' :
+            dataSource.value === 'upload' ? 
+              uploadFile.value?.name || '' : ''
+  const finalContent = '['+sourceName+(dataName?('-'+dataName):'')+']'+'<br>' + inputMessage.value;
+  if (!dataName) {
+    if (dataSource.value == 'custom') {
+        //提示:请选择报表
+        ElMessage.error({
+          message: "请选择报表",
+          type: "error",
+          duration: 2000,
+        })
+        return
+    } else if (dataSource.value == 'upload') {
+        ElMessage.error({
+            message: "请上传数据文件(仅支持excel格式)",
+            type: "error",
+            duration: 2000,
+          })
+          return
+    }
+  }
   loading.value = true;
-  const userMessage: ChatMessage = { role: 'user', content: inputMessage.value };
+  const userMessage: ChatMessage = { role: 'user', content: finalContent };
   messages.push(userMessage);
   const thinkingIndex = messages.length;
   messages.push({ role: 'assistant', content: 'AI正在思考', loading: true });
   
   try {
-    type DataSourceType = 'system' | 'custom' | 'upload' | 'free';
-    const dataSourceMap: Record<DataSourceType, number> = {
-      'system': 1,
-      'custom': 2,
-      'upload': 3, 
-      'free': 4
-    };
-
+    
     const params: AIQuestionParams & { questionId?: number } = {
       questionDataSource: dataSourceMap[dataSource.value],
-      sourceContent: dataSource.value === 'system' ? systemTable.value : '',
-      content: inputMessage.value,
+      sourceContent: dataSource.value === 'system' ? systemTable.value : 
+                   dataSource.value === 'custom' ? selectedReportId.value : '',
+      content: finalContent,
       startDate: dateRange.value[0]?.toISOString().split('T')[0],
       endDate: dateRange.value[1]?.toISOString().split('T')[0],
       url: dataSource.value === 'upload' ? uploadedFilePath.value : ''