Browse Source

update:home ,trainingweb.optimize app.routes.ts

0235701 2 days ago
parent
commit
f3a86636ff

+ 2 - 1
.hintrc

@@ -16,6 +16,7 @@
           "backdrop-filter"
         ]
       }
-    ]
+    ],
+    "meta-viewport": "off"
   }
 }

+ 11 - 10
ai-assisant/src/app/app.routes.ts

@@ -1,16 +1,17 @@
 import { Routes } from '@angular/router';
-import { PageCrmHome } from '../modules/crm/mobile/page-crm-home/page-crm-home';
-import { PageCrmImage } from '../modules/crm/mobile/page-crm-image/page-crm-image';
-import { PageCrmTraining } from '../modules/crm/mobile/page-crm-training/page-crm-training';
-
-
 
 export const routes: Routes = [
-    {
+  {
     path: 'crm/mobile',
     loadChildren: () => import('../modules/crm/mobile/mobile.routes').then(m => m.MOBILE_ROUTES)
   },
-  { path: '', component: PageCrmHome },
-  { path: '', component: PageCrmImage },
-  { path: '', component: PageCrmTraining},
-];
+  {
+    path: '',
+    redirectTo: 'crm/mobile/home', // 默认重定向到 CRM 首页
+    pathMatch: 'full'
+  },
+  {
+    path: '**',
+    redirectTo: 'crm/mobile/home' // 处理 404 错误
+  }
+];

+ 1 - 0
ai-assisant/src/index.html

@@ -8,6 +8,7 @@
   <link rel="icon" type="image/x-icon" href="favicon.ico">
   <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
   <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
+  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
 </head>
 <body class="mat-typography">
   <app-root></app-root>

+ 465 - 0
ai-assisant/src/lib/ncloud.ts

