Browse Source

update:page-crm-image

0235701 2 days ago
parent
commit
9f2b6539f5

+ 19 - 0
ai-assisant/package-lock.json

@@ -21,6 +21,7 @@
         "@fortawesome/fontawesome-svg-core": "^6.7.2",
         "@fortawesome/free-solid-svg-icons": "^6.7.2",
         "animate.css": "^4.1.1",
+        "chart.js": "^4.5.0",
         "modern-css-reset": "^1.4.0",
         "rxjs": "~7.8.0",
         "tslib": "^2.3.0",
@@ -1748,6 +1749,12 @@
         "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
+    "node_modules/@kurkle/color": {
+      "version": "0.3.4",
+      "resolved": "https://registry.npmmirror.com/@kurkle/color/-/color-0.3.4.tgz",
+      "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+      "license": "MIT"
+    },
     "node_modules/@listr2/prompt-adapter-inquirer": {
       "version": "2.0.22",
       "resolved": "https://registry.npmmirror.com/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.22.tgz",
@@ -3912,6 +3919,18 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/chart.js": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmmirror.com/chart.js/-/chart.js-4.5.0.tgz",
+      "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@kurkle/color": "^0.3.0"
+      },
+      "engines": {
+        "pnpm": ">=8"
+      }
+    },
     "node_modules/chokidar": {
       "version": "4.0.3",
       "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz",

+ 1 - 0
ai-assisant/package.json

@@ -33,6 +33,7 @@
     "@fortawesome/fontawesome-svg-core": "^6.7.2",
     "@fortawesome/free-solid-svg-icons": "^6.7.2",
     "animate.css": "^4.1.1",
+    "chart.js": "^4.5.0",
     "modern-css-reset": "^1.4.0",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",

+ 175 - 0
ai-assisant/src/modules/crm/mobile/page-crm-image/cloud-service.ts

@@ -0,0 +1,175 @@
+// src/app/services/cloud-service.ts
+
+import { Injectable } from '@angular/core';
+
+// 定义服务器URL
+let serverURL = `https://dev.fmode.cn/parse`;
+if (location.protocol == "http:") {
+    serverURL = `http://dev.fmode.cn:1337/parse`;
+}
+
+// 基础云对象类
+export class CloudObject {
+    className: string;
+    id: string | undefined = undefined;
+    createdAt: any;
+    updatedAt: any;
+    data: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    toPointer() {
+        return { "__type": "Pointer", "className": this.className, "objectId": this.id };
+    }
+
+    set(json: Record<string, any>) {
+        Object.keys(json).forEach(key => {
+            if (["objectId", "id", "createdAt", "updatedAt"].indexOf(key) > -1) {
+                return;
+            }
+            this.data[key] = json[key];
+        });
+    }
+
+    get(key: string) {
+        return this.data[key] || null;
+    }
+
+    async save() {
+        let method = "POST";
+        let url = serverURL + `/classes/${this.className}`;
+
+        // 更新
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        const body = JSON.stringify(this.data);
+        const response = await fetch(url, {
+            headers: {
+                "content-type": "application/json;charset=UTF-8",
+                "x-parse-application-id": "dev"
+            },
+            body: body,
+            method: method,
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+        }
+        if (result?.objectId) {
+            this.id = result?.objectId;
+        }
+        return this;
+    }
+
+    async destroy() {
+        if (!this.id) return;
+        const response = await fetch(serverURL + `/classes/${this.className}/${this.id}`, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "DELETE",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result) {
+            this.id = undefined;
+        }
+        return true;
+    }
+}
+
+// 查询类 - 实现了所有必要的查询方法
+export class CloudQuery {
+    className: string;
+    queryParams: Record<string, any> = { where: {} };
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    // 查询条件方法
+    equalTo(key: string, value: any) {
+        this.queryParams['where'][key] = value;
+        return this;
+    }
+    
+    // 设置包含关联对象
+    include(...fields: string[]) {
+        this.queryParams["include"] = fields.join(',');
+        return this;
+    }
+    
+    // 设置查询结果数量限制
+    limit(limit: number) {
+        this.queryParams["limit"] = limit;
+        return this;
+    }
+    
+    // 设置查询结果排序
+    ascending(field: string) {
+        this.queryParams["order"] = field;
+        return this;
+    }
+    
+    // 获取查询的第一个结果
+    async first() {
+        this.limit(1);
+        const results = await this.find();
+        return results.length > 0 ? results[0] : null;
+    }
+    
+    // 执行查询并返回所有结果
+    async find() {
+        // 构建查询URL参数
+        const queryString = Object.keys(this.queryParams)
+            .map(key => `${key}=${encodeURIComponent(JSON.stringify(this.queryParams[key]))}`)
+            .join('&');
+            
+        const response = await fetch(serverURL + `/classes/${this.className}?${queryString}`, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+        
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return [];
+        }
+        
+        // 将结果转换为CloudObject实例
+        return result?.results?.map((item: any) => {
+            const obj = new CloudObject(this.className);
+            obj.id = item.objectId;
+            obj.createdAt = item.createdAt;
+            obj.updatedAt = item.updatedAt;
+            obj.data = { ...item };
+            delete obj.data['objectId'];
+            delete obj.data['createdAt'];
+            delete obj.data['updatedAt'];
+            return obj;
+        }) || [];
+    }
+}
+
+// 云服务类
+@Injectable({
+    providedIn: 'root'
+})
+export class CloudService {
+    constructor() { }
+}

+ 176 - 0
ai-assisant/src/modules/crm/mobile/page-crm-image/crm-service.ts

@@ -0,0 +1,176 @@
+// src/app/services/crm-service.ts
+
+import { Injectable } from '@angular/core';
+import { CloudObject, CloudQuery } from './cloud-service';
+
+// 客户数据模型
+export class Client extends CloudObject {
+    constructor() {
+        super('Client'); // 对应后端的Client类
+    }
+    
+    // 设置客户属性的便捷方法
+    setClientData(data: {
+        clientId: string;
+        clientName: string;
+        clientType: string;
+        chatData: string;
+        date: string;
+    }) {
+        this.set({
+            clientId: data.clientId,
+            clientName: data.clientName,
+            clientType: data.clientType,
+            chatData: data.chatData,
+            date: data.date
+        });
+    }
+    
+    // 获取客户属性的便捷方法
+    getClientData() {
+        return {
+            clientId: this.get('clientId'),
+            clientName: this.get('clientName'),
+            clientType: this.get('clientType'),
+            chatData: this.get('chatData'),
+            date: this.get('date')
+        };
+    }
+}
+
+// 聊天消息数据模型 - 添加了必要的属性定义
+export class ChatMessage extends CloudObject {
+    constructor() {
+        super('ChatMessage'); // 对应后端的ChatMessage类
+    }
+    
+    // 设置消息属性
+    setMessageData(data: {
+        clientId: string;
+        time: string;
+        sender: string;
+        content: string;
+    }) {
+        this.set({
+            clientId: data.clientId,
+            time: data.time,
+            sender: data.sender,
+            content: data.content
+        });
+    }
+    
+    // 添加获取属性的方法
+    getTime() { return this.get('time'); }
+    getSender() { return this.get('sender'); }
+    getContent() { return this.get('content'); }
+    getClientId() { return this.get('clientId'); }
+}
+
+// CRM服务类 - 实现了所有必要的方法
+@Injectable({
+    providedIn: 'root'
+})
+export class CRMServices {
+    constructor() { }
+    
+    // 保存客户数据到后端
+    async saveClient(clientData: {
+        clientId: string;
+        clientName: string;
+        clientType: string;
+        chatData: string;
+        date: string;
+    }): Promise<Client> {
+        // 先检查是否已有该客户ID的记录
+        const query = new CloudQuery('Client');
+        query.equalTo('clientId', clientData.clientId);
+        const existingClient = await query.first();
+        
+        let client: Client;
+        
+        if (existingClient) {
+            // 如果存在,更新现有记录
+            client = new Client();
+            client.id = existingClient.id;
+            client.setClientData(clientData);
+        } else {
+            // 如果不存在,创建新记录
+            client = new Client();
+            client.setClientData(clientData);
+        }
+        
+        // 保存到后端
+        await client.save();
+        return client;
+    }
+    
+    // 获取所有客户
+    async getAllClients(): Promise<Client[]> {
+        const query = new CloudQuery('Client');
+        query.limit(1000); // 设置最大查询数量
+        const results = await query.find();
+        return results.map((result: { id: string | undefined; get: (arg0: string) => any; }) => {
+            const client = new Client();
+            client.id = result.id;
+            client.setClientData({
+                clientId: result.get('clientId'),
+                clientName: result.get('clientName'),
+                clientType: result.get('clientType'),
+                chatData: result.get('chatData'),
+                date: result.get('date')
+            });
+            return client;
+        });
+    }
+    
+    // 保存聊天消息
+    async saveChatMessages(clientId: string, messages: ChatMessage[]): Promise<void> {
+        for (const msg of messages) {
+            // 确保消息与客户关联
+            msg.set({ clientId });
+            await msg.save();
+        }
+    }
+    
+    // 获取客户的聊天消息
+    async getChatMessages(clientId: string): Promise<ChatMessage[]> {
+        const query = new CloudQuery('ChatMessage');
+        query.equalTo('clientId', clientId);
+        query.ascending('time'); // 按时间升序排列
+        const results = await query.find();
+        
+        return results.map((result: { id: string | undefined; get: (arg0: string) => any; }) => {
+            const msg = new ChatMessage();
+            msg.id = result.id;
+            msg.setMessageData({
+                clientId: result.get('clientId'),
+                time: result.get('time'),
+                sender: result.get('sender'),
+                content: result.get('content')
+            });
+            return msg;
+        });
+    }
+    
+    // 删除客户及其相关聊天记录
+    async deleteClient(clientId: string): Promise<void> {
+        // 先查找客户
+        const query = new CloudQuery('Client');
+        query.equalTo('clientId', clientId);
+        const clients = await query.find();
+        
+        if (clients.length > 0) {
+            // 删除客户
+            await clients[0].destroy();
+            
+            // 删除相关的聊天记录
+            const msgQuery = new CloudQuery('ChatMessage');
+            msgQuery.equalTo('clientId', clientId);
+            const messages = await msgQuery.find();
+            
+            for (const msg of messages) {
+                await msg.destroy();
+            }
+        }
+    }
+}

+ 66 - 8
ai-assisant/src/modules/crm/mobile/page-crm-image/page-crm-image.html

@@ -1,3 +1,4 @@
+<!-- page-crm-image.html - 修改后与后端服务集成的代码 -->
 <div class="app-container">
   <!-- 头部导航 -->
   <div class="header">
@@ -19,6 +20,16 @@
       <input type="text" id="client-name" [(ngModel)]="clientName" placeholder="请输入客户名称">
     </div>
     
+    <div class="input-group">
+      <label for="client-type">客户类型</label>
+      <select id="client-type" [(ngModel)]="clientType">
+        <option value="企业客户">企业客户</option>
+        <option value="个人客户">个人客户</option>
+        <option value="潜在客户">潜在客户</option>
+        <option value="VIP客户">VIP客户</option>
+      </select>
+    </div>
+    
     <div class="input-group">
       <label for="chat-data">聊天记录文本</label>
       <textarea id="chat-data" [(ngModel)]="chatData" placeholder="请粘贴或输入聊天记录文本..."></textarea>
@@ -38,6 +49,14 @@
   <div class="client-list-section">
     <div class="section-header">
       <h2 class="section-title">客户分析列表</h2>
+      <div class="section-actions">
+        <button class="btn btn-sm btn-blue" (click)="refreshClients()">
+          <i class="fas fa-refresh"></i> 刷新
+        </button>
+        <button class="btn btn-sm btn-blue" (click)="clearAllClients()">
+          <i class="fas fa-trash"></i> 清空
+        </button>
+      </div>
     </div>
     
     <div class="search-box">
@@ -46,15 +65,24 @@
     </div>
     
     <div class="client-list">
-      <div class="client-item" *ngFor="let client of clients" (click)="onClientClick(client)">
+      <div class="client-item" *ngFor="let client of filteredClients" (click)="loadClient(client)">
         <div class="client-info">
-          <div class="client-avatar">{{ client.name.charAt(0) }}</div>
+          <div class="client-avatar">{{ client.get('clientName')?.charAt(0) || '?' }}</div>
           <div>
-            <div class="client-name">{{ client.name }}</div>
-            <div class="client-meta">{{ client.type }} · ID: {{ client.id }}</div>
+            <div class="client-name">{{ client.get('clientName') || '未命名' }}</div>
+            <div class="client-meta">{{ client.get('clientType') || '未知类型' }} · ID: {{ client.get('clientId') || '未知' }}</div>
           </div>
         </div>
-        <div class="client-date">{{ client.date }}</div>
+        <div class="client-date">{{ client.get('date') || '未知日期' }}</div>
+        <div class="client-actions">
+          <button class="btn btn-icon" (click)="deleteClient(client.get('clientId'), $event)">
+            <i class="fas fa-trash"></i>
+          </button>
+        </div>
+      </div>
+      <div *ngIf="clients.length === 0" class="empty-state">
+        <i class="fas fa-folder-open"></i>
+        <p>暂无客户分析记录</p>
       </div>
     </div>
   </div>
@@ -78,7 +106,9 @@
         </p>
         <div class="recommendation-action">
           <span class="tag">{{ rec.tag }}</span>
-          <button class="action-btn">{{ rec.buttonText }}</button>
+          <button class="action-btn" (click)="handleRecommendationAction(rec)">
+            {{ rec.buttonText }}
+          </button>
         </div>
       </div>
     </div>
@@ -126,6 +156,12 @@
           <span>推荐策略</span>
           <i class="fas fa-chevron-right"></i>
         </div>
+        
+        <div class="modal-actions">
+          <button class="btn btn-blue" (click)="exportReport()">
+            <i class="fas fa-download"></i> 导出报告
+          </button>
+        </div>
       </div>
       <div class="modal-footer">
         <button class="btn btn-confirm" (click)="closeModal()">确定</button>
@@ -150,8 +186,17 @@
           <i class="fas fa-calendar-alt"></i>
           <span>日期时间:{{ chatDetail.date || '未记录' }}</span>
         </div>
-        <div class="text-content">
-          {{ chatDetail.content || '暂无聊天内容' }}
+        <div class="chat-messages">
+          <div class="message" *ngFor="let msg of formattedChatMessages">
+            <div class="message-header">
+              <span class="sender">{{ msg.getSender() }}</span>
+              <span class="time">{{ msg.getTime() }}</span>
+            </div>
+            <div class="message-content">{{ msg.getContent() }}</div>
+          </div>
+          <div *ngIf="formattedChatMessages.length === 0" class="empty-chat">
+            暂无格式化聊天内容
+          </div>
         </div>
       </div>
       <div class="modal-footer">
@@ -173,6 +218,11 @@
         </button>
       </div>
       <div class="modal-body">
+        <!-- 图表容器 -->
+        <div class="chart-container" style="position: relative; height:250px; margin-bottom: 20px;">
+          <canvas id="analysisChart"></canvas>
+        </div>
+        
         <div class="analysis-grid">
           <div class="analysis-item">
             <i class="fas fa-venus-mars"></i>
@@ -205,4 +255,12 @@
       </div>
     </div>
   </div>
+
+  <!-- 加载中弹窗 -->
+  <div class="loading-modal" *ngIf="isLoading">
+    <div class="loading-content">
+      <div class="spinner"></div>
+      <p>{{ loadingMessage || '处理中...' }}</p>
+    </div>
+  </div>
 </div>

+ 267 - 155
ai-assisant/src/modules/crm/mobile/page-crm-image/page-crm-image.scss

@@ -1,3 +1,4 @@
+/* page-crm-image.scss - 添加功能后的完整代码 */
 * {
   margin: 0;
   padding: 0;
@@ -88,7 +89,8 @@ body {
 }
 
 .input-group input,
-.input-group textarea {
+.input-group textarea,
+.input-group select {
   width: 100%;
   padding: 12px 16px;
   border-radius: var(--border-radius);
@@ -97,7 +99,8 @@ body {
 }
 
 .input-group input:focus,
-.input-group textarea:focus {
+.input-group textarea:focus,
+.input-group select:focus {
   outline: none;
   border-color: var(--primary);
   box-shadow: 0 0 0 3px rgba(74, 143, 231, 0.2);
@@ -128,15 +131,30 @@ body {
   gap: 8px;
 }
 
-.btn-primary {
+.btn-blue {
   background: var(--primary);
-  color: var(--text-dark);
+  color: white;
+  
+  &:hover {
+    background: #3a7bd5;
+    box-shadow: 0 4px 8px rgba(74, 143, 231, 0.3);
+  }
 }
 
-.btn-secondary {
-  background: var(--primary);
-  color: var(--text-dark);
-  opacity: 0.8;
+.btn-sm {
+  padding: 8px 12px;
+  font-size: 12px;
+}
+
+.btn-icon {
+  padding: 6px 10px;
+  background: rgba(74, 143, 231, 0.1);
+  color: var(--primary);
+  border-radius: 8px;
+  
+  &:hover {
+    background: rgba(74, 143, 231, 0.2);
+  }
 }
 
 /* 客户列表区 */
@@ -170,6 +188,11 @@ body {
   border-radius: 2px;
 }
 
+.section-actions {
+  display: flex;
+  gap: 8px;
+}
+
 .search-box {
   position: relative;
   margin-bottom: 16px;
@@ -212,6 +235,7 @@ body {
   align-items: center;
   justify-content: space-between;
   cursor: pointer;
+  transition: all 0.2s ease;
 }
 
 .client-item:last-child {
@@ -220,6 +244,7 @@ body {
 
 .client-item:hover {
   background: #f9fbfe;
+  transform: translateX(4px);
 }
 
 .client-info {
@@ -257,6 +282,23 @@ body {
   color: var(--text-light);
 }
 
+.client-actions {
+  display: flex;
+  gap: 8px;
+}
+
+.empty-state {
+  padding: 40px 20px;
+  text-align: center;
+  color: var(--text-light);
+}
+
+.empty-state i {
+  font-size: 40px;
+  margin-bottom: 10px;
+  color: rgba(74, 143, 231, 0.3);
+}
+
 /* 智能推荐区 */
 .recommendation-section {
   margin-top: 24px;
@@ -275,6 +317,12 @@ body {
   padding: 20px;
   position: relative;
   border: 1px solid rgba(0, 0, 0, 0.03);
+  transition: all 0.2s ease;
+}
+
+.recommendation-card:hover {
+  transform: translateY(-4px);
+  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.12);
 }
 
 .card-header {
@@ -333,6 +381,12 @@ body {
   font-weight: 600;
   border: none;
   cursor: pointer;
+  transition: all 0.2s ease;
+}
+
+.action-btn:hover {
+  background: #3a7bd5;
+  box-shadow: 0 4px 8px rgba(74, 143, 231, 0.3);
 }
 
 /* 弹窗样式 */
@@ -348,6 +402,14 @@ body {
   justify-content: center;
   z-index: 1000;
   padding: 20px;
+  opacity: 0;
+  visibility: hidden;
+  transition: opacity 0.3s ease, visibility 0.3s ease;
+}
+
+.report-modal.active {
+  opacity: 1;
+  visibility: visible;
 }
 
 .modal-content {
@@ -358,6 +420,12 @@ body {
   max-height: 90vh;
   overflow-y: auto;
   box-shadow: var(--card-shadow);
+  transform: scale(0.9);
+  transition: transform 0.3s ease;
+}
+
+.report-modal.active .modal-content {
+  transform: scale(1);
 }
 
 .modal-header {
@@ -365,12 +433,12 @@ body {
   border-bottom: 1px solid #eee;
   text-align: center;
   position: relative;
-  
-  h2 {
-    color: var(--primary);
-    font-size: 20px;
-    margin: 0;
-  }
+}
+
+.modal-header h2 {
+  color: var(--primary);
+  font-size: 20px;
+  margin: 0;
 }
 
 .back-btn {
@@ -383,6 +451,36 @@ body {
   font-size: 16px;
   color: var(--primary);
   cursor: pointer;
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  padding: 5px 10px;
+  border-radius: 4px;
+  
+  &:hover {
+    background: rgba(74, 143, 231, 0.1);
+  }
+  
+  i {
+    font-size: 12px;
+  }
+}
+
+.close-btn {
+  position: absolute;
+  right: 20px;
+  top: 50%;
+  transform: translateY(-50%);
+  background: none;
+  border: none;
+  color: var(--text-light);
+  font-size: 16px;
+  cursor: pointer;
+  padding: 5px;
+  
+  &:hover {
+    color: var(--primary);
+  }
 }
 
 .modal-body {
@@ -391,22 +489,22 @@ body {
 
 .form-group {
   margin-bottom: 20px;
-  
-  label {
-    display: block;
-    margin-bottom: 8px;
-    font-weight: 600;
-    color: var(--text-dark);
-    font-size: 14px;
-  }
-  
-  input {
-    width: 100%;
-    padding: 10px 12px;
-    border-radius: var(--border-radius);
-    border: 1px solid #e0e4e8;
-    font-size: 14px;
-  }
+}
+
+.form-group label {
+  display: block;
+  margin-bottom: 8px;
+  font-weight: 600;
+  color: var(--text-dark);
+  font-size: 14px;
+}
+
+.form-group input {
+  width: 100%;
+  padding: 10px 12px;
+  border-radius: var(--border-radius);
+  border: 1px solid #e0e4e8;
+  font-size: 14px;
 }
 
 .info-item {
@@ -415,12 +513,12 @@ body {
   gap: 10px;
   padding: 12px 0;
   border-bottom: 1px solid #f5f5f5;
-  
-  i {
-    color: var(--primary);
-    width: 24px;
-    text-align: center;
-  }
+}
+
+.info-item i {
+  color: var(--primary);
+  width: 24px;
+  text-align: center;
 }
 
 .clickable-item {
@@ -430,20 +528,22 @@ body {
   padding: 12px 0;
   border-bottom: 1px solid #f5f5f5;
   cursor: pointer;
-  
-  i:first-child {
-    color: var(--primary);
-    width: 24px;
-    text-align: center;
-  }
-  
-  i.fa-chevron-right {
-    color: var(--text-light);
-  }
-  
-  &:hover {
-    background-color: #f9f9f9;
-  }
+  transition: all 0.2s ease;
+}
+
+.clickable-item i:first-child {
+  color: var(--primary);
+  width: 24px;
+  text-align: center;
+}
+
+.clickable-item i.fa-chevron-right {
+  color: var(--text-light);
+}
+
+.clickable-item:hover {
+  background-color: #f9f9f9;
+  padding-left: 8px;
 }
 
 .text-content {
@@ -452,6 +552,7 @@ body {
   border-radius: 8px;
   margin-top: 15px;
   line-height: 1.6;
+  white-space: pre-wrap;
 }
 
 .analysis-grid {
@@ -468,10 +569,10 @@ body {
   display: flex;
   align-items: center;
   gap: 8px;
-  
-  i {
-    color: var(--primary);
-  }
+}
+
+.analysis-item i {
+  color: var(--primary);
 }
 
 .modal-footer {
@@ -489,134 +590,145 @@ body {
   font-weight: 600;
   cursor: pointer;
 }
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-  font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
+
+.modal-actions {
+  margin-top: 20px;
+  display: flex;
+  gap: 12px;
 }
 
-:root {
-  --primary: #4a8fe7;
-  --primary-dark: #0a192f;
-  --secondary: #5e72e4;
-  --accent: #ff6b6b;
-  --text-dark: #1a1a1a;
-  --text-medium: #4a4a4a;
-  --text-light: #8a8f9c;
-  --bg-white: #ffffff;
-  --bg-light: #f5f7fa;
-  --card-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
-  --border-radius: 12px;
+/* 聊天记录样式 */
+.chat-messages {
+  margin-top: 15px;
 }
 
-body {
-  background-color: var(--bg-white);
-  color: var(--text-medium);
-  padding: 0;
-  max-width: 480px;
-  margin: 0 auto;
-  min-height: 100vh;
-  position: relative;
-  overflow-x: hidden;
-  background: linear-gradient(to bottom, #f5f9ff 0%, #ffffff 100px);
+.message {
+  margin-bottom: 15px;
 }
 
-.app-container {
-  padding: 0 16px 40px;
-  position: relative;
+.message-header {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 5px;
 }
 
-/* 按钮样式 */
-.btn {
-  padding: 12px 20px;
-  border-radius: var(--border-radius);
-  font-size: 14px;
+.sender {
   font-weight: 600;
-  border: none;
-  cursor: pointer;
-  flex: 1;
+  color: var(--text-dark);
+}
+
+.time {
+  font-size: 12px;
+  color: var(--text-light);
+}
+
+.message-content {
+  padding: 10px 15px;
+  background: var(--bg-light);
+  border-radius: 8px;
+  line-height: 1.6;
+}
+
+.empty-chat {
+  padding: 20px;
+  text-align: center;
+  color: var(--text-light);
+  background: var(--bg-light);
+  border-radius: 8px;
+}
+
+/* 加载中样式 */
+.loading-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.6);
   display: flex;
   align-items: center;
   justify-content: center;
-  gap: 8px;
-  transition: all 0.2s ease;
+  z-index: 2000;
 }
 
-.btn-blue {
-  background: var(--primary);
-  color: white;
-  
-  &:hover {
-    background: #3a7bd5;
-    box-shadow: 0 4px 8px rgba(74, 143, 231, 0.3);
+.loading-content {
+  background: white;
+  padding: 30px 40px;
+  border-radius: var(--border-radius);
+  text-align: center;
+  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+}
+
+.spinner {
+  width: 40px;
+  height: 40px;
+  border: 4px solid rgba(74, 143, 231, 0.3);
+  border-radius: 50%;
+  border-top-color: var(--primary);
+  animation: spin 1s linear infinite;
+  margin: 0 auto 15px;
+}
+
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
   }
 }
 
-.action-btn {
-  padding: 8px 16px;
-  border-radius: 30px;
-  background: var(--primary);
-  color: white;
-  font-size: 14px;
-  font-weight: 600;
-  border: none;
-  cursor: pointer;
+.loading-content p {
+  font-weight: 500;
+  color: var(--text-dark);
 }
+// src/app/pages/page-crm-image/page-crm-image.scss
 
-.btn-confirm {
-  @extend .btn-blue;
-  padding: 12px 24px;
+// 添加或修改以下样式
+
+.client-avatar {
+  width: 40px;
+  height: 40px;
+  background-color: #4a8fe7;
+  color: white;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-weight: bold;
+  margin-right: 15px;
 }
 
-/* 弹窗导航按钮 */
-.back-btn {
-  background: none;
-  border: none;
-  color: var(--primary);
-  font-size: 14px;
-  cursor: pointer;
+.loading-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
   display: flex;
   align-items: center;
-  gap: 5px;
-  padding: 5px 10px;
-  border-radius: 4px;
-  
-  &:hover {
-    background: rgba(74, 143, 231, 0.1);
-  }
-  
-  i {
-    font-size: 12px;
-  }
+  justify-content: center;
+  z-index: 1000;
 }
 
-.close-btn {
-  background: none;
-  border: none;
-  color: var(--text-light);
-  font-size: 16px;
-  cursor: pointer;
-  padding: 5px;
-  
-  &:hover {
-    color: var(--primary);
-  }
+.loading-content {
+  background-color: white;
+  padding: 20px;
+  border-radius: 8px;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+  text-align: center;
 }
 
-/* 弹窗头部布局 */
-.modal-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 15px 20px;
-  position: relative;
-  border-bottom: 1px solid #eee;
+.spinner {
+  border: 4px solid rgba(0, 0, 0, 0.1);
+  border-left-color: #4a8fe7;
+  border-radius: 50%;
+  width: 30px;
+  height: 30px;
+  animation: spin 1s linear infinite;
+  margin: 0 auto 10px;
+}
 
-  h2 {
-    margin: 0 auto;
-    padding: 0 30px;
-    font-size: 18px;
-    color: var(--text-dark);
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
   }
 }

+ 347 - 45
ai-assisant/src/modules/crm/mobile/page-crm-image/page-crm-image.ts

@@ -1,6 +1,31 @@
-import { Component } from '@angular/core';
+// src/app/pages/page-crm-image/page-crm-image.ts
+
+import { Component, AfterViewInit, ElementRef, ViewChild, OnInit } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
+import Chart from 'chart.js/auto';
+import { CRMServices, Client, ChatMessage } from './crm-service'; // 导入新的服务和模型
+
+// 定义推荐项类型
+interface Recommendation {
+  icon: string;
+  title: string;
+  content: string;
+  tag: string;
+  buttonText: string;
+  iconColor: string;
+  iconTextColor: string;
+}
+
+// 定义分析结果类型
+interface AnalysisResult {
+  gender?: string;
+  age?: string;
+  job?: string;
+  psychology?: string;
+  consumption?: string;
+  needs?: string;
+}
 
 @Component({
   selector: 'app-page-crm-image',
@@ -9,25 +34,25 @@ import { FormsModule } from '@angular/forms';
   templateUrl: './page-crm-image.html',
   styleUrls: ['./page-crm-image.scss']
 })
-export class PageCrmImage {
+export class PageCrmImage implements AfterViewInit, OnInit {
   // 表单数据
   clientId = '';
   clientName = '';
   chatData = '';
   searchTerm = '';
   reportName = '';
+  clientType = '企业客户';
   
   // 弹窗控制
   showBaseReportModal = false;
   showChatDetailModal = false;
   showAnalysisResultModal = false;
+  isLoading = false;
+  loadingMessage = '';
   
   // 当前日期
   currentDate = new Date();
   
-  // 客户数据
-  clientType = '企业客户';
-  
   // 聊天记录详情
   chatDetail = {
     date: '2023-07-15 14:30',
@@ -35,7 +60,7 @@ export class PageCrmImage {
   };
   
   // 分析结果
-  analysisResult = {
+  analysisResult: AnalysisResult = {
     gender: '男',
     age: '35-40岁',
     job: 'IT经理',
@@ -44,16 +69,35 @@ export class PageCrmImage {
     needs: '需要高效解决方案'
   };
   
+  // 图表数据
+  chartData = {
+    labels: ['理性决策', '价格敏感', '品牌忠诚', '冒险尝试', '社交导向'],
+    datasets: [{
+      label: '客户心理特征分析',
+      data: [75, 40, 30, 20, 55],
+      backgroundColor: [
+        'rgba(74, 143, 231, 0.7)',
+        'rgba(94, 114, 228, 0.7)',
+        'rgba(23, 162, 184, 0.7)',
+        'rgba(255, 99, 132, 0.7)',
+        'rgba(54, 162, 235, 0.7)'
+      ],
+      borderColor: [
+        'rgb(74, 143, 231)',
+        'rgb(94, 114, 228)',
+        'rgb(23, 162, 184)',
+        'rgb(255, 99, 132)',
+        'rgb(54, 162, 235)'
+      ],
+      borderWidth: 1
+    }]
+  };
+  
   // 客户列表
-  clients = [
-    { id: 'C1001', name: '张明远', type: '企业客户', date: '06-28' },
-    { id: 'P2003', name: '李思琪', type: '个人客户', date: '06-27' },
-    { id: 'C1002', name: '王建国', type: '企业客户', date: '06-25' },
-    { id: 'P2004', name: '陈晓薇', type: '个人客户', date: '06-24' }
-  ];
-
+  clients: Client[] = [];
+  
   // 推荐数据
-  recommendations = [
+  recommendations: Recommendation[] = [
     {
       icon: 'fas fa-user-friends',
       title: '相似客户推荐',
@@ -73,58 +117,316 @@ export class PageCrmImage {
       iconTextColor: '#5e72e4'
     }
   ];
-
+  
+  // 格式化后的聊天消息
+  formattedChatMessages: ChatMessage[] = [];
+  
+  @ViewChild('analysisChart', { static: false }) analysisChartRef: ElementRef | undefined;
+  private analysisChart: Chart | undefined;
+  
+  constructor(private crmService: CRMServices) {
+    // 从后端加载客户数据
+    this.loadClientsFromBackend();
+  }
+  
+  ngOnInit() {
+    // 初始化时格式化聊天记录
+    this.formatChatMessages();
+  }
+  
+  ngAfterViewInit() {
+    this.createAnalysisChart();
+  }
+  
+  // 获取过滤后的客户列表
+  get filteredClients() {
+    if (!this.searchTerm) return this.clients;
+    
+    const term = this.searchTerm.toLowerCase();
+    return this.clients.filter(client => 
+      client.get('clientName')?.toLowerCase().includes(term) || 
+      client.get('clientId')?.toLowerCase().includes(term)
+    );
+  }
+  
+  // 从后端加载客户数据
+  async loadClientsFromBackend() {
+    this.showLoading('正在加载客户数据...');
+    
+    try {
+      const clients = await this.crmService.getAllClients();
+      this.clients = clients;
+    } catch (error) {
+      console.error('加载客户数据失败', error);
+      alert('加载客户数据失败,请稍后再试');
+    } finally {
+      this.hideLoading();
+    }
+  }
+  
+  // 格式化聊天记录
+  formatChatMessages() {
+    this.formattedChatMessages = [];
+    
+    if (!this.chatData) return;
+    
+    // 假设聊天记录格式为:[时间] 发送者: 消息内容
+    const chatRegex = /\[([^\]]+)\]\s*([^:]+):\s*(.+)/g;
+    let match;
+    
+    while ((match = chatRegex.exec(this.chatData)) !== null) {
+        const msg = new ChatMessage();
+        msg.setMessageData({
+            clientId: this.clientId,
+            time: match[1],
+            sender: match[2],
+            content: match[3]
+        });
+        this.formattedChatMessages.push(msg);
+    }
+    
+    // 如果没有匹配到特定格式,创建一个默认消息
+    if (this.formattedChatMessages.length === 0 && this.chatData.trim()) {
+        const msg = new ChatMessage();
+        msg.setMessageData({
+            clientId: this.clientId,
+            time: new Date().toLocaleTimeString(),
+            sender: '系统',
+            content: this.chatData
+        });
+        this.formattedChatMessages.push(msg);
+    }
+  }
+  
+  // 创建分析图表
+  createAnalysisChart() {
+    if (this.analysisChartRef) {
+      const ctx = this.analysisChartRef.nativeElement.getContext('2d');
+      if (ctx) {
+        this.analysisChart = new Chart(ctx, {
+          type: 'radar',
+          data: this.chartData,
+          options: {
+            scales: {
+              r: {
+                angleLines: {
+                  display: true
+                },
+                suggestedMin: 0,
+                suggestedMax: 100
+              }
+            }
+          }
+        });
+      }
+    }
+  }
+  
   // 保存分析
-  save() {
+  async save() {
     if (!this.clientId || !this.clientName || !this.chatData) {
       alert('请填写完整的客户信息和聊天记录');
       return;
     }
-    this.showBaseReportModal = true;
+    
+    // 格式化聊天记录
+    this.formatChatMessages();
+    
+    // 保存到后端
+    this.showLoading('正在保存客户信息...');
+    
+    try {
+      // 保存客户数据
+      await this.crmService.saveClient({
+        clientId: this.clientId,
+        clientName: this.clientName,
+        clientType: this.clientType,
+        chatData: this.chatData,
+        date: new Date().toLocaleDateString()
+      });
+      
+      // 保存聊天消息
+      await this.crmService.saveChatMessages(this.clientId, this.formattedChatMessages);
+      
+      // 刷新客户列表
+      await this.loadClientsFromBackend();
+      
+      this.hideLoading();
+      this.showBaseReportModal = true;
+    } catch (error) {
+      console.error('保存客户数据失败', error);
+      alert('保存客户数据失败,请稍后再试');
+      this.hideLoading();
+    }
   }
-
+  
   // 开始分析
-  analyze() {
+  async analyze() {
     if (!this.clientId || !this.clientName || !this.chatData) {
       alert('请填写完整的客户信息和聊天记录');
       return;
     }
-    alert(`开始分析客户 ${this.clientName} (${this.clientId}) 的聊天记录...`);
+    
+    // 格式化聊天记录
+    this.formatChatMessages();
+    
+    // 模拟分析过程
+    this.showLoading('正在分析客户数据...');
+    
+    setTimeout(() => {
+      // 模拟分析结果
+      this.analysisResult = {
+        gender: Math.random() > 0.5 ? '男' : '女',
+        age: `${Math.floor(Math.random() * 20) + 25}-${Math.floor(Math.random() * 5) + 30}岁`,
+        job: ['IT经理', '市场营销', '金融分析师', '教师', '医生'][Math.floor(Math.random() * 5)],
+        psychology: ['理性决策型', '冲动消费型', '品牌忠诚型', '社交导向型'][Math.floor(Math.random() * 4)],
+        consumption: ['注重性价比', '高端消费', '中等消费', '价格敏感型'][Math.floor(Math.random() * 4)],
+        needs: ['需要高效解决方案', '寻求价格优惠', '追求品质体验', '关注品牌价值'][Math.floor(Math.random() * 4)]
+      };
+      
+      // 更新图表数据
+      this.chartData.datasets[0].data = [
+        Math.floor(Math.random() * 40) + 60, // 理性决策
+        Math.floor(Math.random() * 80) + 20, // 价格敏感
+        Math.floor(Math.random() * 70) + 30, // 品牌忠诚
+        Math.floor(Math.random() * 50) + 10, // 冒险尝试
+        Math.floor(Math.random() * 70) + 30  // 社交导向
+      ];
+      
+      if (this.analysisChart) {
+        this.analysisChart.data = this.chartData;
+        this.analysisChart.update();
+      }
+      
+      this.hideLoading();
+      alert(`客户 ${this.clientName} (${this.clientId}) 分析完成!`);
+    }, 2000);
   }
-
-  // 打开聊天详情弹窗
-  openChatDetailModal() {
-    this.showBaseReportModal = false;
-    this.showChatDetailModal = true;
+  
+  // 导出报告
+  exportReport() {
+    this.showLoading('正在生成报告...');
+    
+    setTimeout(() => {
+      this.hideLoading();
+      alert('报告已成功导出!');
+    }, 1500);
   }
-
-  // 打开分析结果弹窗
-  openAnalysisResultModal() {
-    this.showBaseReportModal = false;
-    this.showAnalysisResultModal = true;
+  
+  // 加载客户数据
+  async loadClient(client: Client) {
+    this.clientId = client.get('clientId');
+    this.clientName = client.get('clientName');
+    this.clientType = client.get('clientType');
+    this.chatData = client.get('chatData');
+    
+    // 从后端加载聊天记录
+    this.showLoading('正在加载客户数据...');
+    
+    try {
+      const messages = await this.crmService.getChatMessages(this.clientId);
+      this.formattedChatMessages = messages;
+      
+      this.hideLoading();
+      this.showBaseReportModal = true;
+    } catch (error) {
+      console.error('加载聊天记录失败', error);
+      alert('加载聊天记录失败,请稍后再试');
+      this.hideLoading();
+    }
   }
-
-  // 打开推荐策略弹窗
-  openStrategyModal() {
-    alert('推荐策略功能开发中...');
+  
+  // 删除客户
+  async deleteClient(clientId: string, event: Event) {
+    event.stopPropagation(); // 阻止事件冒泡
+    
+    if (confirm('确定要删除这个客户记录吗?')) {
+      this.showLoading('正在删除客户数据...');
+      
+      try {
+        await this.crmService.deleteClient(clientId);
+        // 刷新客户列表
+        await this.loadClientsFromBackend();
+        this.hideLoading();
+      } catch (error) {
+        console.error('删除客户失败', error);
+        alert('删除客户失败,请稍后再试');
+        this.hideLoading();
+      }
+    }
   }
-
-  // 返回基础弹窗
+  
+  // 清空所有客户
+  async clearAllClients() {
+    if (confirm('确定要清空所有客户记录吗?此操作不可撤销!')) {
+      this.showLoading('正在清空客户数据...');
+      
+      try {
+        // 逐个删除客户
+        for (const client of this.clients) {
+          await this.crmService.deleteClient(client.get('clientId'));
+        }
+        
+        // 刷新客户列表
+        this.clients = [];
+        this.hideLoading();
+      } catch (error) {
+        console.error('清空客户数据失败', error);
+        alert('清空客户数据失败,请稍后再试');
+        this.hideLoading();
+      }
+    }
+  }
+  
+  // 刷新客户列表
+  async refreshClients() {
+    await this.loadClientsFromBackend();
+  }
+  
+  // 显示加载中状态
+  showLoading(message: string) {
+    this.loadingMessage = message;
+    this.isLoading = true;
+  }
+  
+  // 隐藏加载中状态
+  hideLoading() {
+    this.isLoading = false;
+  }
+  
+  // 关闭模态框
+  closeModal() {
+    this.showBaseReportModal = false;
+    this.showChatDetailModal = false;
+    this.showAnalysisResultModal = false;
+  }
+  
+  // 返回基础报告模态框
   backToBaseModal() {
     this.showChatDetailModal = false;
     this.showAnalysisResultModal = false;
     this.showBaseReportModal = true;
   }
-
-  // 关闭所有弹窗
-  closeModal() {
+  
+  // 打开聊天详情模态框
+  openChatDetailModal() {
     this.showBaseReportModal = false;
-    this.showChatDetailModal = false;
-    this.showAnalysisResultModal = false;
+    this.showChatDetailModal = true;
   }
-
-  // 客户点击事件
-  onClientClick(client: any) {
-    alert(`进入客户详情页:${client.name}`);
+  
+  // 打开分析结果模态框
+  openAnalysisResultModal() {
+    this.showBaseReportModal = false;
+    this.showAnalysisResultModal = true;
+  }
+  
+  // 打开策略模态框(假设实现)
+  openStrategyModal() {
+    alert('推荐策略功能将在未来版本中实现');
+  }
+  
+  // 处理推荐项点击
+  handleRecommendationAction(recommendation: Recommendation) {
+    alert(`执行推荐操作: ${recommendation.title}`);
   }
 }