@@ -0,0 +1,465 @@
+// CloudObject.ts
+
+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;
+    }
+}
+
+// CloudQuery.ts
+export class CloudQuery {
+    className: string;
+    queryParams: Record<string, any> = { where: {} };
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    include(...fileds: string[]) {
+        this.queryParams["include"] = fileds;
+    }
+    greaterThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gt"] = value;
+    }
+
+    greaterThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gte"] = value;
+    }
+
+    lessThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lt"] = value;
+    }
+
+    lessThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lte"] = value;
+    }
+
+    equalTo(key: string, value: any) {
+        if (!this.queryParams["where"]) this.queryParams["where"] = {};
+        this.queryParams["where"][key] = value;
+    }
+    //新增代码
+    skip(count: number) {
+        this.queryParams["skip"] = count;
+        return this;
+    }
+
+    limit(count: number) {
+        this.queryParams["limit"] = count;
+        return this;
+    }
+
+    orderBy(field: string, direction: 'asc' | 'desc' = 'asc') {
+        const prefix = direction === 'desc' ? '-' : '';
+        this.queryParams["order"] = `${prefix}${field}`;
+        return this;
+    }
+
+    async count(): Promise<number> {
+        const url = serverURL + `/classes/${this.className}?count=1&limit=0`;
+        
+        const response = await fetch(url, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        return json?.count || 0;
+    }
+
+
+    //
+    async get(id: string) {
+        const url = serverURL + `/classes/${this.className}/${id}?`;
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        if (json) {
+            let existsObject = this.dataToObj(json)
+            return existsObject;
+        }
+        return null
+    }
+
+    async find(): Promise<Array<CloudObject>> {
+        let url = serverURL + `/classes/${this.className}?`;
+
+        let queryStr = ``
+        Object.keys(this.queryParams).forEach(key => {
+            let paramStr = JSON.stringify(this.queryParams[key]);
+            if (key == "include") {
+                paramStr = this.queryParams[key]?.join(",")
+            }
+            if (queryStr) {
+                url += `${key}=${paramStr}`;
+            } else {
+                url += `&${key}=${paramStr}`;
+            }
+        })
+        // if (Object.keys(this.queryParams["where"]).length) {
+
+        // }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        let list = json?.results || []
+        let objList = list.map((item: any) => this.dataToObj(item))
+        return objList || [];
+    }
+
+
+    async first() {
+        let url = serverURL + `/classes/${this.className}?`;
+
+        if (Object.keys(this.queryParams["where"]).length) {
+            const whereStr = JSON.stringify(this.queryParams["where"]);
+            url += `where=${whereStr}&limit=1`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        const exists = json?.results?.[0] || null;
+        if (exists) {
+            let existsObject = this.dataToObj(exists)
+            return existsObject;
+        }
+        return null
+    }
+
+    dataToObj(exists: any): CloudObject {
+        let existsObject = new CloudObject(this.className);
+        Object.keys(exists).forEach(key => {
+            if (exists[key]?.__type == "Object") {
+                exists[key] = this.dataToObj(exists[key])
+            }
+        })
+        existsObject.set(exists);
+        existsObject.id = exists.objectId;
+        existsObject.createdAt = exists.createdAt;
+        existsObject.updatedAt = exists.updatedAt;
+        return existsObject;
+    }
+}
+
+// CloudUser.ts
+export class CloudUser extends CloudObject {
+    constructor() {
+        super("_User"); // 假设用户类在Parse中是"_User"
+        // 读取用户缓存信息
+        let userCacheStr = localStorage.getItem("NCloud/dev/User")
+        if (userCacheStr) {
+            let userData = JSON.parse(userCacheStr)
+            // 设置用户信息
+            this.id = userData?.objectId;
+            this.sessionToken = userData?.sessionToken;
+            this.data = userData; // 保存用户数据
+        }
+    }
+
+    sessionToken: string | null = ""
+    /** 获取当前用户信息 */
+    async current() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return null;
+        }
+        return this;
+        // const response = await fetch(serverURL + `/users/me`, {
+        //     headers: {
+        //         "x-parse-application-id": "dev",
+        //         "x-parse-session-token": this.sessionToken // 使用sessionToken进行身份验证
+        //     },
+        //     method: "GET"
+        // });
+
+        // const result = await response?.json();
+        // if (result?.error) {
+        //     console.error(result?.error);
+        //     return null;
+        // }
+        // return result;
+    }
+
+    /** 登录 */
+    async login(username: string, password: string): Promise<CloudUser | null> {
+        const response = await fetch(serverURL + `/login`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify({ username, password }),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+
+        // 设置用户信息
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        // 缓存用户信息
+        console.log(result)
+        localStorage.setItem("NCloud/dev/User", JSON.stringify(result))
+        return this;
+    }
+
+    /** 登出 */
+    async logout() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return;
+        }
+
+        const response = await fetch(serverURL + `/logout`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "x-parse-session-token": this.sessionToken
+            },
+            method: "POST"
+        });
+
+        let result = await response?.json();
+
+        if (result?.error) {
+            console.error(result?.error);
+            if (result?.error == "Invalid session token") {
+                this.clearUserCache()
+                return true;
+            }
+            return false;
+        }
+
+        this.clearUserCache()
+        return true;
+    }
+    clearUserCache() {
+        // 清除用户信息
+        localStorage.removeItem("NCloud/dev/User")
+        this.id = undefined;
+        this.sessionToken = null;
+        this.data = {};
+    }
+
+    /** 注册 */
+    async signUp(username: string, password: string, additionalData: Record<string, any> = {}) {
+        const userData = {
+            username,
+            password,
+            ...additionalData // 合并额外的用户数据
+        };
+
+        const response = await fetch(serverURL + `/users`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify(userData),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+
+        // 设置用户信息
+        // 缓存用户信息
+        console.log(result)
+        localStorage.setItem("NCloud/dev/User", JSON.stringify(result))
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        return this;
+    }
+
+    override async save() {
+        let method = "POST";
+        let url = serverURL + `/users`;
+
+        // 更新用户信息
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        let data: any = JSON.parse(JSON.stringify(this.data))
+        delete data.createdAt
+        delete data.updatedAt
+        delete data.ACL
+        delete data.objectId
+        const body = JSON.stringify(data);
+        let headersOptions: any = {
+            "content-type": "application/json;charset=UTF-8",
+            "x-parse-application-id": "dev",
+            "x-parse-session-token": this.sessionToken, // 添加sessionToken以进行身份验证
+        }
+        const response = await fetch(url, {
+            headers: headersOptions,
+            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;
+        }
+        localStorage.setItem("NCloud/dev/User", JSON.stringify(this.data))
+        return this;
+    }
+}
+
+export class CloudApi {
+    async fetch(path: string, body: any, options?: {
+        method: string
+        body: any
+    }) {
+
+        let reqOpts: any = {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            method: options?.method || "POST",
+            mode: "cors",
+            credentials: "omit"
+        }
+        if (body || options?.body) {
+            reqOpts.body = JSON.stringify(body || options?.body);
+            reqOpts.json = true;
+        }
+        let host = `https://dev.fmode.cn`
+        // host = `http://127.0.0.1:1337`
+        let url = `${host}/api/` + path
+        console.log(url, reqOpts)
+        const response = await fetch(url, reqOpts);
+        let json = await response.json();
+        return json
+    }
+}

+ 109 - 122
ai-assisant/src/modules/crm/mobile/page-crm-home/page-crm-home.html

@@ -6,7 +6,6 @@
     <title>AI实验室 - 酒店销售智能平台</title>
     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
     <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
-    
 </head>
 <body>
     <div class="dashboard-container">
@@ -22,11 +21,11 @@
                 </div>
             </div>
             <div class="user-actions">
-                <div class="action-btn">
+                <div class="action-btn" (click)="toggleMessagePopup()">
                     <i class="fas fa-bell"></i>
-                    <div class="badge">5</div>
+                    <div class="badge" *ngIf="unreadMessagesCount > 0">{{ unreadMessagesCount }}</div>
                 </div>
-                <div class="action-btn">
+                <div class="action-btn" (click)="toggleProfilePopup()">
                     <i class="fas fa-user"></i>
                 </div>
             </div>
@@ -35,9 +34,8 @@
         <!-- 欢迎区域 -->
         <div class="welcome-section">
             <h1 class="greeting">你好,<span>销售精英</span> 👋</h1>
-            
             <div class="motivational-text">
-                每一次对话都是机会,每一次练习都是进步。今天,让AI成为您最强大的销售伙伴!
+                {{ motivationalText }}
             </div>
         </div>
         
@@ -45,9 +43,6 @@
         <div class="stats-card">
             <div class="stats-header">
                 <div class="stats-title">团队今日战绩</div>
-                <div class="action-btn">
-                    <i class="fas fa-arrow-right"></i>
-                </div>
             </div>
             <div class="stats-container">
                 <div class="stat-item">
@@ -67,56 +62,16 @@
         
         <!-- 功能卡片网格 -->
         <div class="features-grid">
-            <!-- 虚拟陪练 -->
-            <div class="feature-card">
-                <div class="pulse"></div>
-                <div class="card-icon">
-                    <i class="fas fa-robot"></i>
-                </div>
-                <h3 class="card-title">虚拟陪练</h3>
-                <p class="card-desc">与AI客户进行真实销售对话练习,提升沟通技巧</p>
-                <div class="card-stats">
-                    <span><i class="fas fa-history"></i> 86次练习</span>
-                    <span>92%得分</span>
-                </div>
-            </div>
-            
-            <!-- 话术决策 -->
-            <div class="feature-card">
+            <div class="feature-card" *ngFor="let feature of features" (click)="navigateTo(feature.id)">
+                <div class="pulse" *ngIf="feature.hasNotification"></div>
                 <div class="card-icon">
-                    <i class="fas fa-comments"></i>
+                    <i [class]="feature.icon"></i>
                 </div>
-                <h3 class="card-title">话术决策</h3>
-                <p class="card-desc">智能生成最佳沟通策略,应对各种销售场景</p>
+                <h3 class="card-title">{{ feature.title }}</h3>
+                <p class="card-desc">{{ feature.description }}</p>
                 <div class="card-stats">
-                    <span><i class="fas fa-star"></i> 34个策略</span>
-                    <span>89%有效</span>
-                </div>
-            </div>
-            
-            <!-- 数据训练 -->
-            <div class="feature-card">
-                <div class="card-icon">
-                    <i class="fas fa-database"></i>
-                </div>
-                <h3 class="card-title">数据训练</h3>
-                <p class="card-desc">上传销售数据训练AI模型,优化决策能力</p>
-                <div class="card-stats">
-                    <span><i class="fas fa-chart-line"></i> 1.2GB</span>
-                    <span>95%准确</span>
-                </div>
-            </div>
-            
-            <!-- 客户画像 -->
-            <div class="feature-card">
-                <div class="card-icon">
-                    <i class="fas fa-user-tie"></i>
-                </div>
-                <h3 class="card-title">客户画像</h3>
-                <p class="card-desc">生成详细客户分析报告,洞察客户需求</p>
-                <div class="card-stats">
-                    <span><i class="fas fa-file-alt"></i> 28份</span>
-                    <span>更新中</span>
+                    <span><i [class]="feature.statIcon"></i> {{ feature.stat1 }}</span>
+                    <span>{{ feature.stat2 }}</span>
                 </div>
             </div>
         </div>
@@ -136,81 +91,113 @@
         <div class="inspiration-section">
             <h2 class="inspiration-title"><i class="fas fa-trophy"></i> 成功源于持续精进</h2>
             <p class="inspiration-content">
-                您已经完成了<span style="color: var(--primary-blue); font-weight: 700;">86次</span>虚拟陪练,
+                您已经完成了<span style="color: var(--primary-blue); font-weight: 700;">{{ user.trainingCount }}次</span>虚拟陪练,
                 击败了<span style="color: var(--primary-blue); font-weight: 700;">92%</span>的同级销售。
                 继续保持,下一个销售冠军就是您!
             </p>
         </div>
         
+        <!-- 消息弹窗 -->
+        <div class="popup-overlay" *ngIf="showMessagePopup" (click)="toggleMessagePopup()">
+            <div class="popup-container" (click)="$event.stopPropagation()">
+                <div class="popup-header">
+                    <h3>消息通知 ({{ unreadMessagesCount }}未读)</h3>
+                    <div class="popup-actions">
+                        <button class="mark-all-read" (click)="markAllAsRead()" *ngIf="unreadMessagesCount > 0">
+                            <fa-icon [icon]="icons.faCheck"></fa-icon> 全部已读
+                        </button>
+                        <button (click)="toggleMessagePopup()"><fa-icon [icon]="icons.faTimes"></fa-icon></button>
+                    </div>
+                </div>
+                <div class="message-list-container">
+                    <div class="message-list">
+                        <div class="message-item" 
+                             *ngFor="let message of messages" 
+                             [class.unread]="message.unread"
+                             (click)="showMessageDetail(message, $event)">
+                            <div class="message-content">
+                                <div class="message-header">
+                                    <div class="message-title">{{ message.title }}</div>
+                                    <div class="message-time">{{ message.time }}</div>
+                                </div>
+                                <div class="message-preview">{{ message.content | slice:0:50 }}...</div>
+                            </div>
+                            <button class="read-btn" 
+                                    (click)="markMessageAsRead(message, $event)" 
+                                    *ngIf="message.unread">
+                                <fa-icon [icon]="icons.faCheck"></fa-icon>
+                            </button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 消息详情弹窗 -->
+        <div class="popup-overlay" *ngIf="selectedMessage" (click)="closeMessageDetail()">
+            <div class="popup-container" (click)="$event.stopPropagation()">
+                <div class="popup-header">
+                    <h3>{{ selectedMessage.title }}</h3>
+                    <button (click)="closeMessageDetail()"><fa-icon [icon]="icons.faTimes"></fa-icon></button>
+                </div>
+                <div class="message-content-detail">
+                    <div class="message-meta">
+                        <span>{{ selectedMessage.time }}</span>
+                        <span class="unread-badge" *ngIf="selectedMessage.unread">未读</span>
+                    </div>
+                    <p>{{ selectedMessage.content }}</p>
+                    <div class="message-actions">
+                        <button class="btn-primary" 
+                                (click)="markAsRead(selectedMessage)" 
+                                *ngIf="selectedMessage.unread">
+                            <fa-icon [icon]="icons.faCheck"></fa-icon> 标记为已读
+                        </button>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 个人信息弹窗 -->
+        <div class="popup-overlay" *ngIf="showProfilePopup" (click)="toggleProfilePopup()">
+            <div class="popup-container" (click)="$event.stopPropagation()">
+                <div class="popup-header">
+                    <h3>个人信息</h3>
+                    <button (click)="toggleProfilePopup()"><fa-icon [icon]="icons.faTimes"></fa-icon></button>
+                </div>
+                <div class="profile-content">
+                    <div class="profile-avatar">
+                        <fa-icon [icon]="icons.faUserCircle"></fa-icon>
+                    </div>
+                    <div class="profile-details">
+                        <div class="detail-item">
+                            <span class="detail-label">姓名:</span>
+                            <span class="detail-value">{{ user.name }}</span>
+                        </div>
+                        <div class="detail-item">
+                            <span class="detail-label">职位:</span>
+                            <span class="detail-value">{{ user.role }}</span>
+                        </div>
+                        <div class="detail-item">
+                            <span class="detail-label">年龄:</span>
+                            <span class="detail-value">{{ user.age }}</span>
+                        </div>
+                        <div class="detail-item">
+                            <span class="detail-label">出生日期:</span>
+                            <span class="detail-value">{{ user.birthDate }}</span>
+                        </div>
+                        <div class="detail-item">
+                            <span class="detail-label">训练次数:</span>
+                            <span class="detail-value">{{ user.trainingCount }} 次</span>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        
         <!-- 底部版权信息 -->
         <div class="footer">
             <p>© 2023 AI实验室 - 酒店销售智能平台 | 赋能销售精英,创造无限可能</p>
         </div>
     </div>
-
-    <script>
-        // 模拟动态数据更新
-        document.addEventListener('DOMContentLoaded', function() {
-            // 更新统计数据
-            const stats = document.querySelectorAll('.stat-value');
-            let values = [128, 42, 92];
-            let counters = values.map(value => 0);
-            
-            stats.forEach((stat, index) => {
-                const target = values[index];
-                const increment = target / 50;
-                
-                const updateCounter = () => {
-                    if (counters[index] < target) {
-                        counters[index] += increment;
-                        stat.textContent = index === 2 ? 
-                            Math.min(Math.ceil(counters[index]), target) + "%" : 
-                            Math.min(Math.ceil(counters[index]), target);
-                        setTimeout(updateCounter, 30);
-                    } else {
-                        stat.textContent = index === 2 ? target + "%" : target;
-                    }
-                };
-                
-                updateCounter();
-            });
-            
-            // 卡片悬停效果增强
-            const cards = document.querySelectorAll('.feature-card');
-            cards.forEach(card => {
-                card.addEventListener('mouseenter', function() {
-                    this.style.transform = 'translateY(-10px)';
-                });
-                
-                card.addEventListener('mouseleave', function() {
-                    this.style.transform = 'translateY(0)';
-                });
-            });
-            
-            // 用户操作按钮效果
-            const actionBtns = document.querySelectorAll('.action-btn');
-            actionBtns.forEach(btn => {
-                btn.addEventListener('click', function() {
-                    this.style.transform = 'scale(0.95)';
-                    setTimeout(() => {
-                        this.style.transform = '';
-                    }, 200);
-                });
-            });
-            
-            // 添加卡片进入动画
-            const featureCards = document.querySelectorAll('.feature-card');
-            featureCards.forEach((card, index) => {
-                card.style.opacity = '0';
-                card.style.transform = 'translateY(30px)';
-                
-                setTimeout(() => {
-                    card.style.transition = 'opacity 0.5s ease, transform 0.5s ease';
-                    card.style.opacity = '1';
-                    card.style.transform = 'translateY(0)';
-                }, 300 + index * 150);
-            });
-        });
-    </script>
 </body>
 </html>

+ 518 - 210
ai-assisant/src/modules/crm/mobile/page-crm-home/page-crm-home.scss

@@ -1,4 +1,4 @@
-        :root {
+:root {
             --primary-blue: #2563eb;
             --accent-teal: #0d9488;
             --accent-purple: #7b4bff;
@@ -34,19 +34,19 @@
             background: linear-gradient(135deg, #f8fafc, #e0f2fe);
             color: var(--text-dark);
             line-height: 1.6;
-            overflow-x: hidden;
             min-height: 100vh;
             padding: 0;
         }
 
         .dashboard-container {
+            width: 100%;
             max-width: 1200px;
             margin: 0 auto;
             padding: 20px 16px 40px;
             position: relative;
         }
 
-        /* 头部导航 - 增强效果 */
+        /* 头部导航 - 响应式调整 */
         .header {
             display: flex;
             justify-content: space-between;
@@ -55,37 +55,40 @@
             margin-bottom: 10px;
             position: relative;
             border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+            flex-wrap: wrap;
         }
 
         .logo {
             display: flex;
             align-items: center;
             gap: 14px;
+            flex-shrink: 0;
+            margin-bottom: 10px;
         }
 
         .logo-icon {
-            width: 46px;
-            height: 46px;
+            width: 40px;
+            height: 40px;
             background: var(--accent-gradient);
-            border-radius: 14px;
+            border-radius: 12px;
             display: flex;
             align-items: center;
             justify-content: center;
             box-shadow: var(--shadow-md);
             transition: var(--transition);
+            flex-shrink: 0;
         }
 
         .logo-icon i {
-            font-size: 22px;
+            font-size: 18px;
             color: white;
             animation: pulse 2s infinite;
         }
 
         .logo-text {
-            font-size: 24px;
+            font-size: 20px;
             font-weight: 800;
             color: var(--text-dark);
-            letter-spacing: -0.5px;
             line-height: 1.2;
             background: linear-gradient(90deg, var(--primary-blue), var(--accent-purple));
             -webkit-background-clip: text;
@@ -93,7 +96,7 @@
         }
 
         .logo-subtext {
-            font-size: 13px;
+            font-size: 12px;
             color: var(--primary-blue);
             font-weight: 600;
             letter-spacing: 0.5px;
@@ -102,26 +105,27 @@
 
         .user-actions {
             display: flex;
-            gap: 20px;
+            gap: 15px;
+            flex-shrink: 0;
         }
 
         .action-btn {
-            background: var(--card-bg);
-            border-radius: 12px;
-            width: 44px;
-            height: 44px;
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            color: var(--text-medium);
-            font-size: 18px;
-            cursor: pointer;
-            transition: var(--transition);
-            border: 1px solid var(--border-color);
-            position: relative;
-            box-shadow: var(--shadow-sm);
+          flex: 1;
+          padding: 12px;
+          border-radius: 12px;
+          border: none;
+          font-weight: 600;
+          cursor: pointer;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          gap: 8px;
+          transition: all 0.3s ease;
+          color: white;
+          box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
         }
 
+
         .action-btn:hover {
             background: #f1f5f9;
             transform: translateY(-3px);
@@ -133,23 +137,23 @@
             position: absolute;
             top: -6px;
             right: -6px;
-            width: 22px;
-            height: 22px;
+            width: 18px;
+            height: 18px;
             background: var(--danger);
             color: white;
             border-radius: 50%;
             display: flex;
             align-items: center;
             justify-content: center;
-            font-size: 12px;
+            font-size: 10px;
             font-weight: 600;
             animation: bounce 1.5s infinite;
         }
 
-        /* 欢迎区域 - 增强效果 */
+        /* 欢迎区域 - 响应式调整 */
         .welcome-section {
-            margin: 25px 0 40px;
-            padding: 30px;
+            margin: 20px 0 30px;
+            padding: 20px;
             text-align: center;
             background: var(--card-bg);
             border-radius: var(--border-radius);
@@ -165,14 +169,14 @@
             top: 0;
             left: 0;
             width: 100%;
-            height: 5px;
+            height: 4px;
             background: var(--accent-gradient);
         }
 
         .greeting {
-            font-size: 36px;
+            font-size: 24px;
             font-weight: 800;
-            margin-bottom: 15px;
+            margin-bottom: 10px;
             line-height: 1.3;
             position: relative;
             display: inline-block;
@@ -190,7 +194,7 @@
             bottom: 2px;
             left: 0;
             width: 100%;
-            height: 12px;
+            height: 8px;
             background: rgba(37, 99, 235, 0.2);
             border-radius: 4px;
             z-index: -1;
@@ -198,14 +202,13 @@
         }
 
         .motivational-text {
-            font-size: 18px;
+            font-size: 16px;
             color: var(--text-dark);
             font-weight: 500;
-            line-height: 1.7;
-            max-width: 90%;
-            margin: 30px auto 15px;
-            padding: 20px;
-            border-radius: 18px;
+            line-height: 1.6;
+            margin: 20px auto 10px;
+            padding: 15px;
+            border-radius: 14px;
             background: rgba(37, 99, 235, 0.03);
             border-left: 4px solid var(--primary-blue);
             position: relative;
@@ -215,32 +218,120 @@
         .motivational-text::before {
             content: '"';
             position: absolute;
-            top: -20px;
-            left: 10px;
-            font-size: 70px;
+            top: -15px;
+            left: 8px;
+            font-size: 50px;
             color: rgba(37, 99, 235, 0.1);
             font-family: Georgia, serif;
         }
 
-        /* 功能卡片网格 - 增强效果 */
+        /* 数据统计卡片 - 响应式调整 */
+        .stats-card {
+            background: var(--card-bg);
+            border-radius: var(--border-radius);
+            padding: 20px;
+            margin: 25px 0;
+            border: 1px solid var(--border-color);
+            box-shadow: var(--shadow-md);
+            position: relative;
+            overflow: hidden;
+            background-image: radial-gradient(circle at top right, rgba(37, 99, 235, 0.05) 0%, transparent 30%);
+        }
+
+        .stats-card::before {
+            content: '';
+            position: absolute;
+            top: 0;
+            left: 0;
+            right: 0;
+            height: 4px;
+            background: var(--accent-gradient);
+        }
+
+        .stats-header {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-bottom: 15px;
+        }
+
+        .stats-title {
+            font-size: 18px;
+            font-weight: 700;
+            color: var(--text-dark);
+            display: flex;
+            align-items: center;
+            gap: 8px;
+        }
+
+        .stats-title::before {
+            content: "";
+            display: block;
+            width: 5px;
+            height: 20px;
+            background: #4f46e5; /* 修改为紫色,与虚拟陪练区分 */
+            border-radius: 4px;
+        }
+
+        .stats-container {
+            display: flex;
+            justify-content: space-between;
+            gap: 12px;
+            flex-wrap: wrap;
+        }
+
+        .stat-item {
+            text-align: center;
+            padding: 12px 8px;
+            background: var(--light-gray);
+            border-radius: 10px;
+            transition: var(--transition);
+            box-shadow: var(--shadow-sm);
+            position: relative;
+            overflow: hidden;
+            flex: 1 1 100px;
+            min-width: 0;
+        }
+
+        .stat-item:hover {
+            transform: translateY(-3px);
+            box-shadow: var(--shadow-md);
+            background: white;
+        }
+
+        .stat-value {
+            font-size: 20px;
+            font-weight: 800;
+            color: #4f46e5; /* 修改为紫色,与虚拟陪练区分 */
+            margin-bottom: 5px;
+            text-shadow: 0 2px 4px rgba(79, 70, 229, 0.1);
+        }
+
+        .stat-label {
+            font-size: 13px;
+            color: var(--text-light);
+            font-weight: 500;
+        }
+
+        /* 功能卡片网格 - 响应式调整 */
         .features-grid {
             display: grid;
             grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
-            gap: 24px;
-            margin: 40px 0;
+            gap: 16px;
+            margin: 25px 0;
         }
 
         .feature-card {
             background: var(--card-bg);
             border-radius: var(--border-radius);
-            padding: 28px 24px;
+            padding: 18px;
             position: relative;
             overflow: hidden;
             transition: var(--transition);
             border: 1px solid var(--border-color);
             box-shadow: var(--shadow-md);
             cursor: pointer;
-            min-height: 250px;
+            min-height: 180px;
             display: flex;
             flex-direction: column;
             z-index: 1;
@@ -260,8 +351,8 @@
         }
 
         .feature-card:hover {
-            transform: translateY(-10px);
-            box-shadow: 0 15px 30px rgba(0, 0, 0, 0.12);
+            transform: translateY(-5px);
+            box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
             border-color: transparent;
         }
 
@@ -271,14 +362,14 @@
         }
 
         .card-icon {
-            width: 60px;
-            height: 60px;
-            border-radius: 16px;
+            width: 45px;
+            height: 45px;
+            border-radius: 12px;
             display: flex;
             align-items: center;
             justify-content: center;
-            margin-bottom: 20px;
-            font-size: 28px;
+            margin-bottom: 12px;
+            font-size: 22px;
             transition: var(--transition);
             position: relative;
             z-index: 2;
@@ -286,24 +377,24 @@
         }
 
         .feature-card:hover .card-icon {
-            transform: translateY(-5px);
+            transform: translateY(-3px);
         }
 
         .card-title {
-            font-size: 22px;
+            font-size: 17px;
             font-weight: 700;
-            margin-bottom: 15px;
+            margin-bottom: 10px;
             color: var(--text-dark);
             position: relative;
             z-index: 2;
         }
 
         .card-desc {
-            font-size: 16px;
+            font-size: 14px;
             color: var(--text-medium);
-            line-height: 1.6;
+            line-height: 1.5;
             flex-grow: 1;
-            margin-bottom: 15px;
+            margin-bottom: 10px;
             position: relative;
             z-index: 2;
         }
@@ -312,10 +403,10 @@
             display: flex;
             justify-content: space-between;
             margin-top: auto;
-            font-size: 14px;
+            font-size: 12px;
             font-weight: 500;
             color: var(--text-light);
-            padding-top: 12px;
+            padding-top: 8px;
             border-top: 1px solid rgba(0, 0, 0, 0.05);
             position: relative;
             z-index: 2;
@@ -325,102 +416,12 @@
             margin-right: 5px;
         }
 
-        /* 数据统计卡片 - 增强效果 */
-        .stats-card {
-            background: var(--card-bg);
-            border-radius: var(--border-radius);
-            padding: 28px;
-            margin: 40px 0;
-            border: 1px solid var(--border-color);
-            box-shadow: var(--shadow-md);
-            position: relative;
-            overflow: hidden;
-            background-image: radial-gradient(circle at top right, rgba(37, 99, 235, 0.05) 0%, transparent 30%);
-        }
-
-        .stats-card::before {
-            content: '';
-            position: absolute;
-            top: 0;
-            left: 0;
-            right: 0;
-            height: 5px;
-            background: var(--accent-gradient);
-        }
-
-        .stats-header {
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-            margin-bottom: 30px;
-        }
-
-        .stats-title {
-            font-size: 22px;
-            font-weight: 700;
-            color: var(--text-dark);
-            display: flex;
-            align-items: center;
-            gap: 10px;
-        }
-
-        .stats-title::before {
-            content: "";
-            display: block;
-            width: 8px;
-            height: 30px;
-            background: var(--primary-blue);
-            border-radius: 4px;
-        }
-
-        .action-btn {
-            background: rgba(37, 99, 235, 0.1);
-            color: var(--primary-blue);
-        }
-
-        .stats-container {
-            display: grid;
-            grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
-            gap: 24px;
-        }
-
-        .stat-item {
-            text-align: center;
-            padding: 25px 15px;
-            background: var(--light-gray);
-            border-radius: 14px;
-            transition: var(--transition);
-            box-shadow: var(--shadow-sm);
-            position: relative;
-            overflow: hidden;
-        }
-
-        .stat-item:hover {
-            transform: translateY(-5px);
-            box-shadow: var(--shadow-md);
-            background: white;
-        }
-
-        .stat-value {
-            font-size: 36px;
-            font-weight: 800;
-            color: var(--primary-blue);
-            margin-bottom: 8px;
-            text-shadow: 0 2px 4px rgba(37, 99, 235, 0.1);
-        }
-
-        .stat-label {
-            font-size: 15px;
-            color: var(--text-light);
-            font-weight: 500;
-        }
-
-        /* 激励区域 - 增强效果 */
+        /* 激励区域 - 响应式调整 */
         .inspiration-section {
             background: linear-gradient(135deg, #f0f9ff, var(--light-blue));
             border-radius: var(--border-radius);
-            padding: 40px 35px;
-            margin: 40px 0;
+            padding: 20px;
+            margin: 25px 0;
             text-align: center;
             border: 1px solid #dbeafe;
             position: relative;
@@ -430,30 +431,29 @@
         }
 
         .inspiration-title {
-            font-size: 26px;
+            font-size: 18px;
             font-weight: 800;
             color: var(--primary-blue);
-            margin-bottom: 20px;
+            margin-bottom: 12px;
             display: flex;
             align-items: center;
             justify-content: center;
-            gap: 12px;
+            gap: 8px;
         }
 
         .inspiration-content {
-            font-size: 18px;
+            font-size: 15px;
             color: var(--text-dark);
-            line-height: 1.7;
-            max-width: 700px;
+            line-height: 1.6;
             margin: 0 auto;
             font-weight: 500;
         }
 
         .inspiration-icon {
             position: absolute;
-            top: 20px;
-            right: 20px;
-            font-size: 80px;
+            top: 10px;
+            right: 10px;
+            font-size: 50px;
             color: rgba(37, 99, 235, 0.08);
             z-index: 0;
         }
@@ -493,27 +493,27 @@
 
         .pulse {
             position: absolute;
-            top: 15px;
-            right: 15px;
-            width: 10px;
-            height: 10px;
+            top: 8px;
+            right: 8px;
+            width: 7px;
+            height: 7px;
             background: var(--danger);
             border-radius: 50%;
             animation: pulse 1.5s infinite;
             z-index: 3;
         }
 
-        /* 新增: 底部版权信息 */
+        /* 底部版权信息 */
         .footer {
             text-align: center;
-            padding: 30px 0 20px;
+            padding: 20px 0 10px;
             color: var(--text-light);
-            font-size: 14px;
+            font-size: 13px;
             border-top: 1px solid rgba(0, 0, 0, 0.05);
             margin-top: 20px;
         }
 
-        /* 响应式设计 */
+        /* 响应式调整 */
         @media (max-width: 768px) {
             .dashboard-container {
                 padding: 15px 12px 30px;
@@ -524,71 +524,379 @@
             }
             
             .logo-icon {
-                width: 42px;
-                height: 42px;
+                width: 36px;
+                height: 36px;
             }
             
             .logo-text {
-                font-size: 22px;
+                font-size: 18px;
             }
             
-            .greeting {
-                font-size: 28px;
+            .logo-subtext {
+                font-size: 11px;
             }
             
-            .motivational-text {
-                font-size: 17px;
-                padding: 15px;
+            .action-btn {
+                width: 36px;
+                height: 36px;
+                font-size: 15px;
             }
             
-            .features-grid {
-                gap: 18px;
+            .greeting {
+                font-size: 22px;
             }
             
-            .feature-card {
-                padding: 24px 20px;
+            .motivational-text {
+                font-size: 15px;
+                padding: 12px;
             }
             
-            .inspiration-section {
-                padding: 25px 22px;
+            .stats-title {
+                font-size: 16px;
             }
             
             .stat-value {
-                font-size: 28px;
-            }
-        }
-
-        @media (max-width: 480px) {
-            .header {
-                flex-wrap: wrap;
+                font-size: 18px;
             }
             
-            .logo {
-                margin-bottom: 15px;
+            .card-title {
+                font-size: 16px;
             }
             
-            .user-actions {
-                width: 100%;
-                justify-content: flex-end;
+            .card-desc {
+                font-size: 13px;
             }
             
-            .greeting {
-                font-size: 24px;
+            .inspiration-title {
+                font-size: 17px;
             }
             
-            .motivational-text {
-                font-size: 16px;
+            .inspiration-content {
+                font-size: 14px;
             }
-            
-            .stats-container {
+        }
+
+        @media (max-width: 480px) {
+            .features-grid {
                 grid-template-columns: 1fr;
             }
             
-            .inspiration-title {
-                font-size: 22px;
+            .stats-container {
+                flex-direction: column;
+                gap: 10px;
             }
             
-            .inspiration-content {
-                font-size: 16px;
+            .stat-item {
+                flex: 1 1 auto;
             }
         }
+        /* 弹窗样式 */
+.popup-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+  backdrop-filter: blur(5px);
+  animation: fadeIn 0.3s ease-out;
+}
+
+.popup-container {
+  background: white;
+  border-radius: var(--border-radius);
+  width: 90%;
+  max-width: 500px;
+  max-height: 80vh;
+  overflow: hidden;
+  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+  display: flex;
+  flex-direction: column;
+  animation: slideUp 0.3s ease-out;
+}
+
+.popup-header {
+  padding: 16px 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border-bottom: 1px solid var(--border-color);
+  background: var(--light-gray);
+  
+  h3 {
+    font-size: 18px;
+    font-weight: 700;
+    margin: 0;
+    color: var(--text-dark);
+  }
+  
+  button {
+    background: none;
+    border: none;
+    font-size: 18px;
+    color: var(--text-light);
+    cursor: pointer;
+    padding: 5px;
+    
+    &:hover {
+      color: var(--primary-blue);
+    }
+  }
+}
+
+.message-list {
+  overflow-y: auto;
+  flex-grow: 1;
+}
+
+.message-item {
+  padding: 16px 20px;
+  border-bottom: 1px solid var(--border-color);
+  cursor: pointer;
+  transition: var(--transition);
+  
+  &:hover {
+    background: var(--light-gray);
+  }
+  
+  &.unread {
+    background: rgba(37, 99, 235, 0.05);
+  }
+}
+
+.message-header {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 8px;
+}
+
+.message-title {
+  font-weight: 600;
+  color: var(--text-dark);
+}
+
+.message-time {
+  font-size: 12px;
+  color: var(--text-light);
+}
+
+.message-preview {
+  font-size: 14px;
+  color: var(--text-medium);
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.message-content {
+  padding: 20px;
+  overflow-y: auto;
+  
+  .message-meta {
+    font-size: 14px;
+    color: var(--text-light);
+    margin-bottom: 20px;
+  }
+  
+  p {
+    line-height: 1.6;
+    margin-bottom: 20px;
+  }
+}
+
+.message-actions {
+  display: flex;
+  justify-content: flex-end;
+}
+
+.profile-content {
+  padding: 20px;
+  text-align: center;
+}
+
+.profile-avatar {
+  font-size: 60px;
+  color: var(--primary-blue);
+  margin-bottom: 20px;
+}
+
+.profile-details {
+  text-align: left;
+}
+
+.detail-item {
+  display: flex;
+  margin-bottom: 15px;
+  padding-bottom: 15px;
+  border-bottom: 1px solid var(--border-color);
+}
+
+.detail-label {
+  font-weight: 600;
+  color: var(--text-dark);
+  width: 100px;
+}
+
+.detail-value {
+  color: var(--text-medium);
+  flex-grow: 1;
+}
+
+.btn-primary {
+  background: var(--primary-blue);
+  color: white;
+  border: none;
+  padding: 8px 16px;
+  border-radius: 8px;
+  font-weight: 600;
+  cursor: pointer;
+  transition: var(--transition);
+  
+  &:hover {
+    background: darken(#2563eb, 10%);
+  }
+}
+
+/* 动画 */
+@keyframes fadeIn {
+  from { 
+    opacity: 0;
+    transform: translateY(8px);
+  }
+  to { 
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+@keyframes slideUp {
+  from { 
+    transform: translateY(20px);
+    opacity: 0;
+  }
+  to { 
+    transform: translateY(0);
+    opacity: 1;
+  }
+}
+/* 消息列表容器 - 可滚动 */
+.message-list-container {
+  height: 60vh;
+  overflow-y: auto;
+  padding-right: 5px;
+  
+  /* 滚动条样式 */
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+  
+  &::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 10px;
+  }
+  
+  &::-webkit-scrollbar-thumb {
+    background: #c1c1c1;
+    border-radius: 10px;
+    
+    &:hover {
+      background: #a8a8a8;
+    }
+  }
+}
+
+/* 消息项样式 */
+.message-item {
+  display: flex;
+  align-items: center;
+  padding: 12px 16px;
+  border-bottom: 1px solid #e2e8f0;
+  transition: all 0.3s ease;
+  
+  &:hover {
+    background: #f8fafc;
+  }
+  
+  &.unread {
+    background: rgba(37, 99, 235, 0.05);
+  }
+}
+
+.message-content {
+  flex-grow: 1;
+  min-width: 0; /* 防止内容溢出 */
+  cursor: pointer;
+}
+
+/* 已读按钮样式 */
+.read-btn {
+  background: #e2e8f0;
+  border: none;
+  padding: 6px 12px;
+  border-radius: 16px;
+  font-size: 12px;
+  color: #4b5563;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  white-space: nowrap;
+  margin-left: 10px;
+  
+  &:hover {
+    background: #cbd5e1;
+    color: #1e40af;
+  }
+}
+
+/* 全部已读按钮 */
+.mark-all-read {
+  background: none;
+  border: none;
+  color: #2563eb;
+  font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  padding: 6px 12px;
+  margin-right: 10px;
+  
+  &:hover {
+    text-decoration: underline;
+  }
+}
+
+.popup-actions {
+  display: flex;
+  align-items: center;
+}
+
+/* 消息详情样式 */
+.message-content-detail {
+  padding: 20px;
+  
+  p {
+    white-space: pre-line;
+    line-height: 1.6;
+    margin: 20px 0;
+  }
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+  .message-list-container {
+    height: 50vh;
+  }
+  
+  .message-item {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+  
+  .read-btn {
+    margin-left: 0;
+    margin-top: 8px;
+    align-self: flex-end;
+  }
+}

+ 123 - 18
ai-assisant/src/modules/crm/mobile/page-crm-home/page-crm-home.spec.ts

@@ -1,23 +1,128 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { RouterModule } from '@angular/router';
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { MatDialog } from '@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+import { MatListModule } from '@angular/material/list';
+import { MatIconModule } from '@angular/material/icon';
+import { MatBadgeModule } from '@angular/material/badge';
+import { MatCardModule } from '@angular/material/card';
+import { MatToolbarModule } from '@angular/material/toolbar';
 
-import { PageCrmHome } from './page-crm-home';
+@Component({
+  imports: [
+    RouterModule,
+    CommonModule,
+    MatButtonModule,
+    MatListModule,
+    MatIconModule,
+    MatBadgeModule,
+    MatCardModule,
+    MatToolbarModule
+  ],
+  templateUrl: './page-crm-home.html',
+  styleUrl: './page-crm-home.scss',
+  selector: 'page-crm-home',
+  standalone: true,
+})
+export class PageCrmHome implements OnInit {
+  constructor(private dialog: MatDialog) {}
 
-describe('PageCrmHome', () => {
-  let component: PageCrmHome;
-  let fixture: ComponentFixture<PageCrmHome>;
+  // Font Awesome 图标
+  icons = {
+    faBrain: 'brain', 
+    faBell: 'bell', 
+    faUser: 'user', 
+    faArrowRight: 'arrow-right', 
+    faRobot: 'robot', 
+    faComments: 'comments', 
+    faDatabase: 'database', 
+    faUserTie: 'user-tie', 
+    faTimes: 'times', 
+    faHistory: 'history', 
+    faStar: 'star', 
+    faChartLine: 'chart-line', 
+    faFileAlt: 'file-alt', 
+    faBullhorn: 'bullhorn'
+  };
 
-  beforeEach(async () => {
-    await TestBed.configureTestingModule({
-      imports: [PageCrmHome]
-    })
-    .compileComponents();
+  // 状态变量
+  unreadMessagesCount: number = 3;
 
-    fixture = TestBed.createComponent(PageCrmHome);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
+  // 数据
+  motivationalText: string = '每一次对话都是展现专业的机会,每一次练习都在提升成功的概率!今天,让AI成为您最强大的销售伙伴';
+  stats = {
+    practiceCount: 42,
+    strategyCount: 28,
+    satisfactionRate: 96
+  };
+  
+  features = {
+    virtualPractice: { count: 12, score: 85 },
+    speechStrategy: { count: 8, effectiveness: 92 },
+    dataTraining: { size: '3.2TB', accuracy: 98 },
+    customerProfile: { count: 24 }
+  };
+  
+  userStats = {
+    practiceCount: 12,
+    beatPercentage: 86
+  };
+  
+  user = {
+    name: '张明',
+    role: '高级销售经理'
+  };
+  
+  messages = [
+    { title: '新的陪练挑战', preview: '您有一个新的虚拟陪练挑战等待完成,主题:高端客户价格谈判', time: '10分钟前', unread: true },
+    { title: '话术策略更新', preview: '您收藏的"投诉处理"话术策略已更新至3.2版本', time: '1小时前', unread: true },
+    { title: '陪练成绩通知', preview: '您最近的虚拟陪练成绩为92分,超过89%的销售同事', time: '昨天', unread: false },
+    { title: '团队周报', preview: '本周团队陪练报告已生成,点击查看详细分析', time: '2天前', unread: false },
+    { title: '系统维护通知', preview: '系统将于本周六凌晨2:00-4:00进行维护升级', time: '3天前', unread: false }
+  ];
+  
+  trainings = [
+    { title: '高端客户价格谈判', date: '2023-10-15', difficulty: '⭐⭐⭐', score: 92 },
+    { title: '首次接触技巧', date: '2023-10-12', difficulty: '⭐⭐', score: 85 },
+    { title: '投诉处理模拟', date: '2023-10-08', difficulty: '⭐⭐⭐', score: 88 },
+    { title: '长期合作谈判', date: '2023-10-02', difficulty: '⭐⭐⭐⭐', score: 78 }
+  ];
 
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-});
+  motivationalPhrases = [
+    "每一次对话都是展现专业的机会,每一次练习都在提升成功的概率!今天,让AI成为您最强大的销售伙伴",
+    "每一次拒绝都是离成功更近一步!",
+    "卓越的销售不是推销产品,而是解决问题!",
+    "今天的练习,明天的签约!",
+    "客户的需求是您成功的地图,AI是您的导航仪!",
+    "专业来自准备,成功源于坚持!",
+    "您不是一个人在战斗,AI是您最强大的后援!"
+  ];
+
+  ngOnInit(): void {
+    this.updateMotivationalText();
+    setInterval(() => this.updateMotivationalText(), 10000);
+    setInterval(() => this.updateStats(), 5000);
+  }
+
+  updateMotivationalText(): void {
+    const randomIndex = Math.floor(Math.random() * this.motivationalPhrases.length);
+    this.motivationalText = this.motivationalPhrases[randomIndex];
+  }
+
+  updateStats(): void {
+    this.stats.practiceCount = Math.floor(Math.random() * 20 + 35);
+    this.stats.strategyCount = Math.floor(Math.random() * 15 + 20);
+    this.stats.satisfactionRate = Math.floor(Math.random() * 5 + 94);
+  }
+
+  
+   
+
+    
+  }
+
+ 
+  
+
+  

+ 135 - 77
ai-assisant/src/modules/crm/mobile/page-crm-home/page-crm-home.ts

@@ -3,82 +3,122 @@ import { Component, OnInit } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
 import { 
-  faBrain, 
-  faBell, 
-  faUser, 
-  faArrowRight, 
-  faRobot, 
-  faComments, 
-  faDatabase, 
-  faUserTie, 
-  faTimes, 
-  faHistory, 
-  faStar, 
-  faChartLine, 
-  faFileAlt, 
-  faBullhorn 
+  faBrain, faBell, faUser, faRobot, faComments, 
+  faDatabase, faUserTie, faTimes, faHistory, faStar, 
+  faChartLine, faFileAlt, faBullhorn, faUserCircle,
+  faFire, faTrophy, faCheck
 } from '@fortawesome/free-solid-svg-icons';
+
 @Component({
-  imports: [RouterModule,CommonModule],
+  imports: [RouterModule, CommonModule, FontAwesomeModule],
   templateUrl: './page-crm-home.html',
   styleUrl: './page-crm-home.scss',
-  selector: 'page-crm-home', // 确保选择器匹配HTML中的使用
+  selector: 'page-crm-home',
   standalone: true,
 })
-
-
 export class PageCrmHome implements OnInit {
   // Font Awesome 图标
   icons = {
-    faBrain, faBell, faUser, faArrowRight, faRobot, faComments, 
+    faBrain, faBell, faUser, faRobot, faComments, 
     faDatabase, faUserTie, faTimes, faHistory, faStar, 
-    faChartLine, faFileAlt, faBullhorn
+    faChartLine, faFileAlt, faBullhorn, faUserCircle,
+    faFire, faTrophy, faCheck
   };
 
-  // 状态变量
+  // 弹窗状态
   showMessagePopup: boolean = false;
   showProfilePopup: boolean = false;
-  unreadMessagesCount: number = 3;
+  selectedMessage: any = null;
+  unreadMessagesCount: number = 0;
 
   // 数据
   motivationalText: string = '每一次对话都是展现专业的机会,每一次练习都在提升成功的概率!今天,让AI成为您最强大的销售伙伴';
-  stats = {
-    practiceCount: 42,
-    strategyCount: 28,
-    satisfactionRate: 96
-  };
   
-  features = {
-    virtualPractice: { count: 12, score: 85 },
-    speechStrategy: { count: 8, effectiveness: 92 },
-    dataTraining: { size: '3.2TB', accuracy: 98 },
-    customerProfile: { count: 24 }
-  };
-  
-  userStats = {
-    practiceCount: 12,
-    beatPercentage: 86
-  };
+  features = [
+    {
+      id: 'virtual-practice',
+      title: '虚拟陪练',
+      description: '与AI客户进行真实销售对话练习,提升沟通技巧',
+      icon: 'fas fa-robot',
+      statIcon: 'fas fa-history',
+      stat1: '86次练习',
+      stat2: '92%得分',
+      hasNotification: true
+    },
+    {
+      id: 'speech-strategy',
+      title: '话术决策',
+      description: '智能生成最佳沟通策略,应对各种销售场景',
+      icon: 'fas fa-comments',
+      statIcon: 'fas fa-star',
+      stat1: '34个策略',
+      stat2: '89%有效'
+    },
+    {
+      id: 'data-training',
+      title: '数据训练',
+      description: '上传销售数据训练AI模型,优化决策能力',
+      icon: 'fas fa-database',
+      statIcon: 'fas fa-chart-line',
+      stat1: '1.2GB',
+      stat2: '95%准确'
+    },
+    {
+      id: 'customer-profile',
+      title: '客户画像',
+      description: '生成详细客户分析报告,洞察客户需求',
+      icon: 'fas fa-user-tie',
+      statIcon: 'fas fa-file-alt',
+      stat1: '28份',
+      stat2: '更新中'
+    }
+  ];
+
+  messages = [
+    { 
+      id: 1,
+      title: '新的陪练挑战', 
+      content: '您有一个新的虚拟陪练挑战等待完成,主题:高端客户价格谈判。请尽快完成以获得最佳学习效果。',
+      time: '10分钟前', 
+      unread: true 
+    },
+    { 
+      id: 2,
+      title: '话术策略更新', 
+      content: '您收藏的"投诉处理"话术策略已更新至3.2版本,包含了最新的客户反馈数据。',
+      time: '1小时前', 
+      unread: true 
+    },
+    { 
+      id: 3,
+      title: '系统维护通知', 
+      content: '系统将于本周六凌晨2:00-4:00进行维护升级,期间将无法使用服务。',
+      time: '昨天', 
+      unread: true 
+    },
+    { 
+      id: 4,
+      title: '陪练成绩通知', 
+      content: '您最近的虚拟陪练成绩为92分,超过89%的销售同事,继续保持!',
+      time: '2天前', 
+      unread: true 
+    },
+    { 
+      id: 5,
+      title: '团队周报', 
+      content: '本周团队陪练报告已生成,点击查看详细分析。',
+      time: '3天前', 
+      unread: true 
+    }
+  ];
   
   user = {
     name: '张明',
-    role: '高级销售经理'
+    role: '高级销售经理',
+    age: 32,
+    birthDate: '1991-05-15',
+    trainingCount: 86
   };
-  
-  messages = [
-    { title: '新的陪练挑战', preview: '您有一个新的虚拟陪练挑战等待完成,主题:高端客户价格谈判', time: '10分钟前', unread: true },
-    { title: '话术策略更新', preview: '您收藏的"投诉处理"话术策略已更新至3.2版本', time: '1小时前', unread: true },
-    { title: '陪练成绩通知', preview: '您最近的虚拟陪练成绩为92分,超过89%的销售同事', time: '昨天', unread: false },
-    { title: '团队周报', preview: '本周团队陪练报告已生成,点击查看详细分析', time: '2天前', unread: false },
-    { title: '系统维护通知', preview: '系统将于本周六凌晨2:00-4:00进行维护升级', time: '3天前', unread: false }
-  ];
-  
-  trainings = [
-    { title: '高端客户价格谈判', date: '2023-10-15', difficulty: '⭐⭐⭐', score: 92 },
-    { title: '首次接触技巧', date: '2023-10-12', difficulty: '⭐⭐', score: 85 },
-    { title: '投诉处理模拟', date: '2023-10-08', difficulty: '⭐⭐⭐', score: 88 },
-    { title: '长期合作谈判', date: '2023-10-02', difficulty: '⭐⭐⭐⭐', score: 78 }
-  ];
 
   motivationalPhrases = [
     "每一次对话都是展现专业的机会,每一次练习都在提升成功的概率!今天,让AI成为您最强大的销售伙伴",
@@ -91,14 +131,11 @@ export class PageCrmHome implements OnInit {
   ];
 
   ngOnInit(): void {
-    // 随机选择激励语句
+    this.updateUnreadCount();
     this.updateMotivationalText();
     
     // 每10秒更新一次激励语句
     setInterval(() => this.updateMotivationalText(), 10000);
-    
-    // 每5秒更新统计数据
-    setInterval(() => this.updateStats(), 5000);
   }
 
   updateMotivationalText(): void {
@@ -106,19 +143,10 @@ export class PageCrmHome implements OnInit {
     this.motivationalText = this.motivationalPhrases[randomIndex];
   }
 
-  updateStats(): void {
-    // 模拟数据更新
-    this.stats.practiceCount = Math.floor(Math.random() * 20 + 35);
-    this.stats.strategyCount = Math.floor(Math.random() * 15 + 20);
-    this.stats.satisfactionRate = Math.floor(Math.random() * 5 + 94);
-  }
-
   toggleMessagePopup(): void {
     this.showMessagePopup = !this.showMessagePopup;
     if (this.showMessagePopup) {
-      // 标记消息为已读
-      this.unreadMessagesCount = 0;
-      this.messages.forEach(msg => msg.unread = false);
+      this.updateUnreadCount();
     }
   }
 
@@ -126,19 +154,49 @@ export class PageCrmHome implements OnInit {
     this.showProfilePopup = !this.showProfilePopup;
   }
 
-  closeMessagePopup(event?: MouseEvent): void {
-    if (event && event.target !== event.currentTarget) return;
-    this.showMessagePopup = false;
+  showMessageDetail(message: any, event?: Event): void {
+    if (event) event.stopPropagation();
+    
+    this.selectedMessage = message;
+    
+    // 如果消息未读,则标记为已读
+    if (message.unread) {
+      message.unread = false;
+      this.updateUnreadCount();
+    }
+  }
+
+  closeMessageDetail(): void {
+    this.selectedMessage = null;
+  }
+
+  markMessageAsRead(message: any, event: Event): void {
+    event.stopPropagation();
+    if (message.unread) {
+      message.unread = false;
+      this.updateUnreadCount();
+    }
+  }
+
+  markAllAsRead(): void {
+    this.messages.forEach(message => message.unread = false);
+    this.updateUnreadCount();
+  }
+
+  markAsRead(message: any): void {
+    if (message.unread) {
+      message.unread = false;
+      this.updateUnreadCount();
+    }
   }
 
-  closeProfilePopup(event?: MouseEvent): void {
-    if (event && event.target !== event.currentTarget) return;
-    this.showProfilePopup = false;
+  updateUnreadCount(): void {
+    this.unreadMessagesCount = this.messages.filter(m => m.unread).length;
   }
 
-  navigateTo(feature: string): void {
-    console.log(`Navigating to ${feature}`);
+  navigateTo(featureId: string): void {
+    console.log('导航至:', featureId);
     // 实际项目中这里会有路由导航逻辑
-    // this.router.navigate([`/${feature}`]);
+    // this.router.navigate([`/${featureId}`]);
   }
 }

+ 130 - 10
ai-assisant/src/modules/crm/mobile/page-crm-image/page-crm-image.html

@@ -7,8 +7,8 @@
     </div>
   </div>
 
-  <!-- 第一部分:分析输入区 -->
-  <div class="analysis-input animated-card">
+  <!-- 分析输入区 -->
+  <div class="analysis-input">
     <div class="input-group">
       <label for="client-id">客户编号</label>
       <input type="text" id="client-id" [(ngModel)]="clientId" placeholder="请输入客户编号">
@@ -25,16 +25,16 @@
     </div>
     
     <div class="action-buttons">
-      <button class="btn btn-secondary" (click)="save()">
+      <button class="btn btn-blue" (click)="save()">
         <i class="fas fa-save"></i> 保存
       </button>
-      <button class="btn btn-primary" (click)="analyze()">
+      <button class="btn btn-blue" (click)="analyze()">
         <i class="fas fa-play"></i> 开始分析
       </button>
     </div>
   </div>
 
-  <!-- 第二部分:客户分析列表 -->
+  <!-- 客户分析列表 -->
   <div class="client-list-section">
     <div class="section-header">
       <h2 class="section-title">客户分析列表</h2>
@@ -46,8 +46,7 @@
     </div>
     
     <div class="client-list">
-      <!-- 客户项 -->
-      <div class="client-item animated-card" *ngFor="let client of clients" (click)="onClientClick(client)">
+      <div class="client-item" *ngFor="let client of clients" (click)="onClientClick(client)">
         <div class="client-info">
           <div class="client-avatar">{{ client.name.charAt(0) }}</div>
           <div>
@@ -60,15 +59,14 @@
     </div>
   </div>
 
-  <!-- 第三部分:智能推荐区 -->
+  <!-- 智能推荐区 -->
   <div class="recommendation-section">
     <div class="section-header">
       <h2 class="section-title">智能推荐</h2>
     </div>
     
     <div class="recommendation-cards">
-      <!-- 推荐卡片 -->
-      <div class="recommendation-card animated-card" *ngFor="let rec of recommendations">
+      <div class="recommendation-card" *ngFor="let rec of recommendations">
         <div class="card-header">
           <div class="card-icon" [style.background]="rec.iconColor" [style.color]="rec.iconTextColor">
             <i [class]="rec.icon"></i>
@@ -85,4 +83,126 @@
       </div>
     </div>
   </div>
+
+  <!-- 一级弹窗:基本信息 -->
+  <div class="report-modal" *ngIf="showBaseReportModal">
+    <div class="modal-content">
+      <div class="modal-header">
+        <button class="close-btn" (click)="closeModal()">
+          <i class="fas fa-times"></i>
+        </button>
+        <h2>客户画像分析报告</h2>
+      </div>
+      <div class="modal-body">
+        <div class="form-group">
+          <label for="report-name">名称</label>
+          <input type="text" id="report-name" [(ngModel)]="reportName" placeholder="请输入报告名称">
+        </div>
+        
+        <div class="info-item">
+          <i class="fas fa-calendar-alt"></i>
+          <span>日期时间:{{ currentDate | date:'yyyy-MM-dd HH:mm' }}</span>
+        </div>
+        
+        <div class="info-item">
+          <i class="fas fa-user-tag"></i>
+          <span>客户类型:{{ clientType || '未设置' }}</span>
+        </div>
+        
+        <div class="clickable-item" (click)="openChatDetailModal()">
+          <i class="fas fa-comments"></i>
+          <span>聊天记录文本</span>
+          <i class="fas fa-chevron-right"></i>
+        </div>
+        
+        <div class="clickable-item" (click)="openAnalysisResultModal()">
+          <i class="fas fa-chart-pie"></i>
+          <span>分析结果</span>
+          <i class="fas fa-chevron-right"></i>
+        </div>
+        
+        <div class="clickable-item" (click)="openStrategyModal()">
+          <i class="fas fa-lightbulb"></i>
+          <span>推荐策略</span>
+          <i class="fas fa-chevron-right"></i>
+        </div>
+      </div>
+      <div class="modal-footer">
+        <button class="btn btn-confirm" (click)="closeModal()">确定</button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 二级弹窗:聊天记录详情 -->
+  <div class="report-modal" *ngIf="showChatDetailModal">
+    <div class="modal-content">
+      <div class="modal-header">
+        <button class="back-btn" (click)="backToBaseModal()">
+          <i class="fas fa-arrow-left"></i> 返回
+        </button>
+        <h2>聊天记录详情</h2>
+        <button class="close-btn" (click)="closeModal()">
+          <i class="fas fa-times"></i>
+        </button>
+      </div>
+      <div class="modal-body">
+        <div class="info-item">
+          <i class="fas fa-calendar-alt"></i>
+          <span>日期时间:{{ chatDetail.date || '未记录' }}</span>
+        </div>
+        <div class="text-content">
+          {{ chatDetail.content || '暂无聊天内容' }}
+        </div>
+      </div>
+      <div class="modal-footer">
+        <button class="btn btn-confirm" (click)="closeModal()">确定</button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 二级弹窗:分析结果 -->
+  <div class="report-modal" *ngIf="showAnalysisResultModal">
+    <div class="modal-content">
+      <div class="modal-header">
+        <button class="back-btn" (click)="backToBaseModal()">
+          <i class="fas fa-arrow-left"></i> 返回
+        </button>
+        <h2>分析结果</h2>
+        <button class="close-btn" (click)="closeModal()">
+          <i class="fas fa-times"></i>
+        </button>
+      </div>
+      <div class="modal-body">
+        <div class="analysis-grid">
+          <div class="analysis-item">
+            <i class="fas fa-venus-mars"></i>
+            <span>性别:{{ analysisResult.gender || '未知' }}</span>
+          </div>
+          <div class="analysis-item">
+            <i class="fas fa-birthday-cake"></i>
+            <span>年龄:{{ analysisResult.age || '未知' }}</span>
+          </div>
+          <div class="analysis-item">
+            <i class="fas fa-briefcase"></i>
+            <span>职业:{{ analysisResult.job || '未知' }}</span>
+          </div>
+          <div class="analysis-item">
+            <i class="fas fa-brain"></i>
+            <span>心理特征:{{ analysisResult.psychology || '未分析' }}</span>
+          </div>
+          <div class="analysis-item">
+            <i class="fas fa-shopping-cart"></i>
+            <span>消费习惯:{{ analysisResult.consumption || '未分析' }}</span>
+          </div>
+          <div class="analysis-item">
+            <i class="fas fa-lightbulb"></i>
+            <span>需求信息:{{ analysisResult.needs || '未分析' }}</span>
+          </div>
+        </div>
+      </div>
+      <div class="modal-footer">
+        <button class="btn btn-confirm" (click)="closeModal()">确定</button>
+      </div>
+    </div>
+  </div>
 </div>

+ 477 - 231
ai-assisant/src/modules/crm/mobile/page-crm-image/page-crm-image.scss

@@ -1,376 +1,622 @@
-/* 保持原有的CSS样式不变,直接复制过来 */
 * {
-    margin: 0;
-    padding: 0;
-    box-sizing: border-box;
-    font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
 }
 
 :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;
-    --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
+  --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;
 }
 
 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);
+  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);
 }
 
 .app-container {
-    padding: 0 16px 40px;
-    position: relative;
+  padding: 0 16px 40px;
+  position: relative;
 }
 
 /* 头部导航 */
 .header {
-    padding: 20px 0;
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    position: sticky;
-    top: 0;
-    background: var(--bg-white);
-    z-index: 100;
-    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
+  padding: 20px 0;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
 }
 
 .header-title {
-    font-size: 22px;
-    font-weight: 700;
-    color: var(--text-dark);
-    display: flex;
-    align-items: center;
-    gap: 10px;
+  font-size: 22px;
+  font-weight: 700;
+  color: var(--text-dark);
+  display: flex;
+  align-items: center;
+  gap: 10px;
 }
 
 .header-title i {
-    color: var(--primary);
-    font-size: 24px;
-    background: rgba(74, 143, 231, 0.1);
-    width: 40px;
-    height: 40px;
-    border-radius: 12px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
+  color: var(--primary);
+  font-size: 24px;
+  background: rgba(74, 143, 231, 0.1);
+  width: 40px;
+  height: 40px;
+  border-radius: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }
 
 /* 分析输入区 */
 .analysis-input {
-    background: var(--bg-white);
-    border-radius: var(--border-radius);
-    box-shadow: var(--card-shadow);
-    padding: 20px;
-    margin-bottom: 24px;
-    border: 1px solid rgba(0, 0, 0, 0.03);
+  background: var(--bg-white);
+  border-radius: var(--border-radius);
+  box-shadow: var(--card-shadow);
+  padding: 20px;
+  margin-bottom: 24px;
+  border: 1px solid rgba(0, 0, 0, 0.03);
 }
 
 .input-group {
-    margin-bottom: 16px;
+  margin-bottom: 16px;
 }
 
 .input-group label {
-    display: block;
-    margin-bottom: 8px;
-    font-weight: 600;
-    color: var(--text-dark);
-    font-size: 14px;
+  display: block;
+  margin-bottom: 8px;
+  font-weight: 600;
+  color: var(--text-dark);
+  font-size: 14px;
 }
 
 .input-group input,
 .input-group textarea {
-    width: 100%;
-    padding: 12px 16px;
-    border-radius: var(--border-radius);
-    border: 1px solid #e0e4e8;
-    font-size: 14px;
-    transition: var(--transition);
+  width: 100%;
+  padding: 12px 16px;
+  border-radius: var(--border-radius);
+  border: 1px solid #e0e4e8;
+  font-size: 14px;
 }
 
 .input-group input:focus,
 .input-group textarea:focus {
-    outline: none;
-    border-color: var(--primary);
-    box-shadow: 0 0 0 3px rgba(74, 143, 231, 0.2);
+  outline: none;
+  border-color: var(--primary);
+  box-shadow: 0 0 0 3px rgba(74, 143, 231, 0.2);
 }
 
 .input-group textarea {
-    min-height: 120px;
-    resize: vertical;
+  min-height: 120px;
+  resize: vertical;
 }
 
 .action-buttons {
-    display: flex;
-    gap: 12px;
-    margin-top: 20px;
+  display: flex;
+  gap: 12px;
+  margin-top: 20px;
 }
 
 .btn {
-    padding: 12px 20px;
-    border-radius: var(--border-radius);
-    font-size: 14px;
-    font-weight: 600;
-    border: none;
-    cursor: pointer;
-    transition: var(--transition);
-    flex: 1;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    gap: 8px;
+  padding: 12px 20px;
+  border-radius: var(--border-radius);
+  font-size: 14px;
+  font-weight: 600;
+  border: none;
+  cursor: pointer;
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
 }
 
 .btn-primary {
-    background: var(--primary);
-    color: white;
-    box-shadow: 0 4px 8px rgba(74, 143, 231, 0.2);
-}
-
-.btn-primary:hover {
-    background: #3a7bd5;
-    transform: translateY(-2px);
-    box-shadow: 0 6px 12px rgba(74, 143, 231, 0.3);
+  background: var(--primary);
+  color: var(--text-dark);
 }
 
 .btn-secondary {
-    background: var(--bg-light);
-    color: var(--text-medium);
-}
-
-.btn-secondary:hover {
-    background: #e1ebfa;
-    transform: translateY(-2px);
+  background: var(--primary);
+  color: var(--text-dark);
+  opacity: 0.8;
 }
 
 /* 客户列表区 */
 .client-list-section {
-    margin-bottom: 24px;
+  margin-bottom: 24px;
 }
 
 .section-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 16px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
 }
 
 .section-title {
-    font-size: 18px;
-    font-weight: 700;
-    color: var(--text-dark);
-    position: relative;
-    padding-left: 12px;
+  font-size: 18px;
+  font-weight: 700;
+  color: var(--text-dark);
+  position: relative;
+  padding-left: 12px;
 }
 
 .section-title:before {
-    content: '';
-    position: absolute;
-    left: 0;
-    top: 4px;
-    height: 16px;
-    width: 4px;
-    background: var(--primary);
-    border-radius: 2px;
+  content: '';
+  position: absolute;
+  left: 0;
+  top: 4px;
+  height: 16px;
+  width: 4px;
+  background: var(--primary);
+  border-radius: 2px;
 }
 
 .search-box {
-    position: relative;
-    margin-bottom: 16px;
+  position: relative;
+  margin-bottom: 16px;
 }
 
 .search-box input {
-    width: 100%;
-    padding: 12px 16px 12px 42px;
-    border-radius: var(--border-radius);
-    border: 1px solid #e0e4e8;
-    font-size: 14px;
-    transition: var(--transition);
+  width: 100%;
+  padding: 12px 16px 12px 42px;
+  border-radius: var(--border-radius);
+  border: 1px solid #e0e4e8;
+  font-size: 14px;
 }
 
 .search-box input:focus {
-    outline: none;
-    border-color: var(--primary);
-    box-shadow: 0 0 0 3px rgba(74, 143, 231, 0.2);
+  outline: none;
+  border-color: var(--primary);
+  box-shadow: 0 0 0 3px rgba(74, 143, 231, 0.2);
 }
 
 .search-box i {
-    position: absolute;
-    left: 16px;
-    top: 50%;
-    transform: translateY(-50%);
-    color: var(--text-light);
+  position: absolute;
+  left: 16px;
+  top: 50%;
+  transform: translateY(-50%);
+  color: var(--text-light);
 }
 
 .client-list {
-    background: var(--bg-white);
-    border-radius: var(--border-radius);
-    box-shadow: var(--card-shadow);
-    overflow: hidden;
-    border: 1px solid rgba(0, 0, 0, 0.03);
+  background: var(--bg-white);
+  border-radius: var(--border-radius);
+  box-shadow: var(--card-shadow);
+  overflow: hidden;
+  border: 1px solid rgba(0, 0, 0, 0.03);
 }
 
 .client-item {
-    padding: 16px;
-    border-bottom: 1px solid #f0f2f5;
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    transition: var(--transition);
-    cursor: pointer;
+  padding: 16px;
+  border-bottom: 1px solid #f0f2f5;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  cursor: pointer;
 }
 
 .client-item:last-child {
-    border-bottom: none;
+  border-bottom: none;
 }
 
 .client-item:hover {
-    background: #f9fbfe;
+  background: #f9fbfe;
 }
 
 .client-info {
-    display: flex;
-    align-items: center;
-    gap: 12px;
+  display: flex;
+  align-items: center;
+  gap: 12px;
 }
 
 .client-avatar {
-    width: 40px;
-    height: 40px;
-    border-radius: 50%;
-    background: linear-gradient(135deg, #4a8fe7, #5e72e4);
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    color: white;
-    font-weight: bold;
-    font-size: 16px;
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #4a8fe7, #5e72e4);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-weight: bold;
+  font-size: 16px;
 }
 
 .client-name {
-    font-weight: 600;
-    color: var(--text-dark);
-    margin-bottom: 2px;
+  font-weight: 600;
+  color: var(--text-dark);
+  margin-bottom: 2px;
 }
 
 .client-meta {
-    font-size: 12px;
-    color: var(--text-light);
+  font-size: 12px;
+  color: var(--text-light);
 }
 
 .client-date {
-    font-size: 12px;
-    color: var(--text-light);
+  font-size: 12px;
+  color: var(--text-light);
 }
 
 /* 智能推荐区 */
 .recommendation-section {
-    margin-top: 24px;
+  margin-top: 24px;
 }
 
 .recommendation-cards {
-    display: flex;
-    flex-direction: column;
-    gap: 16px;
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
 }
 
 .recommendation-card {
-    background: var(--bg-white);
-    border-radius: var(--border-radius);
-    box-shadow: var(--card-shadow);
-    padding: 20px;
-    position: relative;
-    border: 1px solid rgba(0, 0, 0, 0.03);
+  background: var(--bg-white);
+  border-radius: var(--border-radius);
+  box-shadow: var(--card-shadow);
+  padding: 20px;
+  position: relative;
+  border: 1px solid rgba(0, 0, 0, 0.03);
 }
 
 .card-header {
-    display: flex;
-    align-items: center;
-    gap: 12px;
-    margin-bottom: 16px;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 16px;
 }
 
 .card-icon {
-    width: 42px;
-    height: 42px;
-    border-radius: 12px;
-    background: rgba(74, 143, 231, 0.15);
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    color: var(--primary);
-    font-size: 20px;
+  width: 42px;
+  height: 42px;
+  border-radius: 12px;
+  background: rgba(74, 143, 231, 0.15);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: var(--primary);
+  font-size: 20px;
 }
 
 .card-title {
-    font-size: 16px;
-    font-weight: 700;
-    color: var(--text-dark);
+  font-size: 16px;
+  font-weight: 700;
+  color: var(--text-dark);
 }
 
 .recommendation-content {
-    color: var(--text-medium);
-    font-size: 14px;
-    line-height: 1.6;
-    margin-bottom: 16px;
+  color: var(--text-medium);
+  font-size: 14px;
+  line-height: 1.6;
+  margin-bottom: 16px;
 }
 
 .recommendation-action {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
 }
 
 .tag {
-    padding: 6px 12px;
-    border-radius: 20px;
-    background: var(--bg-light);
-    font-size: 12px;
-    color: var(--text-medium);
-    font-weight: 500;
+  padding: 6px 12px;
+  border-radius: 20px;
+  background: var(--bg-light);
+  font-size: 12px;
+  color: var(--text-medium);
+  font-weight: 500;
 }
 
 .action-btn {
-    padding: 8px 16px;
-    border-radius: 30px;
-    background: var(--primary);
-    color: white;
-    font-size: 14px;
+  padding: 8px 16px;
+  border-radius: 30px;
+  background: var(--primary);
+  color: white;
+  font-size: 14px;
+  font-weight: 600;
+  border: none;
+  cursor: pointer;
+}
+
+/* 弹窗样式 */
+.report-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+  padding: 20px;
+}
+
+.modal-content {
+  background: white;
+  border-radius: var(--border-radius);
+  width: 100%;
+  max-width: 480px;
+  max-height: 90vh;
+  overflow-y: auto;
+  box-shadow: var(--card-shadow);
+}
+
+.modal-header {
+  padding: 20px;
+  border-bottom: 1px solid #eee;
+  text-align: center;
+  position: relative;
+  
+  h2 {
+    color: var(--primary);
+    font-size: 20px;
+    margin: 0;
+  }
+}
+
+.back-btn {
+  position: absolute;
+  left: 20px;
+  top: 50%;
+  transform: translateY(-50%);
+  background: none;
+  border: none;
+  font-size: 16px;
+  color: var(--primary);
+  cursor: pointer;
+}
+
+.modal-body {
+  padding: 20px;
+}
+
+.form-group {
+  margin-bottom: 20px;
+  
+  label {
+    display: block;
+    margin-bottom: 8px;
     font-weight: 600;
-    border: none;
-    cursor: pointer;
-    transition: var(--transition);
-    box-shadow: 0 4px 8px rgba(74, 143, 231, 0.2);
+    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;
+  }
+}
+
+.info-item {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 12px 0;
+  border-bottom: 1px solid #f5f5f5;
+  
+  i {
+    color: var(--primary);
+    width: 24px;
+    text-align: center;
+  }
+}
+
+.clickable-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  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;
+  }
+}
+
+.text-content {
+  padding: 15px;
+  background: #f9f9f9;
+  border-radius: 8px;
+  margin-top: 15px;
+  line-height: 1.6;
+}
+
+.analysis-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 12px;
+  margin-top: 15px;
+}
+
+.analysis-item {
+  background: #f5f7fa;
+  padding: 12px;
+  border-radius: 8px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  
+  i {
+    color: var(--primary);
+  }
+}
+
+.modal-footer {
+  padding: 20px;
+  text-align: center;
+  border-top: 1px solid #eee;
 }
 
-.action-btn:hover {
+.btn-confirm {
+  background: var(--primary);
+  color: white;
+  padding: 12px 24px;
+  border-radius: 30px;
+  border: none;
+  font-weight: 600;
+  cursor: pointer;
+}
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
+}
+
+: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;
+}
+
+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);
+}
+
+.app-container {
+  padding: 0 16px 40px;
+  position: relative;
+}
+
+/* 按钮样式 */
+.btn {
+  padding: 12px 20px;
+  border-radius: var(--border-radius);
+  font-size: 14px;
+  font-weight: 600;
+  border: none;
+  cursor: pointer;
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  transition: all 0.2s ease;
+}
+
+.btn-blue {
+  background: var(--primary);
+  color: white;
+  
+  &:hover {
     background: #3a7bd5;
-    transform: translateY(-2px);
-    box-shadow: 0 6px 12px rgba(74, 143, 231, 0.3);
+    box-shadow: 0 4px 8px rgba(74, 143, 231, 0.3);
+  }
 }
 
-/* 动画效果 */
-@keyframes fadeIn {
-    from { opacity: 0; transform: translateY(20px); }
-    to { opacity: 1; transform: translateY(0); }
+.action-btn {
+  padding: 8px 16px;
+  border-radius: 30px;
+  background: var(--primary);
+  color: white;
+  font-size: 14px;
+  font-weight: 600;
+  border: none;
+  cursor: pointer;
+}
+
+.btn-confirm {
+  @extend .btn-blue;
+  padding: 12px 24px;
+}
+
+/* 弹窗导航按钮 */
+.back-btn {
+  background: none;
+  border: none;
+  color: var(--primary);
+  font-size: 14px;
+  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 {
+  background: none;
+  border: none;
+  color: var(--text-light);
+  font-size: 16px;
+  cursor: pointer;
+  padding: 5px;
+  
+  &:hover {
+    color: var(--primary);
+  }
 }
 
-.animated-card {
-    animation: fadeIn 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards;
+/* 弹窗头部布局 */
+.modal-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 15px 20px;
+  position: relative;
+  border-bottom: 1px solid #eee;
+
+  h2 {
+    margin: 0 auto;
+    padding: 0 30px;
+    font-size: 18px;
+    color: var(--text-dark);
+  }
 }

+ 79 - 37
ai-assisant/src/modules/crm/mobile/page-crm-image/page-crm-image.ts

@@ -10,37 +10,49 @@ import { FormsModule } from '@angular/forms';
   styleUrls: ['./page-crm-image.scss']
 })
 export class PageCrmImage {
+  // 表单数据
   clientId = '';
   clientName = '';
   chatData = '';
-
+  searchTerm = '';
+  reportName = '';
+  
+  // 弹窗控制
+  showBaseReportModal = false;
+  showChatDetailModal = false;
+  showAnalysisResultModal = false;
+  
+  // 当前日期
+  currentDate = new Date();
+  
+  // 客户数据
+  clientType = '企业客户';
+  
+  // 聊天记录详情
+  chatDetail = {
+    date: '2023-07-15 14:30',
+    content: '客户表示对新产品很感兴趣,询问了价格和功能细节,希望下周能安排演示。'
+  };
+  
+  // 分析结果
+  analysisResult = {
+    gender: '男',
+    age: '35-40岁',
+    job: 'IT经理',
+    psychology: '理性决策型',
+    consumption: '注重性价比',
+    needs: '需要高效解决方案'
+  };
+  
+  // 客户列表
   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'
-    }
+    { 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' }
   ];
 
+  // 推荐数据
   recommendations = [
     {
       icon: 'fas fa-user-friends',
@@ -62,27 +74,57 @@ export class PageCrmImage {
     }
   ];
 
-  searchTerm = '';
-
-  onClientClick(client: any) {
-    alert(`进入客户详情页:${client.name}`);
-  }
-
-  analyze() {
+  // 保存分析
+  save() {
     if (!this.clientId || !this.clientName || !this.chatData) {
       alert('请填写完整的客户信息和聊天记录');
       return;
     }
-    
-    alert(`开始分析客户 ${this.clientName} (${this.clientId}) 的聊天记录...`);
+    this.showBaseReportModal = true;
   }
 
-  save() {
+  // 开始分析
+  analyze() {
     if (!this.clientId || !this.clientName || !this.chatData) {
       alert('请填写完整的客户信息和聊天记录');
       return;
     }
-    
-    alert(`已保存客户 ${this.clientName} (${this.clientId}) 的信息和聊天记录`);
+    alert(`开始分析客户 ${this.clientName} (${this.clientId}) 的聊天记录...`);
+  }
+
+  // 打开聊天详情弹窗
+  openChatDetailModal() {
+    this.showBaseReportModal = false;
+    this.showChatDetailModal = true;
+  }
+
+  // 打开分析结果弹窗
+  openAnalysisResultModal() {
+    this.showBaseReportModal = false;
+    this.showAnalysisResultModal = true;
+  }
+
+  // 打开推荐策略弹窗
+  openStrategyModal() {
+    alert('推荐策略功能开发中...');
+  }
+
+  // 返回基础弹窗
+  backToBaseModal() {
+    this.showChatDetailModal = false;
+    this.showAnalysisResultModal = false;
+    this.showBaseReportModal = true;
+  }
+
+  // 关闭所有弹窗
+  closeModal() {
+    this.showBaseReportModal = false;
+    this.showChatDetailModal = false;
+    this.showAnalysisResultModal = false;
+  }
+
+  // 客户点击事件
+  onClientClick(client: any) {
+    alert(`进入客户详情页:${client.name}`);
   }
 }

+ 272 - 170
ai-assisant/src/modules/crm/mobile/page-crm-training/page-crm-training.html

@@ -1,225 +1,327 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-<head>
-  <meta charset="UTF-8">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>虚拟陪练舱</title>
-</head>
-<body>
-  <div class="container">
-    <!-- 头部导航 -->
-    <header class="header">
-      <div class="nav-top">
-        <button class="back-btn">
-          <i class="icon-arrow-left"></i>
-        </button>
-        <h1 class="page-title">虚拟陪练舱</h1>
-        <div class="spacer"></div>
-      </div>
-    </header>
-
-    <!-- 场景选择卡 -->
-    <section class="section scenario-section">
-      <div class="section-title">
-        <div>
-          <i class="icon-layers"></i>
-          选择陪练场景
-        </div>
-        <button class="edit-btn" id="editTypesBtn">
-          <i class="icon-edit"></i>
-          <span id="editText">编辑分类</span>
-        </button>
-      </div>
-      
-      <div class="difficulty-tabs">
-        <div class="tab active" data-difficulty="easy">初级</div>
-        <div class="tab" data-difficulty="medium">中级</div>
-        <div class="tab" data-difficulty="hard">高级</div>
+<div class="training-container">
+  <!-- 头部导航 -->
+  <header class="header">
+    <div class="nav-top">
+      <button class="back-btn" (click)="goBack()" [disabled]="!canGoBack()">
+        <i class="fas fa-arrow-left"></i>
+      </button>
+      <h1 class="page-title">虚拟陪练舱</h1>
+      <div></div>
+    </div>
+  </header>
+  
+  <!-- 场景选择卡 -->
+  <section class="section-card">
+    <div class="section-title">
+      <div>
+        <i class="fas fa-layer-group"></i>
+        选择陪练场景
       </div>
-      
-      <div class="customer-bubbles">
-        <div class="bubble active">
-          商务客户
-          <button class="delete-btn hidden">
-            <i class="icon-close"></i>
-          </button>
+      <button class="edit-btn" (click)="toggleEditMode()">
+        <i class="fas" [ngClass]="{'fa-edit': !isEditing, 'fa-check': isEditing}"></i>
+        {{isEditing ? '完成编辑' : '编辑分类'}}
+      </button>
+    </div>
+    
+    <div class="difficulty-tabs">
+      @for (tab of difficultyTabs; track tab.label) {
+        <div class="tab" 
+             [class.active]="tab.active"
+             (click)="selectTab(tab)">
+          {{tab.label}}
         </div>
-        <div class="bubble">
-          老年客户
-          <button class="delete-btn hidden">
-            <i class="icon-close"></i>
-          </button>
+      }
+    </div>
+    
+    <div class="customer-bubbles">
+      @for (type of customerTypes; track type.id) {
+        <div class="bubble" 
+             [class.active]="type.active"
+             (click)="selectCustomerType(type)">
+          {{type.name}}
+          @if (isEditing) {
+            <button class="delete-btn" (click)="deleteCustomerType(type); $event.stopPropagation()">
+              <i class="fas fa-times"></i>
+            </button>
+          }
         </div>
-        <div class="add-bubble" id="addCustomerType">
-          <i class="icon-plus"></i>
+      }
+      @if (isEditing) {
+        <div class="add-bubble" (click)="openAddCustomerModal()">
+          <i class="fas fa-plus"></i>
           添加类型
         </div>
-      </div>
-    </section>
-
-    <!-- 对话主界面 -->
-    <section class="section conversation-section">
+      }
+    </div>
+    
+    @if (!currentScenario && customerTypes.length > 0) {
+      <button class="start-btn" (click)="startTraining()">
+        <i class="fas fa-play"></i>
+        开始训练
+      </button>
+    }
+  </section>
+  
+  <!-- 对话主界面 -->
+  @if (currentScenario) {
+    <section class="conversation-section">
       <div class="avatar-container">
         <div class="customer-avatar">
-          <i class="icon-user"></i>
+          <i class="fas" [ngClass]="getCustomerIcon()"></i>
         </div>
       </div>
       
-      <div class="conversation-container" id="messageContainer">
-        <div class="message customer-message">
-          您好,我对贵公司的产品很感兴趣,但觉得价格有点高。
-        </div>
-        <div class="message user-message">
-          感谢您的关注,我们的产品采用优质材料,这个价格已经很有竞争力了。
-        </div>
+      <div class="conversation-container" #conversationContainer>
+        @for (message of messages; track $index) {
+          <div class="message" 
+               [class.customer-message]="message.isCustomer"
+               [class.user-message]="!message.isCustomer">
+            {{message.text}}
+          </div>
+        }
       </div>
       
       <div class="input-area">
-        <button class="voice-btn" id="voiceBtn">
-          <i class="icon-mic"></i>
+        <button class="voice-btn" 
+                [class.active]="isRecording"
+                (click)="toggleRecording()">
+          <i class="fas" [ngClass]="{'fa-microphone': !isRecording, 'fa-square': isRecording}"></i>
         </button>
-        <input type="text" class="text-input" placeholder="输入回复内容..." id="messageInput">
-        <button class="send-btn" id="sendBtn">
-          <i class="icon-send"></i>
+        <input type="text" 
+               class="text-input" 
+               placeholder="输入回复内容..."
+               [(ngModel)]="userInput"
+               (keyup.enter)="sendMessage()">
+        <button class="send-btn" (click)="sendMessage()" [disabled]="!userInput.trim()">
+          <i class="fas fa-paper-plane"></i>
         </button>
       </div>
     </section>
-
+    
     <!-- 能力仪表盘 -->
-    <section class="section dashboard-section">
+    <section class="dashboard">
       <h2 class="section-title">
-        <i class="icon-chart"></i>
+        <i class="fas fa-chart-line"></i>
         能力评估
       </h2>
       
       <div class="progress-container">
         <svg class="progress-circle" viewBox="0 0 180 180">
-          <circle cx="90" cy="90" r="80" class="progress-bg"></circle>
-          <circle cx="90" cy="90" r="80" class="progress-fill" 
-                  stroke-dasharray="502" stroke-dashoffset="70"></circle>
+          <circle cx="90" cy="90" r="80" stroke="#e2e8f0" stroke-width="10" fill="none" />
+          <circle cx="90" cy="90" r="80" stroke="#4285f4" stroke-width="10" fill="none" 
+                  [attr.stroke-dasharray]="502" [attr.stroke-dashoffset]="getProgressOffset()" stroke-linecap="round" />
         </svg>
-        <div class="progress-value">86</div>
+        <div class="progress-value">{{currentScore}}</div>
       </div>
       
       <div class="skills-container">
-        <div class="skill">
-          <div class="skill-name">沟通能力</div>
-          <div class="skill-progress">
-            <div class="skill-progress-fill" style="width: 85%"></div>
-          </div>
-        </div>
-        <div class="skill">
-          <div class="skill-name">专业知识</div>
-          <div class="skill-progress">
-            <div class="skill-progress-fill" style="width: 92%"></div>
-          </div>
-        </div>
-        <div class="skill">
-          <div class="skill-name">反应速度</div>
-          <div class="skill-progress">
-            <div class="skill-progress-fill" style="width: 78%"></div>
+        @for (skill of skills; track skill.name) {
+          <div class="skill">
+            <div class="skill-name">{{skill.name}}</div>
+            <div class="skill-progress">
+              <div class="skill-progress-fill" 
+                   [style.width.%]="skill.value" 
+                   [style.background]="getSkillColor(skill.value)"></div>
+            </div>
           </div>
-        </div>
+        }
       </div>
     </section>
-
+    
     <!-- 存档面板 -->
-    <section class="section report-section">
-      <div class="report-header">
-        <h3 class="report-title">本次陪练报告</h3>
-        <div class="comparison">
-          <i class="icon-arrow-up"></i>
-          <span>12%</span>
+    @if (messages.length >= 8) {
+      <section class="report-card">
+        <div class="report-header">
+          <h3 class="report-title">本次陪练报告</h3>
+          <div class="comparison" [class.down]="scoreChange < 0">
+            <i class="fas" [ngClass]="{'fa-arrow-up': scoreChange >= 0, 'fa-arrow-down': scoreChange < 0}"></i>
+            <span>{{Math.abs(scoreChange)}}%</span>
+          </div>
         </div>
-      </div>
-      
-      <p class="report-summary">
-        本次与商务客户的对话中,您展现了优秀的专业知识和反应能力。建议在价格谈判环节采用更积极的策略,成功率可提升15%。
-      </p>
-      
-      <div class="tags-container">
-        <div class="tag">商务场景</div>
-        <div class="tag">价格谈判</div>
-        <div class="tag">提升空间</div>
-      </div>
-      
-      <div class="actions">
-        <button class="action-btn view-btn" id="viewReportBtn">
-          <i class="icon-file"></i>
-          查看完整报告
+        
+        <p class="report-summary">
+          {{currentReport.summary}}
+        </p>
+        
+        <div class="tags-container">
+          @for (tag of currentReport.tags; track tag) {
+            <div class="tag">{{tag}}</div>
+          }
+        </div>
+        
+        <div class="actions">
+          <button class="action-btn view-btn" (click)="openFullReportModal()">
+            <i class="fas fa-file-alt"></i>
+            查看完整报告
+          </button>
+          <button class="action-btn history-btn" (click)="openHistoryReportModal()">
+            <i class="fas fa-history"></i>
+            历史报告
+          </button>
+        </div>
+      </section>
+    }
+  }
+</div>
+
+<!-- 添加客户类型弹窗 -->
+@if (showAddCustomerModal) {
+  <div class="modal-overlay active">
+    <div class="modal">
+      <div class="modal-header">
+        <button class="back-btn" (click)="closeAddCustomerModal()">
+          <i class="fas fa-arrow-left"></i>
         </button>
-        <button class="action-btn history-btn" id="historyReportBtn">
-          <i class="icon-history"></i>
-          历史报告
+        <h3 class="modal-title">添加客户类型</h3>
+        <button class="close-modal" (click)="closeAddCustomerModal()">
+          <i class="fas fa-times"></i>
         </button>
       </div>
-    </section>
-
-    <!-- 添加客户类型弹窗 -->
-    <div class="modal-overlay" id="addCustomerModal">
-      <div class="modal">
-        <div class="modal-header">
-          <h3 class="modal-title">添加客户类型</h3>
-          <button class="close-modal" id="closeAddCustomerModal">
-            <i class="icon-close"></i>
-          </button>
+      <div class="modal-content">
+        <div class="form-group">
+          <label for="customerType">客户类型名称</label>
+          <input type="text" id="customerType" placeholder="例如:新婚客户" [(ngModel)]="newCustomerTypeName">
         </div>
-        <div class="modal-content">
-          <div class="form-group">
-            <label for="customerType">客户类型名称</label>
-            <input type="text" id="customerType" placeholder="例如:新婚客户">
-          </div>
-        </div>
-        <div class="modal-actions">
-          <button class="modal-btn cancel-btn" id="cancelAddCustomer">取消</button>
-          <button class="modal-btn save-btn" id="saveCustomerType">保存</button>
+        <div class="form-group">
+          <label for="difficulty">难度级别</label>
+          <select id="difficulty" [(ngModel)]="newCustomerDifficulty">
+            @for (tab of difficultyTabs; track tab.value) {
+              <option [value]="tab.value">{{tab.label}}</option>
+            }
+          </select>
         </div>
       </div>
+      <div class="modal-actions">
+        <button class="modal-btn cancel-btn" (click)="closeAddCustomerModal()">取消</button>
+        <button class="modal-btn save-btn" (click)="saveCustomerType()" [disabled]="!newCustomerTypeName.trim()">保存</button>
+      </div>
     </div>
+  </div>
+}
 
-    <!-- 完整报告弹窗 -->
-    <div class="modal-overlay" id="fullReportModal">
-      <div class="modal">
-        <div class="modal-header">
-          <h3 class="modal-title">陪练报告详情</h3>
-          <button class="close-modal" id="closeFullReportModal">
-            <i class="icon-close"></i>
-          </button>
-        </div>
-        <div class="modal-content">
-          <div class="report-detail">
-            <!-- 报告详情内容 -->
+<!-- 完整报告弹窗 -->
+@if (showFullReportModal) {
+  <div class="modal-overlay active">
+    <div class="modal">
+      <div class="modal-header">
+        <button class="back-btn" (click)="closeFullReportModal()">
+          <i class="fas fa-arrow-left"></i>
+        </button>
+        <h3 class="modal-title">陪练报告详情</h3>
+        <button class="close-modal" (click)="closeFullReportModal()">
+          <i class="fas fa-times"></i>
+        </button>
+      </div>
+      <div class="modal-content">
+        <div class="report-detail">
+          <div class="report-info">
+            <div class="info-item">
+              <span class="info-label">日期</span>
+              <span class="info-value">{{currentReport.date | date: 'yyyy年MM月dd日'}}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">客户类型</span>
+              <span class="info-value">{{currentReport.customerType}}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">难度</span>
+              <span class="info-value">{{currentReport.difficulty}}</span>
+            </div>
+          </div>
+          
+          <div class="section">
+            <div class="section-title">
+              <i class="fas fa-comments"></i>
+              对话内容
+            </div>
+            <div class="conversation-history">
+              @for (message of messages; track $index) {
+                <div class="message-history" 
+                     [class.customer-message-history]="message.isCustomer"
+                     [class.user-message-history]="!message.isCustomer">
+                  {{message.text}}
+                </div>
+              }
+            </div>
+          </div>
+          
+          <div class="section">
+            <div class="section-title">
+              <i class="fas fa-star"></i>
+              能力评价
+            </div>
+            <div class="evaluation-content">
+              <p>{{currentReport.evaluation.intro}}</p>
+              <ul>
+                @for (point of currentReport.evaluation.points; track point) {
+                  <li>{{point}}</li>
+                }
+              </ul>
+              <div class="score-tag" [ngClass]="getScoreTagClass()">
+                综合评分: {{currentScore}}/100
+              </div>
+            </div>
+          </div>
+          
+          <div class="section">
+            <div class="section-title">
+              <i class="fas fa-lightbulb"></i>
+              总结方法
+            </div>
+            <div class="summary-content">
+              <p>{{currentReport.summaryMethods.intro}}</p>
+              <ol>
+                @for (method of currentReport.summaryMethods.methods; track method) {
+                  <li>{{method}}</li>
+                }
+              </ol>
+              <div class="tag-list">
+                @for (tag of currentReport.summaryMethods.tags; track tag) {
+                  <div class="tag">{{tag}}</div>
+                }
+              </div>
+            </div>
           </div>
         </div>
       </div>
     </div>
+  </div>
+}
 
-    <!-- 历史报告弹窗 -->
-    <div class="modal-overlay" id="historyReportModal">
-      <div class="modal modal-lg">
-        <div class="modal-header">
-          <h3 class="modal-title">历史陪练报告</h3>
-          <button class="close-modal" id="closeHistoryReportModal">
-            <i class="icon-close"></i>
-          </button>
-        </div>
-        <div class="modal-content">
-          <div class="history-list">
-            <div class="history-item">
+<!-- 历史报告弹窗 -->
+@if (showHistoryReportModal) {
+  <div class="modal-overlay active">
+    <div class="modal">
+      <div class="modal-header">
+        <button class="back-btn" (click)="closeHistoryReportModal()">
+          <i class="fas fa-arrow-left"></i>
+        </button>
+        <h3 class="modal-title">历史陪练报告</h3>
+        <button class="close-modal" (click)="closeHistoryReportModal()">
+          <i class="fas fa-times"></i>
+        </button>
+      </div>
+      <div class="modal-content">
+        <div class="history-list">
+          @for (report of historyReports; track report.id) {
+            <div class="history-item" (click)="viewHistoryReport(report)">
               <div class="history-header">
-                <span class="history-date">2023-10-15</span>
-                <span class="history-type">商务场景</span>
+                <span class="history-date">{{report.date | date: 'yyyy年MM月dd日 HH:mm'}}</span>
+                <span class="history-type">{{report.customerType}}</span>
               </div>
               <div class="history-summary">
-                本次对话展现了良好的沟通能力,但在价格谈判环节需要更灵活的策略。
+                {{report.summary}}
               </div>
             </div>
-          </div>
+          }
+          @if (historyReports.length === 0) {
+            <div class="no-history">
+              <i class="fas fa-inbox"></i>
+              <p>暂无历史报告</p>
+            </div>
+          }
         </div>
       </div>
     </div>
   </div>
-
-  <script src="script.js"></script>
-</body>
-</html>
+}

+ 492 - 234
ai-assisant/src/modules/crm/mobile/page-crm-training/page-crm-training.scss

@@ -1,178 +1,225 @@
-/* 基础重置和字体 */
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-}
-
-body {
-  font-family: 'Noto Sans SC', sans-serif;
-  background-color: #f8fafc;
-  color: #333;
-  line-height: 1.6;
-  padding: 16px;
-}
-
-/* 容器 */
-.container {
+:root {
+  --primary: #4285f4;
+  --primary-light: #8ab4f8;
+  --primary-dark: #0a192f;
+  --text-dark: #000000;
+  --text-light: #000000;
+  --card-bg: #ffffff;
+  --border-color: #eaeaea;
+  --shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+  --success: #34a853;
+  --warning: #f9ab00;
+  --danger: #ea4335;
+  --info: #4285f4;
+}
+
+.training-container {
   max-width: 480px;
   margin: 0 auto;
-  position: relative;
+  padding: 0 16px;
+  padding-bottom: 40px;
+  background-color: #f8fafc;
+  color: var(--text-dark);
+  line-height: 1.6;
 }
 
 /* 头部导航 */
 .header {
   position: sticky;
   top: 0;
-  background: rgba(255, 255, 255, 0.98);
-  backdrop-filter: blur(8px);
+  background: rgba(255, 255, 255, 0.95);
+  backdrop-filter: blur(10px);
   z-index: 100;
-  padding: 12px 0;
-  border-bottom: 1px solid #eaeaea;
-  margin-bottom: 16px;
+  padding: 16px 0;
+  border-bottom: 1px solid var(--border-color);
 }
 
 .nav-top {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 0 8px;
 }
 
 .back-btn {
-  background: none;
+  background: rgba(0, 0, 0, 0.1);
+  width: 36px;
+  height: 36px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
   border: none;
-  font-size: 20px;
-  color: #333;
-  cursor: pointer;
-  padding: 8px;
+  font-size: 16px;
+  color: var(--text-dark);
 }
 
 .page-title {
   font-size: 18px;
   font-weight: 700;
-  color: #0a192f;
-}
-
-.spacer {
-  width: 40px; /* 保持对称 */
-}
-
-/* 图标基础样式 */
-[class^="icon-"] {
-  font-style: normal;
-  display: inline-block;
-  vertical-align: middle;
+  color: var(--text-dark);
 }
 
-/* 各部分通用样式 */
-.section {
-  background: white;
+/* 场景选择卡 */
+.section-card {
+  background-color: var(--card-bg);
   border-radius: 16px;
-  padding: 20px;
-  margin-bottom: 20px;
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
+  padding: 16px;
+  margin: 16px 0;
+  box-shadow: var(--shadow);
 }
 
 .section-title {
   font-size: 16px;
   font-weight: 600;
-  margin-bottom: 16px;
+  margin-bottom: 12px;
   display: flex;
   align-items: center;
   justify-content: space-between;
-  color: #0a192f;
+  color: var(--text-dark);
 }
 
-.section-title [class^="icon-"] {
+.section-title i {
   margin-right: 8px;
-  color: #4285f4;
+  color: var(--primary);
+}
+
+.edit-btn {
+  background: none;
+  border: none;
+  color: var(--text-dark);
+  font-size: 14px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  min-width: 44px;
+  min-height: 44px;
 }
 
-/* 场景选择部分 */
 .difficulty-tabs {
   display: flex;
-  gap: 8px;
-  margin-bottom: 16px;
+  gap: 12px;
+  overflow-x: auto;
+  padding: 8px 0 16px;
+  -ms-overflow-style: none;
+  scrollbar-width: none;
+}
+
+.difficulty-tabs::-webkit-scrollbar {
+  display: none;
 }
 
 .tab {
+  flex: 0 0 auto;
   padding: 8px 16px;
   border-radius: 20px;
-  background: white;
-  border: 1px solid #e2e8f0;
+  background-color: var(--card-bg);
+  border: 1px solid var(--border-color);
+  box-shadow: var(--shadow);
   font-size: 14px;
   cursor: pointer;
-  transition: all 0.2s;
-  flex: 1;
-  text-align: center;
-  white-space: nowrap;
+  transition: all 0.3s ease;
+  min-width: 44px;
+  min-height: 44px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: var(--text-dark);
 }
 
 .tab.active {
-  background: #4285f4;
+  background-color: var(--primary);
   color: white;
-  border-color: #4285f4;
-  font-weight: 500;
+  border-color: var(--primary);
+  font-weight: 600;
 }
 
 .customer-bubbles {
   display: flex;
   flex-wrap: wrap;
-  gap: 10px;
+  gap: 12px;
+  margin-top: 8px;
 }
 
 .bubble {
   padding: 10px 16px;
   border-radius: 20px;
-  background: white;
-  border: 1px solid #e2e8f0;
+  background-color: var(--card-bg);
+  border: 1px solid var(--border-color);
+  box-shadow: var(--shadow);
   font-size: 14px;
   cursor: pointer;
-  transition: all 0.2s;
+  transition: all 0.2s ease;
   position: relative;
   display: flex;
   align-items: center;
-  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
+  gap: 6px;
+  min-width: 44px;
+  min-height: 44px;
+  color: var(--text-dark);
 }
 
 .bubble.active {
-  background: #0a192f;
+  background-color: var(--primary-dark);
   color: white;
-  border-color: #0a192f;
+  border-color: var(--primary-dark);
 }
 
-.delete-btn {
-  display: none;
-  background: none;
+.bubble .delete-btn {
+  display: inline-flex;
+  background: rgba(0, 0, 0, 0.5);
+  border-radius: 50%;
+  width: 18px;
+  height: 18px;
+  align-items: center;
+  justify-content: center;
+  color: white !important;
+  margin-left: 4px;
   border: none;
-  color: rgba(255, 255, 255, 0.7);
-  margin-left: 6px;
   cursor: pointer;
 }
 
 .bubble.active .delete-btn {
-  display: block;
+  background: rgba(255, 255, 255, 0.3);
 }
 
 .add-bubble {
   padding: 10px 16px;
   border-radius: 20px;
-  background: white;
-  border: 1px dashed #cbd5e1;
+  background-color: var(--card-bg);
+  border: 1px dashed var(--border-color);
   font-size: 14px;
   cursor: pointer;
   display: flex;
   align-items: center;
-  color: #94a3b8;
-  transition: all 0.2s;
+  gap: 6px;
+  color: var(--text-dark);
+  min-width: 44px;
+  min-height: 44px;
 }
 
-.add-bubble:hover {
-  border-color: #94a3b8;
+.start-btn {
+  width: 100%;
+  padding: 12px;
+  background-color: var(--primary);
+  color: white;
+  border: none;
+  border-radius: 12px;
+  font-size: 16px;
+  font-weight: 600;
+  margin-top: 16px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+}
+
+/* 对话主界面 */
+.conversation-section {
+  margin-top: 24px;
 }
 
-/* 对话界面 */
 .avatar-container {
   display: flex;
   justify-content: center;
@@ -183,107 +230,145 @@ body {
   width: 100px;
   height: 100px;
   border-radius: 50%;
-  background: linear-gradient(135deg, #4285f4, #0a192f);
+  background: linear-gradient(135deg, var(--primary), var(--primary-dark));
   display: flex;
   align-items: center;
   justify-content: center;
-  color: white;
+  position: relative;
+  overflow: hidden;
+  box-shadow: var(--shadow);
+  transition: all 0.3s ease;
+}
+
+.customer-avatar::before {
+  content: "";
+  position: absolute;
+  width: 140px;
+  height: 140px;
+  background: radial-gradient(circle, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0) 70%);
+  border-radius: 50%;
+}
+
+.customer-avatar i {
   font-size: 36px;
-  box-shadow: 0 8px 20px rgba(66, 133, 244, 0.3);
+  color: white;
 }
 
 .conversation-container {
-  background: white;
-  border-radius: 16px;
+  background-color: var(--card-bg);
+  border-radius: 20px;
+  box-shadow: var(--shadow);
   padding: 16px;
   margin: 16px 0;
-  height: 280px;
+  height: 320px;
   overflow-y: auto;
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-  border: 1px solid #e2e8f0;
 }
 
 .message {
   max-width: 80%;
-  padding: 12px 16px;
+  padding: 12px;
+  margin-bottom: 16px;
   border-radius: 18px;
-  font-size: 15px;
-  line-height: 1.5;
+  position: relative;
   animation: fadeIn 0.3s ease;
+  color: var(--text-dark);
+}
+
+@keyframes fadeIn {
+  from { opacity: 0; transform: translateY(10px); }
+  to { opacity: 1; transform: translateY(0); }
 }
 
 .customer-message {
-  background: #f1f5f9;
-  align-self: flex-start;
+  background-color: #f1f5f9;
   border-bottom-left-radius: 4px;
+  align-self: flex-start;
 }
 
 .user-message {
-  background: #4285f4;
+  background-color: var(--primary);
   color: white;
-  align-self: flex-end;
   border-bottom-right-radius: 4px;
+  margin-left: auto;
 }
 
 .input-area {
   display: flex;
-  gap: 10px;
+  gap: 12px;
   align-items: center;
-  background: white;
+  background-color: var(--card-bg);
   border-radius: 30px;
-  padding: 10px 16px;
+  padding: 12px 20px;
+  box-shadow: var(--shadow);
   margin-top: 16px;
-  border: 1px solid #e2e8f0;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
 }
 
 .voice-btn {
-  width: 44px;
-  height: 44px;
+  width: 48px;
+  height: 48px;
   border-radius: 50%;
-  background: #4285f4;
+  background-color: var(--primary);
   color: white;
   display: flex;
   align-items: center;
   justify-content: center;
   cursor: pointer;
   border: none;
-  font-size: 18px;
-  transition: all 0.2s;
+  font-size: 20px;
+  transition: all 0.3s ease;
 }
 
 .voice-btn.active {
-  background: #ea4335;
+  background-color: var(--danger);
+  color: white;
   animation: pulse 1.5s infinite;
 }
 
+@keyframes pulse {
+  0% { transform: scale(1); }
+  50% { transform: scale(1.05); }
+  100% { transform: scale(1); }
+}
+
 .text-input {
   flex: 1;
-  padding: 12px;
-  border: none;
+  padding: 12px 16px;
+  border: 1px solid var(--border-color);
   border-radius: 24px;
-  font-size: 15px;
+  font-size: 16px;
   outline: none;
-  background: transparent;
+  transition: border-color 0.3s;
+  color: var(--text-dark);
+}
+
+.text-input:focus {
+  border-color: var(--primary);
 }
 
 .send-btn {
-  width: 44px;
-  height: 44px;
+  width: 48px;
+  height: 48px;
   border-radius: 50%;
-  background: #0a192f;
+  background-color: var(--primary-dark);
   color: white;
   display: flex;
   align-items: center;
   justify-content: center;
   cursor: pointer;
   border: none;
-  font-size: 18px;
+  font-size: 20px;
 }
 
 /* 能力仪表盘 */
+.dashboard {
+  background-color: var(--card-bg);
+  border-radius: 20px;
+  box-shadow: var(--shadow);
+  padding: 24px;
+  margin: 24px 0;
+  text-align: center;
+}
+
 .progress-container {
   position: relative;
   width: 180px;
@@ -294,20 +379,6 @@ body {
 .progress-circle {
   width: 100%;
   height: 100%;
-  transform: rotate(-90deg);
-}
-
-.progress-bg {
-  fill: none;
-  stroke: #e2e8f0;
-  stroke-width: 10;
-}
-
-.progress-fill {
-  fill: none;
-  stroke: #4285f4;
-  stroke-width: 10;
-  stroke-linecap: round;
 }
 
 .progress-value {
@@ -317,12 +388,13 @@ body {
   transform: translate(-50%, -50%);
   font-size: 32px;
   font-weight: 700;
-  color: #0a192f;
+  color: var(--text-dark);
 }
 
 .skills-container {
-  display: grid;
-  grid-template-columns: repeat(3, 1fr);
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: center;
   gap: 16px;
   margin-top: 20px;
 }
@@ -333,27 +405,36 @@ body {
   align-items: center;
 }
 
-.skill-name {
-  font-size: 12px;
-  color: #64748b;
-  margin-bottom: 6px;
-}
-
 .skill-progress {
-  width: 100%;
+  width: 80px;
   height: 6px;
-  background: #e2e8f0;
+  background-color: #e2e8f0;
   border-radius: 3px;
+  margin-top: 8px;
   overflow: hidden;
 }
 
 .skill-progress-fill {
   height: 100%;
-  background: #4285f4;
+  background-color: var(--primary);
   border-radius: 3px;
 }
 
-/* 报告卡片 */
+.skill-name {
+  font-size: 12px;
+  color: var(--text-dark);
+  margin-top: 4px;
+}
+
+/* 存档面板 */
+.report-card {
+  background-color: var(--card-bg);
+  border-radius: 20px;
+  box-shadow: var(--shadow);
+  padding: 20px;
+  margin-top: 24px;
+}
+
 .report-header {
   display: flex;
   justify-content: space-between;
@@ -364,21 +445,29 @@ body {
 .report-title {
   font-size: 18px;
   font-weight: 700;
-  color: #0a192f;
+  color: var(--text-dark);
 }
 
 .comparison {
   display: flex;
   align-items: center;
   font-size: 14px;
-  color: #34a853;
+  color: var(--text-dark);
+}
+
+.comparison.down {
+  color: var(--danger);
+}
+
+.comparison i {
+  margin-right: 4px;
 }
 
 .report-summary {
   font-size: 14px;
   line-height: 1.7;
   margin-bottom: 20px;
-  color: #475569;
+  color: var(--text-dark);
 }
 
 .tags-container {
@@ -390,15 +479,15 @@ body {
 
 .tag {
   padding: 6px 12px;
-  background: #f1f5f9;
+  background-color: #e2e8f0;
   border-radius: 16px;
   font-size: 12px;
-  color: #475569;
+  color: var(--text-dark);
 }
 
 .actions {
   display: flex;
-  gap: 10px;
+  gap: 12px;
 }
 
 .action-btn {
@@ -406,24 +495,22 @@ body {
   padding: 12px;
   border-radius: 12px;
   border: none;
-  font-weight: 500;
+  font-weight: 600;
   cursor: pointer;
   display: flex;
   align-items: center;
   justify-content: center;
   gap: 8px;
-  font-size: 14px;
-  transition: all 0.2s;
+  transition: all 0.3s ease;
+  color: white;
 }
 
 .view-btn {
-  background: #4285f4;
-  color: white;
+  background-color: var(--primary);
 }
 
 .history-btn {
-  background: #0a192f;
-  color: white;
+  background-color: var(--primary-dark);
 }
 
 /* 弹窗样式 */
@@ -433,14 +520,14 @@ body {
   left: 0;
   right: 0;
   bottom: 0;
-  background: rgba(0, 0, 0, 0.5);
+  background-color: rgba(0, 0, 0, 0.5);
   display: flex;
   align-items: center;
   justify-content: center;
   z-index: 1000;
   opacity: 0;
   pointer-events: none;
-  transition: opacity 0.3s;
+  transition: opacity 0.3s ease;
 }
 
 .modal-overlay.active {
@@ -449,20 +536,16 @@ body {
 }
 
 .modal {
-  background: white;
+  background-color: white;
   border-radius: 16px;
   width: 90%;
-  max-width: 400px;
-  max-height: 80vh;
+  max-width: 500px;
+  max-height: 90vh;
   overflow-y: auto;
   padding: 24px;
-  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
+  box-shadow: var(--shadow);
   transform: translateY(20px);
-  transition: transform 0.3s;
-}
-
-.modal-lg {
-  max-width: 500px;
+  transition: transform 0.3s ease;
 }
 
 .modal-overlay.active .modal {
@@ -471,17 +554,19 @@ body {
 
 .modal-header {
   display: flex;
-  justify-content: space-between;
   align-items: center;
   margin-bottom: 20px;
   padding-bottom: 16px;
-  border-bottom: 1px solid #e2e8f0;
+  border-bottom: 1px solid var(--border-color);
 }
 
 .modal-title {
   font-size: 18px;
   font-weight: 700;
-  color: #0a192f;
+  color: var(--text-dark);
+  margin-left: 12px;
+  flex: 1;
+  text-align: center;
 }
 
 .close-modal {
@@ -489,67 +574,158 @@ body {
   border: none;
   font-size: 20px;
   cursor: pointer;
-  color: #94a3b8;
+  color: var(--text-dark);
+  min-width: 44px;
+  min-height: 44px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }
 
-.form-group {
-  margin-bottom: 16px;
+.modal-header .back-btn {
+  background: none;
+  border: none;
+  font-size: 20px;
+  color: var(--text-dark);
+  cursor: pointer;
+  margin-right: 12px;
+  min-width: 44px;
+  min-height: 44px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }
 
-.form-group label {
-  display: block;
-  margin-bottom: 8px;
-  font-size: 14px;
-  color: #475569;
+.modal-content {
+  margin-bottom: 24px;
+  color: var(--text-dark);
 }
 
-.form-group input {
-  width: 100%;
-  padding: 12px;
-  border: 1px solid #e2e8f0;
-  border-radius: 8px;
+.report-detail {
+  background-color: white;
+  padding: 20px;
+  border-radius: 16px;
+}
+
+.report-info {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 20px;
+  padding-bottom: 15px;
+  border-bottom: 1px solid var(--border-color);
+}
+
+.info-item {
+  display: flex;
+  flex-direction: column;
+}
+
+.info-label {
+  font-size: 12px;
+  color: var(--text-dark);
+  margin-bottom: 4px;
+}
+
+.info-value {
   font-size: 14px;
-  outline: none;
-  transition: border-color 0.2s;
+  font-weight: 600;
+  color: var(--text-dark);
 }
 
-.form-group input:focus {
-  border-color: #4285f4;
+.section {
+  margin-bottom: 24px;
 }
 
-.modal-actions {
+.section-title {
+  font-size: 16px;
+  font-weight: 600;
+  margin-bottom: 12px;
+  color: var(--text-dark);
   display: flex;
-  gap: 12px;
-  justify-content: flex-end;
+  align-items: center;
+  gap: 8px;
 }
 
-.modal-btn {
-  padding: 10px 16px;
-  border-radius: 8px;
+.section-title i {
+  color: var(--primary);
+}
+
+.conversation-history {
+  background-color: #f8fafc;
+  border-radius: 12px;
+  padding: 16px;
+  max-height: 300px;
+  overflow-y: auto;
+}
+
+.message-history {
+  padding: 10px 12px;
+  margin-bottom: 12px;
+  border-radius: 12px;
   font-size: 14px;
-  cursor: pointer;
-  border: none;
-  transition: all 0.2s;
+  color: var(--text-dark);
 }
 
-.cancel-btn {
-  background: #f1f5f9;
-  color: #475569;
+.customer-message-history {
+  background-color: #e2e8f0;
+  align-self: flex-start;
+  max-width: 80%;
 }
 
-.save-btn {
-  background: #4285f4;
-  color: white;
+.user-message-history {
+  background-color: var(--primary-light);
+  color: var(--text-dark);
+  margin-left: auto;
+  max-width: 80%;
+}
+
+.evaluation-content {
+  background-color: #f8fafc;
+  border-radius: 12px;
+  padding: 16px;
+  font-size: 14px;
+  line-height: 1.6;
+  color: var(--text-dark);
+}
+
+.summary-content {
+  background-color: #f8fafc;
+  border-radius: 12px;
+  padding: 16px;
+  font-size: 14px;
+  line-height: 1.6;
+  color: var(--text-dark);
+}
+
+.tag-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  margin-top: 12px;
+}
+
+.history-list {
+  background-color: #f8fafc;
+  border-radius: 12px;
+  padding: 16px;
+  max-height: 400px;
+  overflow-y: auto;
 }
 
-/* 历史报告 */
 .history-item {
-  padding: 16px 0;
-  border-bottom: 1px solid #e2e8f0;
+  padding: 12px;
+  background-color: white;
+  border-radius: 12px;
+  margin-bottom: 12px;
+  box-shadow: var(--shadow);
+  cursor: pointer;
+  transition: all 0.2s ease;
+  color: var(--text-dark);
 }
 
-.history-item:last-child {
-  border-bottom: none;
+.history-item:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);
 }
 
 .history-header {
@@ -559,43 +735,125 @@ body {
 }
 
 .history-date {
-  font-size: 14px;
-  color: #64748b;
+  font-weight: 600;
+  color: var(--text-dark);
 }
 
 .history-type {
+  background-color: var(--primary);
+  color: white;
+  padding: 2px 8px;
+  border-radius: 10px;
   font-size: 12px;
-  background: #f1f5f9;
+}
+
+.history-summary {
+  font-size: 14px;
+  color: var(--text-dark);
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+
+.no-history {
+  text-align: center;
+  padding: 20px;
+  color: var(--text-dark);
+}
+
+/* 评分标签 */
+.score-tag {
+  display: inline-block;
   padding: 4px 8px;
   border-radius: 12px;
-  color: #475569;
+  font-size: 12px;
+  font-weight: 600;
+  margin-top: 8px;
+  color: var(--text-dark);
 }
 
-.history-summary {
+.high-score {
+  background-color: #d1fae5;
+  color: var(--text-dark);
+}
+
+.medium-score {
+  background-color: #fef3c7;
+  color: var(--text-dark);
+}
+
+.low-score {
+  background-color: #fee2e2;
+  color: var(--text-dark);
+}
+
+/* 表单样式 */
+.form-group {
+  margin-bottom: 20px;
+}
+
+.form-group label {
+  display: block;
+  margin-bottom: 8px;
   font-size: 14px;
-  color: #475569;
-  line-height: 1.6;
+  color: var(--text-dark);
 }
 
-/* 动画 */
-@keyframes pulse {
-  0% { transform: scale(1); }
-  50% { transform: scale(1.05); }
-  100% { transform: scale(1); }
+.form-group input,
+.form-group select {
+  width: 100%;
+  padding: 12px 16px;
+  border: 1px solid var(--border-color);
+  border-radius: 8px;
+  font-size: 14px;
+  outline: none;
+  transition: border-color 0.3s;
+  color: var(--text-dark);
 }
 
-@keyframes fadeIn {
-  from { opacity: 0; transform: translateY(10px); }
-  to { opacity: 1; transform: translateY(0); }
+.form-group input:focus,
+.form-group select:focus {
+  border-color: var(--primary);
+}
+
+.modal-actions {
+  display: flex;
+  gap: 12px;
+  justify-content: flex-end;
 }
 
-/* 响应式调整 */
-@media (max-width: 400px) {
-  .skills-container {
-    grid-template-columns: repeat(2, 1fr);
-  }
-  
-  .section {
-    padding: 16px;
-  }
+.modal-btn {
+  padding: 10px 20px;
+  border-radius: 8px;
+  font-size: 14px;
+  font-weight: 600;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  min-width: 44px;
+  min-height: 44px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.cancel-btn {
+  background-color: #f1f5f9;
+  color: var(--text-dark);
+  border: none;
+}
+
+.save-btn {
+  background-color: var(--primary);
+  color: white;
+  border: none;
+}
+
+/* 确保所有按钮有足够点击区域 */
+button {
+  min-width: 44px;
+  min-height: 44px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }

+ 531 - 130
ai-assisant/src/modules/crm/mobile/page-crm-training/page-crm-training.ts

@@ -1,159 +1,560 @@
-import { Component } from '@angular/core';
+import { Component, ElementRef, ViewChild, AfterViewInit, inject } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
-import { MatIconModule } from '@angular/material/icon';
-import { MatButtonModule } from '@angular/material/button';
-import { MatCardModule } from '@angular/material/card';
-import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
-import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
-import { faMicrophone, faSquare, faArrowLeft, faLayerGroup, faEdit, faPlus, faTimes, faChartLine, faFileAlt, faHistory, faArrowUp, faComments, faStar, faLightbulb } from '@fortawesome/free-solid-svg-icons';
+import { TrainingService } from './training.service';
+import { CloudObject, CloudQuery } from '../../../../lib/ncloud';
 
 @Component({
   selector: 'app-page-crm-training',
   standalone: true,
-  imports: [
-    CommonModule,
-    FormsModule,
-    MatIconModule,
-    MatButtonModule,
-    MatCardModule,
-    MatProgressSpinnerModule,
-    FontAwesomeModule,
-   
-  ],
-   templateUrl:'./page-crm-training.html',
+  imports: [CommonModule, FormsModule],
+  templateUrl: './page-crm-training.html',
   styleUrls: ['./page-crm-training.scss']
 })
-export class PageCrmTraining {
-  // Font Awesome 图标
-  faMicrophone = faMicrophone;
-  faSquare = faSquare;
-  faArrowLeft = faArrowLeft;
-  faLayerGroup = faLayerGroup;
-  faEdit = faEdit;
-  faPlus = faPlus;
-  faTimes = faTimes;
-  faChartLine = faChartLine;
-  faFileAlt = faFileAlt;
-  faHistory = faHistory;
-  faArrowUp = faArrowUp;
-  faComments = faComments;
-  faStar = faStar;
-  faLightbulb = faLightbulb;
-
-  // 组件状态和逻辑
-  isVoiceActive = false;
-  selectedDifficulty = '初级';
-  difficulties = [
-    { label: '⭐ 初级', value: '初级' },
-    { label: '⭐⭐ 中级', value: '中级' },
-    { label: '⭐⭐⭐ 高级', value: '高级' },
-    { label: '专家挑战', value: '专家' }
-  ];
-  
-  customerTypes = [
-    { name: '商务客户', active: true },
-    { name: '家庭客户', active: false },
-    { name: 'VIP客户', active: false },
-    { name: '投诉客户', active: false },
-    { name: '团体客户', active: false }
-  ];
-  isEditingTypes = false;
-  newCustomerType = '';
-  
-  messages = [
-    { text: '您好,我想预订下周五的商务套房,你们有什么优惠吗?', isCustomer: true },
-    { text: '感谢您的咨询!我们目前有商务套餐优惠,包含早餐和会议室使用,您需要了解详情吗?', isCustomer: false },
-    { text: '会议室可以容纳多少人?另外我需要延迟退房到下午4点。', isCustomer: true },
-    { text: '我们的商务套房会议室最多可容纳20人,延迟退房到下午4点需要额外支付50%的房费。', isCustomer: false },
-    { text: '50%的费用太高了,我是贵酒店的黄金会员,能否提供免费延迟退房?', isCustomer: true },
-    { text: '感谢您的会员支持!根据黄金会员权益,我们可以提供免费延迟退房到下午2点,或者您可以选择支付额外费用延长到4点。', isCustomer: false },
-    { text: '下午2点可能不太够,我下午3点有个重要会议。有没有折中方案?', isCustomer: true },
-    { text: '我理解您的情况。我们可以为您提供下午3点的延迟退房,只需额外支付25%的房费,这样您看可以接受吗?', isCustomer: false }
-  ];
-  newMessage = '';
-  
-  skills = [
-    { name: '反应力', value: 90 },
-    { name: '话术', value: 85 },
-    { name: '说服力', value: 75 },
-    { name: '专业度', value: 92 }
-  ];
-  
+export class PageCrmTraining implements AfterViewInit {
+  @ViewChild('conversationContainer') conversationContainer!: ElementRef;
+  private trainingService = inject(TrainingService);
+
+  // 状态控制
+  isEditing = false;
+  isRecording = false;
   showAddCustomerModal = false;
   showFullReportModal = false;
   showHistoryReportModal = false;
-  
-  historyReports = [
-    { 
-      id: 1,
-      date: '2023年10月15日 14:30',
-      type: '商务客户',
-      summary: '本次与商务客户的对话中,您展现了优秀的专业知识和反应能力。建议在价格谈判环节采用更积极的策略。'
+
+  // 表单数据
+  userInput = '';
+  newCustomerTypeName = '';
+  newCustomerDifficulty = 1;
+
+  // 评分数据
+  currentScore = 0;
+  scoreChange = 0;
+
+  // 对话数据
+  messages: Message[] = [];
+  currentScenario: any = null;
+  currentRecordId: string | null = null;
+
+  // 客户类型数据
+  customerTypes: CustomerType[] = [];
+  difficultyTabs: DifficultyTab[] = [
+    { label: '⭐ 初级', value: 1, active: true },
+    { label: '⭐⭐ 中级', value: 2, active: false },
+    { label: '⭐⭐⭐ 高级', value: 3, active: false },
+    { label: '专家挑战', value: 4, active: false }
+  ];
+
+  // 技能数据
+  skills: Skill[] = [
+    { name: '反应力', value: 0, key: 'reactivity' },
+    { name: '话术', value: 0, key: 'technology' },
+    { name: '说服力', value: 0, key: 'persuasiveness' },
+    { name: '专业度', value: 0, key: 'professionalism' }
+  ];
+
+  // 当前报告数据
+  currentReport: Report = {
+    date: new Date(),
+    customerType: '',
+    difficulty: '',
+    summary: '',
+    tags: [],
+    evaluation: {
+      intro: '',
+      points: []
     },
-    { 
-      id: 2,
-      date: '2023年10月12日 10:15',
-      type: '投诉客户',
-      summary: '处理客户投诉时展现了良好的同理心,但解决方案不够全面,建议加强问题解决能力训练。'
+    summaryMethods: {
+      intro: '',
+      methods: [],
+      tags: []
     },
-    { 
-      id: 3,
-      date: '2023年10月10日 16:45',
-      type: 'VIP客户',
-      summary: '为VIP客户提供了个性化服务方案,展现了出色的服务意识,但在附加服务推荐上略显保守。'
+    score: 0
+  };
+
+  // 历史报告数据
+  historyReports: Report[] = [];
+
+  // 解决模板中 Math 报错
+  Math = Math;
+
+  async ngAfterViewInit() {
+    await this.loadTrainingScenarios();
+    await this.loadTrainingHistory();
+    this.scrollToBottom();
+  }
+
+  /* 核心功能方法 */
+  scrollToBottom(): void {
+    try {
+      setTimeout(() => {
+        this.conversationContainer.nativeElement.scrollTop = 
+          this.conversationContainer.nativeElement.scrollHeight;
+      }, 100);
+    } catch(err) { }
+  }
+
+  // 智能返回方法
+  goBack(): void {
+    if (this.showFullReportModal) {
+      this.closeFullReportModal();
+    } else if (this.showHistoryReportModal) {
+      this.closeHistoryReportModal();
+    } else if (this.showAddCustomerModal) {
+      this.closeAddCustomerModal();
+    } else {
+      console.log('返回上一页');
     }
-  ];
+  }
 
-  toggleVoice() {
-    this.isVoiceActive = !this.isVoiceActive;
+  canGoBack(): boolean {
+    return this.showFullReportModal || 
+           this.showHistoryReportModal || 
+           this.showAddCustomerModal;
   }
-  
-  selectDifficulty(difficulty: string) {
-    this.selectedDifficulty = difficulty;
+
+  /* 数据加载方法 */
+  async loadTrainingScenarios() {
+    try {
+      const activeTab = this.difficultyTabs.find(t => t.active);
+      const scenarios = await this.trainingService.getTrainingScenarios({
+        difficulty: activeTab?.value
+      });
+      
+      this.customerTypes = scenarios.map((s: any) => ({
+        id: s.id,
+        name: s.get('name'),
+        active: false,
+        icon: this.getRandomIcon(),
+        difficulty: s.get('difficulty'),
+        customerRole: s.get('customerRole')
+      }));
+      
+      if (this.customerTypes.length > 0) {
+        this.customerTypes[0].active = true;
+      }
+    } catch (error) {
+      console.error('加载训练场景失败:', error);
+    }
   }
-  
-  selectCustomerType(index: number) {
-    if (!this.isEditingTypes) {
-      this.customerTypes.forEach(type => type.active = false);
-      this.customerTypes[index].active = true;
+
+  async loadTrainingHistory() {
+    try {
+      const records = await this.trainingService.getUserTrainingHistory(5);
+      this.historyReports = await Promise.all(records.map(async (r: any) => {
+        const scenario = await new CloudQuery("TrainingScenario").get(r.get('scenarioId').objectId);
+        
+        return {
+          id: r.id,
+          date: new Date(r.get('createdAt')),
+          customerType: r.get('customerType'),
+          difficulty: this.getDifficultyLabel(scenario?.get('difficulty')),
+          summary: r.get('analysisResult') || '暂无分析结果',
+          tags: [],
+          evaluation: {
+            intro: '',
+            points: []
+          },
+          summaryMethods: {
+            intro: '',
+            methods: [],
+            tags: []
+          },
+          score: 0
+        };
+      }));
+    } catch (error) {
+      console.error('加载训练历史失败:', error);
     }
   }
-  
-  deleteCustomerType(index: number) {
-    if (this.customerTypes.length > 1) {
-      this.customerTypes.splice(index, 1);
-    } else {
+
+  /* 训练场景相关方法 */
+  async selectTab(tab: DifficultyTab) {
+    this.difficultyTabs.forEach(t => t.active = false);
+    tab.active = true;
+    await this.loadTrainingScenarios();
+  }
+
+  selectCustomerType(type: CustomerType) {
+    if (this.isEditing) return;
+    
+    this.customerTypes.forEach(t => t.active = false);
+    type.active = true;
+  }
+
+  toggleEditMode() {
+    this.isEditing = !this.isEditing;
+  }
+
+  async deleteCustomerType(type: CustomerType) {
+    if (this.customerTypes.length <= 1) {
       alert('至少需要保留一个客户类型');
+      return;
     }
+    
+    try {
+      await this.trainingService.deleteCustomerType(type.id);
+      const index = this.customerTypes.indexOf(type);
+      if (index !== -1) {
+        this.customerTypes.splice(index, 1);
+        if (type.active && this.customerTypes.length > 0) {
+          this.customerTypes[0].active = true;
+        }
+      }
+    } catch (error) {
+      console.error('删除客户类型失败:', error);
+      alert('删除失败,请稍后重试');
+    }
+  }
+
+  /* 客户类型管理 */
+  openAddCustomerModal() {
+    this.showAddCustomerModal = true;
   }
-  
-  addCustomerType() {
-    if (this.newCustomerType.trim()) {
-      this.customerTypes.push({ name: this.newCustomerType.trim(), active: false });
-      this.newCustomerType = '';
-      this.showAddCustomerModal = false;
+
+  closeAddCustomerModal() {
+    this.showAddCustomerModal = false;
+    this.newCustomerTypeName = '';
+    this.newCustomerDifficulty = 1;
+  }
+
+  async saveCustomerType() {
+    if (!this.newCustomerTypeName.trim()) return;
+    
+    try {
+      const newType = await this.trainingService.addCustomerType(
+        this.newCustomerTypeName,
+        this.newCustomerDifficulty,
+        '自定义角色'
+      );
+      
+      this.customerTypes.push({
+        id: newType.id|| '',
+        name: this.newCustomerTypeName,
+        active: false,
+        icon: this.getRandomIcon(),
+        difficulty: this.newCustomerDifficulty,
+        customerRole: '自定义角色'
+      });
+      
+      this.closeAddCustomerModal();
+    } catch (error) {
+      console.error('添加客户类型失败:', error);
+      alert('添加失败,请稍后重试');
     }
   }
-  
-  sendMessage() {
-    if (this.newMessage.trim()) {
-      this.messages.push({ text: this.newMessage, isCustomer: false });
-      this.newMessage = '';
+
+  getRandomIcon(): string {
+    const icons = [
+      'fa-user', 'fa-user-tie', 'fa-users', 
+      'fa-user-graduate', 'fa-user-md', 'fa-user-ninja'
+    ];
+    return icons[Math.floor(Math.random() * icons.length)];
+  }
+
+  /* 训练相关方法 */
+  async startTraining() {
+    const activeType = this.customerTypes.find(type => type.active);
+    if (!activeType) return;
+    
+    try {
+      const record = await this.trainingService.createTrainingRecord(activeType.id, activeType.name);
+      this.currentRecordId = record.id|| null;
       
-      // 模拟AI回复
-      setTimeout(() => {
-        this.messages.push({ 
-          text: "听起来不错,我会考虑这个方案。能提供一份详细报价吗?", 
-          isCustomer: true 
-        });
-      }, 1000);
+      this.currentScenario = {
+        id: activeType.id,
+        name: activeType.name,
+        difficulty: activeType.difficulty,
+        customerRole: activeType.customerRole
+      };
+      
+      this.messages = [{
+        text: `您好,我是${activeType.customerRole},${this.generateCustomerOpening(activeType.name)}`,
+        isCustomer: true
+      }];
+      
+      this.currentScore = 70;
+      this.scoreChange = 0;
+      this.skills.forEach(s => s.value = 70);
+      
+      this.scrollToBottom();
+    } catch (error) {
+      console.error('开始训练失败:', error);
     }
   }
-  
-  getCircleDashOffset() {
-    const circumference = 2 * Math.PI * 80;
-    const progress = 86; // 86%
-    return circumference - (progress / 100) * circumference;
+
+  generateCustomerOpening(type: string): string {
+    const openings: Record<string, string[]> = {
+      '商务客户': [
+        "我想咨询一下贵公司的企业服务方案。",
+        "我们需要预订一批商务客房,能提供优惠吗?",
+        "关于贵公司的VIP服务,我有几个问题想了解。"
+      ],
+      '家庭客户': [
+        "我们全家计划下周出游,能推荐适合的套餐吗?",
+        "带孩子入住有什么需要注意的事项吗?",
+        "请问有家庭优惠活动吗?"
+      ],
+      'VIP客户': [
+        "作为VIP会员,我应该享受哪些专属权益?",
+        "这次入住希望能升级房型,可以安排吗?",
+        "我的积分可以兑换什么服务?"
+      ]
+    };
+    
+    const defaultOpenings = [
+      "我想咨询一些服务细节。",
+      "能介绍一下你们的主要产品吗?",
+      "我对你们的服务很感兴趣。"
+    ];
+    
+    return openings[type] 
+      ? openings[type][Math.floor(Math.random() * openings[type].length)]
+      : defaultOpenings[Math.floor(Math.random() * defaultOpenings.length)];
   }
+
+  /* 对话功能 */
+  toggleRecording() {
+    this.isRecording = !this.isRecording;
+    if (this.isRecording) {
+      this.startVoiceRecording();
+    } else {
+      this.stopVoiceRecording();
+    }
+  }
+
+  startVoiceRecording() {
+    console.log('开始录音...');
+  }
+
+  stopVoiceRecording() {
+    console.log('停止录音...');
+  }
+
+  async sendMessage() {
+    if (!this.userInput.trim() || !this.currentRecordId) return;
+    
+    this.messages.push({ 
+      text: this.userInput, 
+      isCustomer: false 
+    });
+    this.userInput = '';
+    this.scrollToBottom();
+    
+    setTimeout(async () => {
+      const aiResponse = this.generateAIResponse();
+      this.messages.push({ 
+        text: aiResponse, 
+        isCustomer: true 
+      });
+      
+      this.updateScore();
+      this.scrollToBottom();
+      
+      if (this.messages.length >= 8) {
+        await this.saveAnalysisResult();
+      }
+    }, 1000 + Math.random() * 1000);
+  }
+
+  generateAIResponse(): string {
+    const responses = [
+      "听起来不错,我会考虑这个方案。能提供一份详细报价吗?",
+      "这个方案我需要再考虑一下,能否提供其他选择?",
+      "感谢您的建议,我会和团队讨论后给您回复。",
+      "这个价格可以接受,请问包含哪些服务?",
+      "作为VIP会员,我希望能有更多优惠,可以吗?"
+    ];
+    return responses[Math.floor(Math.random() * responses.length)];
+  }
+
+  /* 评分系统 */
+  updateScore() {
+    const change = Math.floor(Math.random() * 5) - 2;
+    this.currentScore = Math.min(100, Math.max(0, this.currentScore + change));
+    this.scoreChange = Math.floor(Math.random() * 10) - 3;
+    
+    this.skills.forEach(skill => {
+      skill.value = Math.min(100, Math.max(0, skill.value + (Math.floor(Math.random() * 5) - 1)))
+    });
+  }
+
+  getProgressOffset(): number {
+    return (100 - this.currentScore) / 100 * 502;
+  }
+
+  getSkillColor(value: number): string {
+    if (value >= 80) return '#34a853';
+    if (value >= 60) return '#f9ab00';
+    return '#ea4335';
+  }
+
+  /* 报告系统 */
+  async saveAnalysisResult() {
+    if (!this.currentRecordId) return;
+    
+    const analysis = `本次训练评分: ${this.currentScore}\n\n技能评估:\n` +
+      this.skills.map(s => `${s.name}: ${s.value}`).join('\n') +
+      `\n\n总结: ${this.generateSummary()}`;
+    
+    try {
+      await this.trainingService.updateTrainingRecordAnalysis(
+        this.currentRecordId,
+        analysis
+      );
+      
+      this.currentReport = {
+        date: new Date(),
+        customerType: this.getSelectedCustomerType(),
+        difficulty: this.getSelectedDifficulty(),
+        summary: analysis,
+        tags: ['训练完成', '技能评估'],
+        evaluation: {
+          intro: '训练结果分析:',
+          points: this.skills.map(s => `${s.name}得分: ${s.value}`)
+        },
+        summaryMethods: {
+          intro: '建议改进方向:',
+          methods: this.generateImprovementMethods(),
+          tags: ['改进建议']
+        },
+        score: this.currentScore
+      };
+      
+      await this.loadTrainingHistory();
+    } catch (error) {
+      console.error('保存分析结果失败:', error);
+    }
+  }
+
+  generateImprovementMethods(): string[] {
+    const methods = [];
+    if (this.skills[0].value < 80) {
+      methods.push('加强反应速度训练,提高即时应对能力');
+    }
+    if (this.skills[1].value < 80) {
+      methods.push('学习更多话术技巧,丰富表达方式');
+    }
+    if (this.skills[2].value < 80) {
+      methods.push('提高说服力,学习心理学技巧');
+    }
+    if (this.skills[3].value < 80) {
+      methods.push('加强专业知识学习,提高专业度');
+    }
+    return methods.length > 0 ? methods : ['各方面表现良好,继续保持!'];
+  }
+
+  generateSummary(): string {
+    const avgScore = this.skills.reduce((sum, skill) => sum + skill.value, 0) / this.skills.length;
+    if (avgScore >= 80) {
+      return '表现优秀!继续保持当前训练强度,可以挑战更高难度场景。';
+    } else if (avgScore >= 60) {
+      return '表现良好,但仍有提升空间。建议针对薄弱环节加强训练。';
+    } else {
+      return '表现有待提高。建议从基础场景开始,逐步提升各项能力。';
+    }
+  }
+
+  openFullReportModal() {
+    this.showFullReportModal = true;
+  }
+
+  closeFullReportModal() {
+    this.showFullReportModal = false;
+  }
+
+  openHistoryReportModal() {
+    this.showHistoryReportModal = true;
+  }
+
+  closeHistoryReportModal() {
+    this.showHistoryReportModal = false;
+  }
+
+  async viewHistoryReport(report: Report) {
+    this.currentReport = report;
+    this.currentScore = report.score;
+    this.closeHistoryReportModal();
+    setTimeout(() => {
+      this.openFullReportModal();
+    }, 300);
+  }
+
+  /* 辅助方法 */
+  getCustomerIcon(): string {
+    const activeType = this.customerTypes.find(type => type.active);
+    return activeType ? activeType.icon : 'fa-user';
+  }
+
+  getSelectedCustomerType(): string {
+    const activeType = this.customerTypes.find(type => type.active);
+    return activeType ? activeType.name : '';
+  }
+
+  getSelectedDifficulty(): string {
+    const activeTab = this.difficultyTabs.find(tab => tab.active);
+    return activeTab ? activeTab.label : '';
+  }
+
+  getDifficultyLabel(difficulty?: number): string {
+    if (difficulty === undefined) return '';
+    const labels = ['⭐ 初级', '⭐⭐ 中级', '⭐⭐⭐ 高级', '专家挑战'];
+    return labels[difficulty - 1] || '';
+  }
+
+  getScoreTagClass(): string {
+    if (this.currentScore >= 80) return 'high-score';
+    if (this.currentScore >= 60) return 'medium-score';
+    return 'low-score';
+  }
+}
+
+/* 类型定义 */
+interface Message {
+  text: string;
+  isCustomer: boolean;
+}
+
+interface CustomerType {
+  id: string;
+  name: string;
+  active: boolean;
+  icon: string;
+  difficulty: number;
+  customerRole: string;
+}
+
+interface DifficultyTab {
+  label: string;
+  value: number;
+  active: boolean;
+}
+
+interface Skill {
+  name: string;
+  value: number;
+  key: string;
+}
+
+interface Evaluation {
+  intro: string;
+  points: string[];
+}
+
+interface SummaryMethods {
+  intro: string;
+  methods: string[];
+  tags: string[];
+}
+
+interface Report {
+  id?: string;
+  date: Date;
+  customerType: string;
+  difficulty: string;
+  summary: string;
+  tags: string[];
+  evaluation: Evaluation;
+  summaryMethods: SummaryMethods;
+  score: number;
 }

+ 103 - 0
ai-assisant/src/modules/crm/mobile/page-crm-training/training.service.ts

@@ -0,0 +1,103 @@
+import { Injectable } from '@angular/core';
+import { CloudObject, CloudQuery, CloudUser } from '../../../../lib/ncloud';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class TrainingService {
+  private currentUser = new CloudUser();
+
+  constructor() { }
+
+  async getCurrentUser() {
+    return await this.currentUser.current();
+  }
+
+  async getTrainingScenarios(options?: {
+    difficulty?: number;
+    customerRole?: string;
+    limit?: number;
+    skip?: number;
+    order?: string;
+  }) {
+    const query = new CloudQuery("TrainingScenario");
+    
+    if (options?.difficulty) {
+      query.equalTo("difficulty", options.difficulty);
+    }
+    
+    if (options?.customerRole) {
+      query.equalTo("customerRole", options.customerRole);
+    }
+    
+    if (options?.limit) {
+      query.limit(options.limit);
+    }
+    
+    if (options?.skip) {
+      query.skip(options.skip);
+    }
+    
+    if (options?.order) {
+      const [field, direction] = options.order.split('_');
+      query.orderBy(field, direction as 'asc' | 'desc');
+    }
+    
+    return await query.find();
+  }
+
+  async createTrainingRecord(scenarioId: string, customerType: string) {
+    const user = await this.getCurrentUser();
+    if (!user || !user.id) throw new Error("用户未登录");
+    
+    const record = new CloudObject("TrainingRecord");
+    record.set({
+      userId: user.toPointer(),
+scenarioId: new CloudObject("TrainingScenario").set({ objectId: scenarioId }),
+customerType,     
+      analysisResult: ""
+    });
+    
+    return await record.save();
+  }
+
+  async updateTrainingRecordAnalysis(recordId: string, analysis: string) {
+    const record = new CloudObject("TrainingRecord");
+    record.set({ objectId: recordId, analysisResult: analysis });
+    return await record.save();
+  }
+
+  async getUserTrainingHistory(limit: number = 10, skip: number = 0) {
+    const user = await this.getCurrentUser();
+    if (!user || !user.id) throw new Error("用户未登录");
+    
+    const query = new CloudQuery("TrainingRecord");
+    query.equalTo("userId", user.toPointer());
+    query.orderBy("createdAt", "desc");
+    query.limit(limit);
+    query.skip(skip);
+    
+    return await query.find();
+  }
+
+  async addCustomerType(name: string, difficulty: number, customerRole: string) {
+    const scenario = new CloudObject("TrainingScenario");
+    scenario.set({
+      name,
+      difficulty,
+      customerRole,
+      reactivity: 0,
+      technology: 0,
+      persuasiveness: 0,
+      professionalism: 0
+    });
+    
+    return await scenario.save();
+  }
+
+  async deleteCustomerType(scenarioId: string) {
+    const scenario = new CloudObject("TrainingScenario");
+    scenario.set({ objectId: scenarioId });
+    return await scenario.destroy();
+  }
+}