Browse Source

Merge branch 'master' of http://git.fmode.cn:3000/18879694722/AI-Salesperson-Assistant

0235701 15 hours ago
parent
commit
1d142916a6

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

@@ -11,6 +11,12 @@
   <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
 </head>
 <body class="mat-typography">
+<body>
+  <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.7.2/css/all.min.css">
+  <!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> -->
+<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
+
+  <app-page-crm-data></app-page-crm-data>
   <app-root></app-root>
 </body>
 </html>

+ 210 - 0
ai-assisant/src/lib/cloud/comletion.ts

@@ -0,0 +1,210 @@
+export interface TestMessage {
+    role: string
+    content: string
+}
+
+export class TestCompletion {
+    token: string = "r:60abef69e7cd8181b146ceaba1fdbf02"
+    messageList: any = []
+    stream: boolean = true;
+    constructor(messageList: any) {
+        this.messageList = messageList || this.messageList
+    }
+    
+    async sendMessage(messageList?: null | Array<TestMessage>, onMessage?: (content: string) => void): Promise<any> {
+        this.messageList = messageList || this.messageList
+        let body = {
+            "messages": this.messageList,
+            "stream": this.stream,
+            "model": "fmode-4.5-128k",
+            "temperature": 0.5,
+            "presence_penalty": 0,
+            "frequency_penalty": 0,
+            "token": "Bearer " + this.token
+        }
+
+        let response = await fetch("https://server.fmode.cn/api/apig/aigc/gpt/v1/chat/completions", {
+            "headers": {
+            },
+            "body": JSON.stringify(body),
+            "method": "POST",
+            "mode": "cors",
+            "credentials": "omit"
+        });
+
+        // 单次响应处理
+        if (this.stream == false) {
+            let data = await response.json()
+            //console.log(data)
+            let lastContent = data?.choices?.[0]?.message?.content
+            return lastContent
+        }
+
+        // 流式处理
+        if (!response.body) {
+            throw new Error("No response body in stream mode");
+        }
+
+        const reader = response.body.getReader();
+        const decoder = new TextDecoder("utf-8");
+        let buffer = '';
+        let accumulatedContent = "";
+
+        try {
+            while (true) {
+                const { done, value } = await reader.read();
+                if (done) break;
+
+                buffer += decoder.decode(value, { stream: true });
+                
+                // 处理缓冲区中的所有完整行
+                let lastNewline = buffer.lastIndexOf('\n');
+                if (lastNewline === -1) continue; // 等待更多数据
+
+                const completeLines = buffer.substring(0, lastNewline + 1);
+                buffer = buffer.substring(lastNewline + 1);
+
+                const lines = completeLines.split('\n').filter(line => line.trim() !== '');
+
+                for (const line of lines) {
+                    if (!line.startsWith('data:')) continue;
+                    if (line.includes('[DONE]')) continue;
+
+                    try {
+                        const jsonStr = line.substring(5).trim();
+                        if (!jsonStr) continue;
+
+                        // 尝试解析JSON
+                        let data;
+                        try {
+                            data = JSON.parse(jsonStr);
+                        } catch (e) {
+                            // 如果解析失败,可能是数据不完整,暂时保留到缓冲区
+                            buffer = line + '\n' + buffer;
+                            continue;
+                        }
+
+                        const content = data?.choices?.[0]?.delta?.content || '';
+                        if (content) {
+                            accumulatedContent += content;
+                            if (onMessage) {
+                                onMessage(accumulatedContent);
+                            }
+                        }
+                    } catch (e) {
+                        console.error("Error processing stream data:", e);
+                    }
+                }
+            }
+
+            // 处理缓冲区中剩余的数据
+            if (buffer.trim()) {
+                try {
+                    const line = buffer.trim();
+                    if (line.startsWith('data:') && !line.includes('[DONE]')) {
+                        const jsonStr = line.substring(5).trim();
+                        if (jsonStr) {
+                            const data = JSON.parse(jsonStr);
+                            const content = data?.choices?.[0]?.delta?.content || '';
+                            if (content) {
+                                accumulatedContent += content;
+                                if (onMessage) {
+                                    onMessage(accumulatedContent);
+                                }
+                            }
+                        }
+                    }
+                } catch (e) {
+                    console.error("Error processing remaining buffer:", e);
+                }
+            }
+        } finally {
+            reader.releaseLock();
+        }
+
+        return accumulatedContent;
+    }
+}
+
+/**
+ * 使用AI生成符合指定结构的JSON数据
+ * @param prompt 任务要求的整体提示词
+ * @param jsonSchema 期望的JSON结构描述(用于提示词)
+ * @param example 可选的JSON示例(帮助AI理解格式)
+ * @param onMessage 实时生成内容回调
+ * @returns 解析后的JSON对象
+ */
+export async function completionJSON(
+    prompt: string,
+    jsonSchema: string,
+    example: object | null = null,
+    onMessage?: (content: string) => void
+): Promise<any> {
+    // 1. 构建提示词
+    const JsonResultParsePrompt = `请严格按照以下要求生成JSON数据:
+- 数据结构要求:${jsonSchema}
+- 只能返回一个完整的JSON对象 确保JSON格式正确
+- 注意返回的JSON格式每个KEY都有""包裹和Value之间都有:`;
+
+    // 2. 初始化消息列表
+    const messages: TestMessage[] = [
+        {
+            role: "user",
+            content: prompt + JsonResultParsePrompt
+        }
+    ];
+
+    // 3. 创建TestCompletion实例
+    const completion = new TestCompletion(messages);
+    
+    // 4. 存储累积内容和JSON对象
+    let fullContent = "";
+    let jsonObject: any = null;
+    
+    // 5. 发送请求并处理流式响应
+    const result = await completion.sendMessage(null, (content) => {
+        fullContent = content;
+        if (onMessage) {
+            onMessage(content);
+        }
+    });
+    
+    // 6. 最终JSON提取
+    try {
+        jsonObject = extractJSON(fullContent);
+        if (!jsonObject) {
+            throw new Error("无法从响应中提取有效的JSON");
+        }
+    } catch (e) {
+        console.error("JSON解析失败:", e);
+        console.log("原始内容:", fullContent);
+        throw new Error("生成的响应不符合JSON格式");
+    }
+    
+    return jsonObject;
+}
+
+export function extractJSON(str: string) {
+    // 首先尝试直接解析整个字符串
+    try {
+        const obj = JSON.parse(str);
+        return obj;
+    } catch (e) {
+        // 如果直接解析失败,尝试提取JSON部分
+    }
+
+    // 提取JSON的备用方法
+    let firstBrace = str.indexOf('{');
+    let lastBrace = str.lastIndexOf('}');
+    
+    if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
+        return null;
+    }
+    
+    try {
+        const jsonStr = str.substring(firstBrace, lastBrace + 1);
+        return JSON.parse(jsonStr);
+    } catch (e) {
+        return null;
+    }
+}

+ 432 - 0
ai-assisant/src/lib/cloud/ncloud.ts

@@ -0,0 +1,432 @@
+// 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;
+    }
+
+    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
+    }
+}

+ 3 - 1
ai-assisant/src/modules/crm/mobile/mobile.routes.ts

@@ -4,6 +4,7 @@ export const MOBILE_ROUTES: Routes = [
       {
         path: 'decision',
         loadComponent: () => import('./page-crm-decision/page-crm-decision').then(m => m.PageCrmDecision)
+        
       },
       {
         path: 'training',
@@ -11,7 +12,8 @@ export const MOBILE_ROUTES: Routes = [
       },
       {
         path: 'data',
-        loadComponent: () => import('./page-crm-data/page-crm-data').then(m => m.PageCrmData)
+      // 修改为正确的组件类名
+        loadComponent: () => import('./page-crm-data/page-crm-data').then(m => m.PageCrmData)   
       },
        {
         path: 'image',

+ 503 - 1
ai-assisant/src/modules/crm/mobile/page-crm-data/page-crm-data.html

@@ -1 +1,503 @@
-<p>page-crm-data works!</p>
+<!-- page-crm-data.component.html -->
+<div class="container">
+  <div class="decor-circle circle-1"></div>
+  <div class="decor-circle circle-2"></div>
+  
+  <!-- 顶部导航 -->
+  <div class="header">
+    <button class="back-btn" aria-label="返回">
+        <i class="fas fa-arrow-left"></i>
+    </button>
+    <h1 class="title">数据训练中心</h1>
+  </div>
+  
+  <div class="page-header">
+    <h1 class="page-title">AI数据训练中心</h1>
+    <p class="page-subtitle">上传数据、训练模型、优化结果</p>
+  </div>
+  
+  <!-- 上传控制台 -->
+  <div class="card upload-console">
+    <div class="card-header">
+      <div>
+        <div class="card-title">上传控制台</div>
+        <div class="card-subtitle">选择数据类型和输入方式</div>
+      </div>
+    </div>
+    
+    <div class="source-tags">
+      <div *ngFor="let tag of sourceTags" 
+           class="source-tag" 
+           [class.active]="tag.active"
+           (click)="handleTagClick(tag)"
+           (mouseenter)="tag.hover = true"
+           (mouseleave)="tag.hover = false">
+        <i *ngIf="tag.icon" class="fas fa-{{tag.icon}}"></i>
+        {{tag.name}}
+      </div>
+    </div>
+    <!-- 新增四个可点击标签 -->
+    <!-- <div class="source-tag clickable-tag" (click)="openModal('meeting')">
+      <i class="fas fa-calendar-alt"></i> 会议记录
+    </div>
+    <div class="source-tag clickable-tag" (click)="openModal('contract')">
+      <i class="fas fa-file-signature"></i> 客户合同
+    </div>
+    <div class="source-tag clickable-tag" (click)="openModal('feedback')">
+      <i class="fas fa-comment-dots"></i> 反馈表
+    </div>
+    <div class="source-tag clickable-tag" (click)="openModal('training')">
+      <i class="fas fa-chalkboard-teacher"></i> 培训资料
+    </div> -->
+  </div>
+  <!-- 新增模态框结构 -->
+<div class="modal-overlay" *ngIf="showModal" (click)="closeModal()">
+  <div class="modal-content" (click)="$event.stopPropagation()">
+    <button class="modal-close" (click)="closeModal()">
+      <i class="fas fa-times"></i>
+    </button>
+    <h3 class="modal-title">{{modalTitle}}</h3>
+    <div class="modal-body">
+      <ng-container [ngSwitch]="modalType">
+        <div *ngSwitchCase="'meeting'">
+          <p id="meeting-desc">请上传会议记录文件或输入会议内容:</p>
+          <label for="meeting-content" class="sr-only">会议内容</label>
+          <textarea id="meeting-content" class="modal-textarea" 
+                    placeholder="输入会议内容..." 
+                    aria-describedby="meeting-desc"></textarea>
+          <label for="meeting-file" class="file-upload-label">
+            <span class="sr-only">会议记录文件</span>
+            <input id="meeting-file" type="file" class="modal-file-input" 
+                   aria-describedby="meeting-desc">
+          </label>
+        </div>
+        <div *ngSwitchCase="'contract'">
+          <p id="contract-desc">请上传客户合同文件:</p>
+          <label for="contract-file" class="file-upload-label">
+            <span class="sr-only">客户合同文件</span>
+            <input id="contract-file" type="file" class="modal-file-input" 
+                   aria-describedby="contract-desc">
+          </label>
+          <div class="contract-info">
+            <label for="contract-number">合同编号</label>
+            <input id="contract-number" type="text" 
+                   placeholder="输入合同编号" 
+                   class="modal-input">
+            <label for="client-name">客户名称</label>
+            <input id="client-name" type="text" 
+                   placeholder="输入客户名称" 
+                   class="modal-input">
+          </div>
+        </div>
+        <div *ngSwitchCase="'feedback'">
+          <p id="feedback-desc">请选择反馈类型并上传文件:</p>
+          <label for="feedback-type">反馈类型</label>
+          <select id="feedback-type" class="modal-select" 
+                  aria-describedby="feedback-desc">
+            <option value="customer">客户反馈</option>
+            <option value="internal">内部反馈</option>
+            <option value="product">产品反馈</option>
+          </select>
+          <label for="feedback-file" class="file-upload-label">
+            <span class="sr-only">反馈文件</span>
+            <input id="feedback-file" type="file" class="modal-file-input" 
+                   aria-describedby="feedback-desc">
+          </label>
+        </div>
+        <div *ngSwitchCase="'training'">
+          <p id="training-desc">请上传培训资料:</p>
+          <label for="training-file" class="file-upload-label">
+            <span class="sr-only">培训资料文件</span>
+            <input id="training-file" type="file" class="modal-file-input" 
+                   aria-describedby="training-desc">
+          </label>
+          <div class="training-info">
+            <label for="training-topic">培训主题</label>
+            <input id="training-topic" type="text" 
+                   placeholder="输入培训主题" 
+                   class="modal-input">
+            <label for="training-date">培训日期</label>
+            <input id="training-date" type="date" 
+                   class="modal-input"
+                   aria-label="选择培训日期">
+          </div>
+        </div>
+      </ng-container>
+    </div>
+    <!-- 添加到modal-overlay结构后面 -->
+<div class="tag-selector-overlay" *ngIf="showTagSelector" (click)="closeTagSelector()">
+  <div class="tag-selector-container" (click)="$event.stopPropagation()">
+    <div class="tag-selector-header">
+      <h3>选择标签类型</h3>
+      <button class="tag-selector-close" (click)="closeTagSelector()">
+        <i class="fas fa-times"></i>
+      </button>
+    </div>
+    
+    <div class="tag-selector-body">
+      <div class="tag-scroll-container">
+        <div *ngFor="let tag of availableTags" 
+             class="tag-option"
+             [class.selected]="isTagSelected(tag)"
+             (click)="toggleTagSelection(tag)">
+          <i class="fas fa-{{tag.icon}}"></i>
+          {{tag.name}}
+          <span class="checkmark" *ngIf="isTagSelected(tag)">
+            <i class="fas fa-check"></i>
+          </span>
+        </div>
+      </div>
+    </div>
+    
+    <div class="tag-selector-footer">
+      <button class="tag-selector-btn cancel" (click)="closeTagSelector()">
+        取消
+      </button>
+      <button class="tag-selector-btn confirm" (click)="confirmTagSelection()">
+        确认选择
+      </button>
+    </div>
+  </div>
+</div>
+    <div class="modal-footer">
+      <button class="modal-button cancel" (click)="closeModal()">取消</button>
+      <button class="modal-button confirm" (click)="confirmUpload()">确认上传</button>
+    </div>
+  </div>
+</div>
+    
+    <div class="custom-tag-form">
+      <input type="text" class="custom-tag-input" 
+             [(ngModel)]="newTag" 
+             placeholder="输入新数据类型..."
+             (keyup.enter)="addTag()">
+             <button class="add-tag-btn" (click)="openTagSelector()">
+              添加标签
+            </button>
+    </div>
+    
+    <div class="upload-method-selector">
+      <div class="method-title">选择输入方式</div>
+      <div class="method-options">
+        <div *ngFor="let method of uploadMethods" 
+             class="method-option" 
+             [class.active]="method.active"
+             (click)="selectMethod(method)">
+          <div class="method-icon">
+            <i class="fas fa-{{method.icon}}"></i>
+          </div>
+          <div>{{method.name}}</div>
+        </div>
+      </div>
+    </div>
+    
+    <div class="upload-action">
+      <button class="upload-button" (click)="uploadData()">
+        <i class="fas fa-cloud-upload-alt"></i> 上传数据
+      </button>
+    </div>
+    
+    <div class="upload-status" *ngIf="uploadStatus.visible" 
+         [class.error]="!uploadStatus.success">
+      <div [innerHTML]="uploadStatus.message"></div>
+      <div class="upload-progress">
+        <div class="progress-bar" [style.width]="uploadStatus.progress + '%'"></div>
+      </div>
+    </div>
+  </div>
+  
+  <!-- 数据看板 -->
+  <div class="card">
+    <div class="card-header">
+      <div>
+        <div class="card-title">数据看板</div>
+        <div class="card-subtitle">今日处理状态</div>
+      </div>
+      <div class="alert-badge">3</div>
+    </div>
+    
+    <div class="stats-container">
+      <div class="stat-item">
+        <div class="progress-ring">
+          <div class="progress-bg">
+            <div class="progress-inner">128</div>
+          </div>
+        </div>
+        <div class="stat-label">处理量</div>
+      </div>
+      
+      <div class="stat-item">
+        <div class="stat-value">85%</div>
+        <div class="stat-label">质量评分</div>
+        <div class="quality-bar">
+          <div class="quality-fill"></div>
+        </div>
+      </div>
+    </div>
+    
+   <!-- 每日数据记录部分 - 保持不变 -->
+<div class="data-record">
+  <div class="record-header">
+    <div class="card-title">每日数据记录</div>
+    <a class="view-all" (click)="openTrainingDataModal()">
+      查看全部 <i class="fas fa-arrow-right"></i>
+    </a>
+  </div>
+  
+  <div class="record-list">
+    @for (stat of dashboardStats; track stat.label) {
+      <div class="record-item">
+        <div class="record-title">
+          <i class="fas fa-{{stat.icon}}"></i> {{stat.label}}
+        </div>
+        <div class="record-value">{{stat.value}}</div>
+        <div class="record-trend" [class.trend-up]="stat.trendUp" [class.trend-down]="!stat.trendUp">
+          <i class="fas fa-arrow-{{stat.trendUp ? 'up' : 'down'}} trend-icon"></i> {{stat.trend}}%
+        </div>
+      </div>
+    }
+  </div>
+</div>
+
+<!-- 数据管理模态框 - 修改后的完整版本 -->
+@if (showDailyDataModal) {
+<div class="daily-data-overlay" (click)="closeDailyDataModal()"></div>
+
+<div class="daily-data-modal">
+  <button class="daily-data-close" (click)="closeTrainingDataModal()">
+    <i class="fas fa-times"></i>
+  </button>
+  
+  <div class="modal-header">
+    <h3>训练数据管理</h3>
+    <button class="add-btn" (click)="prepareNewItem()">
+      <i class="fas fa-plus"></i> 新增数据
+    </button>
+  </div>
+  
+  <div class="modal-search">
+    <input type="text" placeholder="搜索..." 
+           [(ngModel)]="searchQuery"
+           (input)="searchTrainingData()">
+    <button>
+      <i class="fas fa-search"></i>
+    </button>
+  </div>
+  
+  <div class="modal-content">
+    @for (item of filteredTrainingData; track item.id) {
+    <div class="data-item">
+      <div class="data-main">
+        <div class="data-title">{{ item.get('title') || '未命名数据' }}</div>
+        <div class="data-meta">
+          <span class="data-type">
+            <i class="fas fa-{{getTypeIcon(item.get('type'))}}"></i>
+            {{ getTypeName(item.get('type')) }}
+          </span>
+          <span class="data-date">
+            <i class="far fa-calendar"></i>
+            {{ item.createdAt | date:'yyyy-MM-dd' }}
+          </span>
+        </div>
+      </div>
+      <div class="data-actions">
+        <button (click)="prepareEditItem(item)"><i class="fas fa-edit"></i></button>
+        <button (click)="deleteTrainingItem(item)"><i class="fas fa-trash"></i></button>
+      </div>
+    </div>
+    }
+    @empty {
+      <div class="no-data">暂无数据</div>
+    }
+  </div>
+  
+  <div class="modal-actions">
+    <button class="cancel-btn" (click)="closeDailyDataModal()">
+      <i class="fas fa-times"></i> 关闭
+    </button>
+  </div>
+</div>
+}
+
+<!-- 数据编辑模态框 -->
+@if (showDataModal) {
+<div class="modal-overlay" (click)="showDataModal = false">
+  <div class="modal-content" (click)="$event.stopPropagation()">
+    <button class="modal-close" (click)="showDataModal = false">
+      <i class="fas fa-times"></i>
+    </button>
+    <h3>{{ isEditing ? '编辑数据' : '新增数据' }}</h3>
+    
+    <div class="form-group">
+      <label>标题</label>
+      <input type="text" [(ngModel)]="currentTrainingItem.data['title']" 
+             placeholder="输入数据标题">
+    </div>
+    
+    <div class="form-group">
+      <label for="data-type">数据类型</label>
+      <select id="data-type" [(ngModel)]="currentTrainingItem.data['type']" aria-label="选择数据类型">
+        <option value="meeting">会议记录</option>
+        <option value="contract">客户合同</option>
+        <option value="feedback">反馈表</option>
+        <option value="training">培训资料</option>
+      </select>
+    </div>
+    
+    <div class="form-group">
+      <label>数据内容</label>
+      <textarea [(ngModel)]="currentTrainingItem.data['content']" 
+                placeholder="输入数据内容"></textarea>
+    </div>
+    
+    <div class="form-group">
+      <label>备注</label>
+      <textarea [(ngModel)]="currentTrainingItem.data['notes']" 
+                placeholder="输入备注信息"></textarea>
+    </div>
+    
+    <div class="form-footer">
+      <button class="cancel-btn" (click)="showDataModal = false">
+        <i class="fas fa-times"></i> 取消
+      </button>
+      <button class="confirm-btn" (click)="saveTrainingItem()">
+        <i class="fas fa-check"></i> 保存
+      </button>
+    </div>
+  </div>
+</div>
+}
+  
+  <!-- 历史数据上传记录 -->
+  <div class="card">
+    <div class="card-header">
+      <div>
+        <div class="card-title">历史数据上传记录</div>
+        <div class="card-subtitle">最近上传的文件</div>
+      </div>
+    </div>
+    
+    <!-- 搜索区域 -->
+    <div class="search-container">
+      <div class="search-box">
+        <i class="fas fa-search"></i>
+        <input type="text" class="search-input" 
+               [(ngModel)]="searchTerm" 
+               (input)="filterHistory()"
+               placeholder="搜索文件名、类型或日期...">
+      </div>
+    </div>
+    
+    <div class="history-list">
+      <div *ngFor="let item of filteredHistory" class="history-item">
+        <div class="history-icon">
+          <i class="fas fa-file-{{item.type}}"></i>
+        </div>
+        <div class="history-content">
+          <div class="history-name">{{item.name}}</div>
+          <div class="history-meta">
+            <div class="history-size">
+              <i class="fas fa-database"></i> {{item.size}}MB
+            </div>
+            <div class="history-date">
+              <i class="far fa-calendar"></i> {{item.date}}
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  
+  <!-- 训练结果展示 -->
+  <div class="card">
+    <div class="card-header">
+      <div>
+        <div class="card-title">训练结果展示</div>
+        <div class="card-subtitle">模型训练状态与结果</div>
+      </div>
+    </div>
+    
+    <div class="training-results">
+      <div *ngFor="let result of trainingResults" 
+      class="result-item"
+      (click)="onResultClick(result)"> 
+        <div class="result-icon">
+          <i class="fas fa-{{result.icon}}"></i>
+        </div>
+        <div class="result-content">
+          <div class="result-title">{{result.title}}</div>
+          <div class="result-meta">
+            <div class="result-date">
+              <i class="far fa-clock"></i> 训练时间: {{result.time}}
+            </div>
+            <div class="result-status" [class]="'status-' + result.status">
+              {{ 
+                result.status === 'completed' ? '已完成' : 
+                result.status === 'training' ? '训练中' : '失败'
+              }}
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- 在训练结果展示卡片后面添加这个模态框 -->
+<!-- AI对话模态框 -->
+<div class="ai-dialog-overlay" *ngIf="showAIDialog" (click)="closeAIDialog()">
+  <div class="ai-dialog-container" (click)="$event.stopPropagation()">
+    <div class="ai-dialog-header">
+      <h3>{{ currentAIModel?.title }} - AI智能助手</h3>
+      <button class="ai-dialog-close" (click)="closeAIDialog()">
+        <i class="fas fa-times"></i>
+      </button>
+    </div>
+    
+    <div class="ai-dialog-content">
+      <div class="ai-messages" #messageContainer>
+        <div *ngFor="let message of aiMessages" class="message" [class.user]="message.role === 'user'">
+          <div class="message-avatar">
+            <i *ngIf="message.role === 'assistant'" class="fas fa-robot"></i>
+            <i *ngIf="message.role === 'user'" class="fas fa-user"></i>
+          </div>
+          <div class="message-content">
+            <div class="message-text" [innerHTML]="message.content"></div>
+            <div class="message-time">{{ message.time | date:'HH:mm' }}</div>
+          </div>
+        </div>
+        
+        <div *ngIf="isAIThinking" class="ai-thinking">
+          <div class="typing-indicator">
+            <span></span>
+            <span></span>
+            <span></span>
+          </div>
+        </div>
+      </div>
+      
+      <div class="ai-input-container">
+        <textarea 
+          [(ngModel)]="userMessage" 
+          (keydown.enter)="sendMessage($event)"
+          placeholder="输入您的问题..."
+          class="ai-input"
+          rows="1"
+          #messageInput
+        ></textarea>
+        <button class="ai-send-btn" (click)="sendMessage()" [disabled]="!userMessage.trim()">
+          <i class="fas fa-paper-plane"></i>
+        </button>
+      </div>
+    </div>
+    
+    <div class="ai-dialog-footer">
+      <div class="ai-suggestions">
+        <span>快捷提问:</span>
+        <button *ngFor="let suggestion of aiSuggestions" 
+                (click)="selectSuggestion(suggestion)">
+          {{ suggestion }}
+        </button>
+      </div>
+    </div>
+  </div>
+</div>
+

+ 2108 - 0
ai-assisant/src/modules/crm/mobile/page-crm-data/page-crm-data.scss

@@ -0,0 +1,2108 @@
+/* page-crm-data.component.scss */
+:host {
+    display: block;
+    background-color: #f5f8ff;
+    background-image: linear-gradient(135deg, #f5f8ff 0%, #e8ecff 100%);
+    color: #333333;
+    line-height: 1.6;
+    padding: 16px;
+    max-width: 500px;
+    margin: 0 auto;
+    position: relative;
+    min-height: 100vh;
+    overflow-x: hidden;
+    font-family: 'Segoe UI', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
+  }
+  
+  /* 变量定义 */
+  $primary: #4285f4;
+  $primary-light: rgba(66, 133, 244, 0.1);
+  $primary-dark: #3367d6;
+  $text-dark: #0a192f;
+  $text-medium: #333333;
+  $text-light: #8898aa;
+  $background: #ffffff;
+  $card-bg: #f8f9fa;
+  $border: #e6e9ed;
+  $success: #34a853;
+  $warning: #fbbc05;
+  $error: #ea4335;
+  $shadow: 0 8px 24px rgba(0, 0, 0, 0.05);
+  
+  * {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+  }
+  
+  /* 头部导航 */
+  .header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 16px 0;
+    margin-bottom: 20px;
+    position: sticky;
+    top: 0;
+    background: transparent;
+    z-index: 100;
+  }
+  
+  .back-btn {
+    width: 40px;
+    height: 40px;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: $background;
+    border: none;
+    color: $text-dark;
+    font-size: 18px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    box-shadow: $shadow;
+    
+    &:hover {
+      background: $primary-light;
+      color: $primary;
+    }
+  }
+  
+  .title {
+    font-size: 24px;
+    font-weight: 700;
+    color: $text-dark;
+    text-align: center;
+    flex-grow: 1;
+    text-shadow: 0 2px 4px rgba(0,0,0,0.05);
+  }
+  
+  /* 卡片样式 */
+  .card {
+    background: $background;
+    border-radius: 20px;
+    padding: 24px;
+    margin-bottom: 20px;
+    box-shadow: $shadow;
+    position: relative;
+    overflow: hidden;
+    border: 1px solid rgba(66, 133, 244, 0.1);
+    transition: transform 0.3s ease, box-shadow 0.3s ease;
+    
+    &:hover {
+      transform: translateY(-5px);
+      box-shadow: 0 12px 30px rgba(66, 133, 244, 0.15);
+    }
+  }
+  
+  .card-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
+  }
+  
+  .card-title {
+    font-size: 18px;
+    font-weight: 700;
+    color: $text-dark;
+  }
+  
+  .card-subtitle {
+    font-size: 14px;
+    color: $text-light;
+    margin-top: 4px;
+  }
+  
+  /* 上传控制台 */
+  .upload-console {
+    position: relative;
+    min-height: 220px;
+  }
+  
+  .source-tags {
+    display: flex;
+    gap: 10px;
+    margin-top: 20px;
+    flex-wrap: wrap;
+  }
+  
+  .source-tag {
+    background: #f0f4f8;
+    padding: 8px 16px;
+    border-radius: 20px;
+    font-size: 14px;
+    color: $text-dark;
+    transition: all 0.2s ease;
+    border: 1px solid transparent;
+    cursor: pointer;
+    box-shadow: 0 2px 5px rgba(0,0,0,0.05);
+    
+    &.active {
+      border-color: $primary;
+      box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.3);
+      color: $primary;
+      font-weight: 600;
+      background: rgba(66, 133, 244, 0.08);
+    }
+  }
+  
+  /* 自定义标签区域 */
+  .custom-tag-form {
+    display: flex;
+    margin-top: 25px;
+    gap: 10px;
+    align-items: center;
+  }
+  
+  .custom-tag-input {
+    flex: 1;
+    padding: 12px 18px;
+    border-radius: 20px;
+    border: 1px solid $border;
+    outline: none;
+    font-size: 14px;
+    transition: border-color 0.3s;
+    background: $card-bg;
+    box-shadow: inset 0 2px 4px rgba(0,0,0,0.03);
+    
+    &:focus {
+      border-color: $primary;
+      box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.2);
+    }
+  }
+  
+  .add-tag-btn {
+    background: $primary;
+    color: white;
+    border: none;
+    border-radius: 20px;
+    padding: 12px 24px;
+    font-size: 14px;
+    font-weight: 600;
+    cursor: pointer;
+    transition: background 0.3s;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    box-shadow: 0 4px 10px rgba(66, 133, 244, 0.3);
+    
+    &:hover {
+      background: $primary-dark;
+      transform: translateY(-2px);
+      box-shadow: 0 6px 15px rgba(66, 133, 244, 0.4);
+    }
+  }
+  
+  /* 数据看板 */
+  .stats-container {
+    display: flex;
+    justify-content: space-between;
+    gap: 15px;
+    margin-top: 15px;
+  }
+  
+  .stat-item {
+    flex: 1;
+    text-align: center;
+    padding: 15px;
+    border-radius: 16px;
+    background: $background;
+    position: relative;
+    box-shadow: 0 4px 10px rgba(0,0,0,0.03);
+    border: 1px solid rgba(66, 133, 244, 0.08);
+  }
+  
+  .stat-value {
+    font-size: 24px;
+    font-weight: 700;
+    color: $text-dark;
+    margin: 10px 0;
+  }
+  
+  .stat-label {
+    font-size: 13px;
+    color: $text-light;
+  }
+  
+  .progress-ring {
+    position: relative;
+    width: 80px;
+    height: 80px;
+    margin: 0 auto;
+  }
+  
+  .progress-bg {
+    width: 100%;
+    height: 100%;
+    border-radius: 50%;
+    background: conic-gradient($primary 0% 75%, $border 75% 100%);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  
+  .progress-inner {
+    width: 70px;
+    height: 70px;
+    border-radius: 50%;
+    background: $card-bg;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 18px;
+    font-weight: 700;
+    color: $text-dark;
+  }
+  
+  .quality-bar {
+    height: 8px;
+    background: $border;
+    border-radius: 4px;
+    margin-top: 15px;
+    overflow: hidden;
+    position: relative;
+  }
+  
+  .quality-fill {
+    height: 100%;
+    width: 85%;
+    background: linear-gradient(90deg, $primary, $primary-dark);
+    border-radius: 4px;
+    position: relative;
+    
+    &::after {
+      content: '';
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      background: linear-gradient(90deg, 
+        transparent, 
+        rgba(255, 255, 255, 0.5),
+        transparent);
+      animation: wave 2s linear infinite;
+    }
+  }
+  
+  @keyframes wave {
+    0% { transform: translateX(-100%); }
+    100% { transform: translateX(100%); }
+  }
+  
+  .alert-badge {
+    position: absolute;
+    top: -8px;
+    right: -8px;
+    width: 24px;
+    height: 24px;
+    border-radius: 50%;
+    background: $error;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: white;
+    font-size: 12px;
+    font-weight: bold;
+    animation: pulse 1.5s infinite;
+  }
+  
+  @keyframes pulse {
+    0% { box-shadow: 0 0 0 0 rgba(234, 67, 53, 0.7); }
+    70% { box-shadow: 0 0 0 8px rgba(234, 67, 53, 0); }
+    100% { box-shadow: 0 0 0 0 rgba(234, 67, 53, 0); }
+  }
+  
+  /* 训练结果展示 */
+  .training-results {
+    margin-top: 25px;
+  }
+  
+  .result-item {
+    display: flex;
+    align-items: center;
+    padding: 15px 0;
+    border-bottom: 1px solid $border;
+    transition: all 0.2s ease;
+    
+    &:hover {
+      background: rgba(66, 133, 244, 0.03);
+      border-radius: 12px;
+      padding: 15px;
+    }
+    
+    &:last-child {
+      border-bottom: none;
+    }
+  }
+  
+  .result-icon {
+    width: 40px;
+    height: 40px;
+    border-radius: 12px;
+    background: $primary-light;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: $primary;
+    margin-right: 15px;
+    font-size: 18px;
+  }
+  
+  .result-content {
+    flex: 1;
+  }
+  
+  .result-title {
+    font-weight: 600;
+    color: $text-dark;
+    margin-bottom: 4px;
+  }
+  
+  .result-meta {
+    display: flex;
+    font-size: 13px;
+    color: $text-light;
+    flex-wrap: wrap;
+    gap: 10px;
+  }
+  
+  .result-date {
+    display: flex;
+    align-items: center;
+    gap: 5px;
+  }
+  
+  .result-status {
+    padding: 4px 10px;
+    border-radius: 20px;
+    font-size: 12px;
+    font-weight: 600;
+    
+    &.status-completed {
+      background: rgba(52, 168, 83, 0.1);
+      color: $success;
+    }
+    
+    &.status-training {
+      background: rgba(251, 188, 5, 0.1);
+      color: $warning;
+    }
+    
+    &.status-error {
+      background: rgba(234, 67, 53, 0.1);
+      color: $error;
+    }
+  }
+  
+  /* 数据记录部分 */
+  .data-record {
+    margin-top: 25px;
+  }
+  
+  .record-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 15px;
+  }
+  
+  .view-all {
+    color: $primary;
+    font-size: 14px;
+    text-decoration: none;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    gap: 5px;
+  }
+  
+  .record-list {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    gap: 15px;
+  }
+  
+  .record-item {
+    background: $card-bg;
+    border-radius: 16px;
+    padding: 15px;
+    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.03);
+    border: 1px solid rgba(66, 133, 244, 0.08);
+    transition: all 0.3s ease;
+    
+    &:hover {
+      transform: translateY(-3px);
+      box-shadow: 0 6px 15px rgba(66, 133, 244, 0.15);
+    }
+  }
+  
+  .record-title {
+    font-size: 14px;
+    font-weight: 600;
+    color: $text-dark;
+    margin-bottom: 10px;
+    display: flex;
+    align-items: center;
+    gap: 5px;
+  }
+  
+  .record-value {
+    font-size: 22px;
+    font-weight: 700;
+    color: $primary;
+  }
+  
+  .record-trend {
+    display: flex;
+    align-items: center;
+    font-size: 12px;
+    margin-top: 5px;
+    
+    &.trend-up {
+      color: $success;
+    }
+    
+    &.trend-down {
+      color: $error;
+    }
+  }
+  
+  .trend-icon {
+    margin-right: 4px;
+  }
+  
+  /* 历史数据记录 */
+  .history-list {
+    max-height: 200px;
+    overflow-y: auto;
+    padding-right: 8px;
+  }
+  
+  .history-item {
+    display: flex;
+    align-items: center;
+    padding: 12px 0;
+    border-bottom: 1px solid $border;
+    transition: all 0.2s ease;
+    
+    &:hover {
+      background: rgba(66, 133, 244, 0.03);
+      border-radius: 12px;
+      padding: 12px;
+    }
+  }
+  
+  .history-icon {
+    width: 36px;
+    height: 36px;
+    border-radius: 10px;
+    background: $primary-light;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: $primary;
+    margin-right: 12px;
+    font-size: 16px;
+  }
+  
+  .history-content {
+    flex: 1;
+  }
+  
+  .history-name {
+    font-weight: 500;
+    margin-bottom: 4px;
+  }
+  
+  .history-meta {
+    display: flex;
+    font-size: 12px;
+    color: $text-light;
+    gap: 15px;
+  }
+  
+  .history-size {
+    display: flex;
+    align-items: center;
+    gap: 3px;
+  }
+  
+  .history-date {
+    color: $text-medium;
+    display: flex;
+    align-items: center;
+    gap: 3px;
+  }
+  
+//   /* 页面标题装饰 */
+//   .page-header {
+//     text-align: center;
+//     margin-bottom: 25px;
+//   }
+  
+//   .page-title {
+//     font-size: 28px;
+//     font-weight: 800;
+//     color: $text-dark;
+//     margin-bottom: 8px;
+//     background: linear-gradient(90deg, #3367d6, #4285f4);
+//     //webkit-background-clip: text;
+//     -webkit-text-fill-color: transparent;
+//     position: relative;
+//     display: inline-block;
+//   }
+  
+//   .page-subtitle {
+//     color: $text-light;
+//     font-size: 16px;
+//     max-width: 300px;
+//     margin: 0 auto;
+//   }
+  
+//   /* 装饰元素 */
+//   .decor-circle {
+//     position: absolute;
+//     width: 200px;
+//     height: 200px;
+//     border-radius: 50%;
+//     background: linear-gradient(135deg, rgba(66, 133, 244, 0.1) 0%, rgba(66, 133, 244, 0.05) 100%);
+//     z-index: -1;
+//   }
+/* ===== 修复标题显示问题 ===== */
+.page-header {
+    position: relative;
+    z-index: 10; /* 确保标题在装饰层之上 */
+    margin: 0 0 20px 0;
+  }
+  
+  .page-title {
+    color: #000000; /* 强制黑色字体 */
+    background: white; /* 白色背景 */
+    display: inline-block;
+    padding: 8px 20px;
+    border-radius: 30px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+    position: relative;
+    z-index: 20; /* 高于装饰元素 */
+    font-weight: 600; /* 可选:加粗字体 */
+  }
+  
+  .page-subtitle {
+    color: #555555; /* 深灰色副标题 */
+    background: white;
+    display: inline-block;
+    padding: 5px 15px;
+    border-radius: 20px;
+    margin-top: 8px;
+  }
+  
+  /* 确保装饰圆圈不会覆盖标题 */
+  .decor-circle {
+    z-index: 1 !important; /* 强制装饰元素在底层 */
+  }
+  
+  .circle-1 {
+    top: 10%;
+    left: -50px;
+  }
+  
+  .circle-2 {
+    bottom: 10%;
+    right: -50px;
+  }
+  
+  /* 新增搜索框样式 */
+  .search-container {
+    margin-bottom: 20px;
+    position: relative;
+  }
+  
+  .search-box {
+    display: flex;
+    align-items: center;
+    background: $card-bg;
+    border-radius: 30px;
+    padding: 8px 16px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+    border: 1px solid $border;
+    
+    i {
+      color: $text-light;
+      margin-right: 10px;
+      font-size: 18px;
+    }
+  }
+  
+  .search-input {
+    flex: 1;
+    border: none;
+    background: transparent;
+    padding: 10px 0;
+    font-size: 15px;
+    color: $text-dark;
+    outline: none;
+    
+    &::placeholder {
+      color: $text-light;
+    }
+  }
+  
+  /* 上传控制台新样式 */
+  .upload-method-selector {
+    margin: 25px 0;
+  }
+  
+  .method-title {
+    font-size: 16px;
+    font-weight: 600;
+    margin-bottom: 12px;
+    color: $text-dark;
+  }
+  
+  .method-options {
+    display: flex;
+    gap: 15px;
+    flex-wrap: wrap;
+  }
+  
+  .method-option {
+    flex: 1;
+    min-width: 100px;
+    padding: 15px;
+    background: $card-bg;
+    border-radius: 16px;
+    text-align: center;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    border: 1px solid $border;
+    
+    &:hover {
+      transform: translateY(-3px);
+      box-shadow: 0 6px 15px rgba(66, 133, 244, 0.1);
+    }
+    
+    &.active {
+      border-color: $primary;
+      background: $primary-light;
+      color: $primary;
+      font-weight: 600;
+    }
+  }
+  
+  .method-icon {
+    font-size: 24px;
+    margin-bottom: 10px;
+    color: $primary;
+  }
+  
+  .upload-action {
+    display: flex;
+    justify-content: center;
+    margin-top: 15px;
+  }
+  
+  .upload-button {
+    background: $primary;
+    color: white;
+    border: none;
+    border-radius: 30px;
+    padding: 15px 40px;
+    font-size: 16px;
+    font-weight: 600;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    box-shadow: 0 6px 15px rgba(66, 133, 244, 0.3);
+    
+    &:hover {
+      background: $primary-dark;
+      transform: translateY(-2px);
+      box-shadow: 0 8px 20px rgba(66, 133, 244, 0.4);
+    }
+    
+    &:active {
+      transform: translateY(0);
+    }
+  }
+  
+  /* 上传状态指示器 */
+  .upload-status {
+    margin-top: 20px;
+    padding: 15px;
+    border-radius: 16px;
+    background: rgba(52, 168, 83, 0.1);
+    color: $success;
+    text-align: center;
+    
+    &.error {
+      background: rgba(234, 67, 53, 0.1);
+      color: $error;
+    }
+  }
+  
+  .upload-progress {
+    height: 6px;
+    background: $border;
+    border-radius: 3px;
+    margin-top: 10px;
+    overflow: hidden;
+  }
+  
+  .progress-bar {
+    height: 100%;
+    width: 0%;
+    background: linear-gradient(90deg, $primary, $primary-dark);
+    border-radius: 3px;
+    transition: width 0.3s ease;
+  }
+  //7.1
+  /* 原有样式保持不变... */
+
+/* 新增可点击标签样式 */
+.clickable-tag {
+  cursor: pointer;
+  transition: all 0.3s ease;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  
+  &:hover {
+    background-color: rgba(0, 123, 255, 0.1);
+    transform: translateY(-2px);
+  }
+  
+  i {
+    font-size: 14px;
+  }
+}
+
+/* 模态框样式 */
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 1000;
+  backdrop-filter: blur(3px);
+}
+
+.modal-content {
+  background-color: white;
+  border-radius: 12px;
+  width: 90%;
+  max-width: 500px;
+  max-height: 80vh;
+  overflow-y: auto;
+  box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
+  padding: 20px;
+  position: relative;
+  animation: modalFadeIn 0.3s ease;
+}
+
+@keyframes modalFadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(-20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.modal-close {
+  position: absolute;
+  top: 15px;
+  right: 15px;
+  background: none;
+  border: none;
+  font-size: 20px;
+  cursor: pointer;
+  color: #666;
+  
+  &:hover {
+    color: #333;
+  }
+}
+
+.modal-title {
+  margin-top: 0;
+  margin-bottom: 20px;
+  color: #2c3e50;
+  font-size: 20px;
+}
+
+.modal-body {
+  margin-bottom: 20px;
+  
+  p {
+    margin-top: 0;
+    color: #555;
+  }
+}
+
+.modal-input, .modal-select, .modal-textarea, .modal-file-input {
+  width: 100%;
+  padding: 10px;
+  margin-bottom: 15px;
+  border: 1px solid #ddd;
+  border-radius: 6px;
+  font-size: 14px;
+  
+  &:focus {
+    outline: none;
+    border-color: #007bff;
+  }
+}
+
+.modal-textarea {
+  min-height: 100px;
+  resize: vertical;
+}
+
+.modal-file-input {
+  padding: 8px;
+}
+
+.modal-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+}
+
+.modal-button {
+  padding: 10px 20px;
+  border-radius: 6px;
+  border: none;
+  cursor: pointer;
+  font-weight: 500;
+  transition: all 0.2s ease;
+  
+  &.cancel {
+    background-color: #f1f1f1;
+    color: #333;
+    
+    &:hover {
+      background-color: #e0e0e0;
+    }
+  }
+  
+  &.confirm {
+    background-color: #007bff;
+    color: white;
+    
+    &:hover {
+      background-color: #0069d9;
+    }
+  }
+}
+
+.contract-info, .training-info {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 10px;
+  margin-bottom: 15px;
+}
+/* 添加在原有样式之后 */
+
+/* 可访问性相关样式 */
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  white-space: nowrap;
+  border-width: 0;
+}
+
+.file-upload-label {
+  display: block;
+  margin-bottom: 15px;
+  cursor: pointer;
+  
+  input[type="file"] {
+    width: 100%;
+  }
+}
+
+label:not(.sr-only):not(.file-upload-label) {
+  display: block;
+  margin-bottom: 8px;
+  font-size: 14px;
+  color: #555;
+  font-weight: 500;
+}
+/* 修复source-tag的悬停效果 */
+.source-tag {
+  /* 原有基础样式保持不变 */
+  background: #f0f4f8;
+  padding: 8px 16px;
+  border-radius: 20px;
+  font-size: 14px;
+  color: $text-dark;
+  transition: all 0.2s ease;
+  border: 1px solid transparent;
+  cursor: pointer;
+  box-shadow: 0 2px 5px rgba(0,0,0,0.05);
+  display: flex;
+  align-items: center;
+  gap: 8px;
+
+  /* 确保悬停效果优先级 */
+  &:hover {
+    background: #e0e0e0 !important;
+    transform: translateY(-2px) !important;
+    box-shadow: 0 4px 8px rgba(0,0,0,0.1) !important;
+  }
+
+  /* 活动状态 */
+  &.active {
+    border-color: $primary;
+    box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.3);
+    color: $primary;
+    font-weight: 600;
+    background: rgba(66, 133, 244, 0.08);
+    
+    /* 活动状态下的悬停效果 */
+    &:hover {
+      background: rgba(66, 133, 244, 0.15) !important;
+    }
+  }
+
+  /* 图标样式 */
+  i {
+    margin-right: 0; /* 改为使用gap布局 */
+    font-size: 14px;
+  }
+}
+.source-tag {
+  /* 原有基础样式保持不变 */
+  background: #f0f4f8;
+  padding: 8px 16px;
+  border-radius: 20px;
+  font-size: 14px;
+  color: $text-dark;
+  transition: all 0.3s ease; /* 增加过渡时间使效果更平滑 */
+  border: 1px solid transparent; /* 初始透明边框 */
+  cursor: pointer;
+  box-shadow: 0 2px 5px rgba(0,0,0,0.05);
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  position: relative; /* 为伪元素定位做准备 */
+
+  /* 新增悬停蓝色边框效果 */
+  &:hover {
+    border-color: $primary; /* 蓝色边框 */
+    box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.3); /* 外发光效果 */
+    
+    /* 如果想要更明显的发光效果,可以使用以下替代方案 */
+    /*
+    box-shadow: 0 0 0 2px $primary-light, 
+                0 4px 8px rgba(0,0,0,0.1);
+    */
+  }
+
+  /* 活动状态 */
+  &.active {
+    border-color: $primary;
+    box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.3);
+    color: $primary;
+    font-weight: 600;
+    background: rgba(66, 133, 244, 0.08);
+    
+    /* 活动状态下的悬停效果 */
+    &:hover {
+      background: rgba(66, 133, 244, 0.15);
+      box-shadow: 0 0 0 3px rgba(66, 133, 244, 0.4); /* 悬停时加强效果 */
+    }
+  }
+
+  /* 图标样式 */
+  i {
+    font-size: 14px;
+  }
+}
+
+// 
+/* 标签选择器样式 */
+.tag-selector-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 1001; /* 比模态框更高 */
+  backdrop-filter: blur(3px);
+}
+
+.tag-selector-container {
+  background-color: white;
+  border-radius: 16px;
+  width: 90%;
+  max-width: 400px;
+  max-height: 70vh;
+  overflow: hidden;
+  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+  animation: modalFadeIn 0.3s ease;
+}
+
+.tag-selector-header {
+  padding: 20px;
+  border-bottom: 1px solid #eee;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  
+  h3 {
+    margin: 0;
+    font-size: 18px;
+    color: $text-dark;
+  }
+}
+
+.tag-selector-close {
+  background: none;
+  border: none;
+  font-size: 20px;
+  color: $text-light;
+  cursor: pointer;
+  transition: color 0.2s;
+  
+  &:hover {
+    color: $primary;
+  }
+}
+
+.tag-selector-body {
+  padding: 10px 0;
+}
+
+.tag-scroll-container {
+  max-height: 50vh;
+  overflow-y: auto;
+  padding: 0 15px;
+}
+
+.tag-option {
+  padding: 12px 15px;
+  margin: 5px 0;
+  border-radius: 10px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  transition: all 0.2s;
+  position: relative;
+  border: 1px solid #eee;
+  
+  &:hover {
+    background-color: rgba(66, 133, 244, 0.1);
+    transform: translateX(5px);
+  }
+  
+  &.selected {
+    background-color: rgba(66, 133, 244, 0.15);
+    border-color: $primary;
+  }
+  
+  i {
+    margin-right: 10px;
+    color: $primary;
+    width: 20px;
+    text-align: center;
+  }
+}
+
+.checkmark {
+  margin-left: auto;
+  color: $primary;
+  font-size: 14px;
+}
+
+.tag-selector-footer {
+  display: flex;
+  justify-content: flex-end;
+  padding: 15px;
+  border-top: 1px solid #eee;
+  gap: 10px;
+}
+
+.tag-selector-btn {
+  padding: 10px 20px;
+  border-radius: 20px;
+  border: none;
+  cursor: pointer;
+  font-weight: 500;
+  transition: all 0.2s;
+  
+  &.cancel {
+    background-color: #f5f5f5;
+    color: $text-dark;
+    
+    &:hover {
+      background-color: #e0e0e0;
+    }
+  }
+  
+  &.confirm {
+    background-color: $primary;
+    color: white;
+    
+    &:hover {
+      background-color: $primary-dark;
+    }
+  }
+}
+.add-tag-btn {
+  position: relative; // 确保按钮在顶层
+  z-index: 1;
+}
+/* 在文件末尾添加以下内容 */
+
+/* 标签选择器样式 */
+.tag-selector-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 1001;
+  backdrop-filter: blur(3px);
+}
+
+.tag-selector-container {
+  background-color: white;
+  border-radius: 16px;
+  width: 90%;
+  max-width: 400px;
+  max-height: 70vh;
+  overflow: hidden;
+  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+}
+
+/* 确保按钮层级 */
+.add-tag-btn {
+  position: relative;
+  z-index: 1;
+}
+
+/* 模态框层级 */
+.modal-overlay {
+  z-index: 1000;
+}
+
+
+//<!-- 每日数据记录 
+/* 横屏数据查看模态框样式 */
+/* 每日数据记录卡片样式 */
+.data-record {
+  background: #fff;
+  border-radius: 12px;
+  padding: 16px;
+  margin-bottom: 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+
+  .record-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16px;
+
+    .card-title {
+      font-size: 18px;
+      font-weight: 600;
+      color: #333;
+    }
+
+    .view-all {
+      color: #1890ff;
+      font-size: 14px;
+      cursor: pointer;
+      display: flex;
+      align-items: center;
+      gap: 4px;
+
+      &:hover {
+        color: #40a9ff;
+      }
+
+      i {
+        font-size: 12px;
+      }
+    }
+  }
+
+  .record-list {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+    gap: 12px;
+
+    .record-item {
+      padding: 12px;
+      background: #f9f9f9;
+      border-radius: 8px;
+
+      .record-title {
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        font-size: 14px;
+        color: #666;
+        margin-bottom: 8px;
+
+        i {
+          color: #1890ff;
+        }
+      }
+
+      .record-value {
+        font-size: 20px;
+        font-weight: 600;
+        color: #333;
+        margin-bottom: 4px;
+      }
+
+      .record-trend {
+        font-size: 13px;
+        display: flex;
+        align-items: center;
+        gap: 4px;
+
+        &.trend-up {
+          color: #52c41a;
+        }
+
+        &.trend-down {
+          color: #f5222d;
+        }
+
+        .trend-icon {
+          font-size: 12px;
+        }
+      }
+    }
+  }
+}
+
+/* 每日数据模态框样式 */
+.daily-data-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.5);
+  z-index: 1000;
+  backdrop-filter: blur(3px);
+}
+
+.daily-data-modal {
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  width: 90%;
+  max-width: 800px;
+  max-height: 80vh;
+  background-color: #fff;
+  border-radius: 12px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+  z-index: 1001;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+
+  .daily-data-close {
+    position: absolute;
+    top: 16px;
+    right: 16px;
+    width: 32px;
+    height: 32px;
+    background: rgba(0, 0, 0, 0.05);
+    border: none;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    transition: all 0.2s;
+    z-index: 1;
+
+    &:hover {
+      background: rgba(0, 0, 0, 0.1);
+    }
+
+    i {
+      color: #666;
+      font-size: 16px;
+    }
+  }
+
+  .modal-header {
+    padding: 20px 24px;
+    border-bottom: 1px solid #f0f0f0;
+
+    h3 {
+      margin: 0;
+      font-size: 18px;
+      font-weight: 600;
+      color: #333;
+    }
+  }
+
+  .modal-search {
+    padding: 16px 24px;
+    display: flex;
+    border-bottom: 1px solid #f0f0f0;
+
+    input {
+      flex: 1;
+      padding: 8px 12px;
+      border: 1px solid #d9d9d9;
+      border-radius: 6px;
+      font-size: 14px;
+      transition: all 0.3s;
+
+      &:focus {
+        outline: none;
+        border-color: #1890ff;
+        box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
+      }
+    }
+
+    button {
+      margin-left: 12px;
+      padding: 0 16px;
+      background-color: #1890ff;
+      color: white;
+      border: none;
+      border-radius: 6px;
+      cursor: pointer;
+      transition: all 0.3s;
+
+      &:hover {
+        background-color: #40a9ff;
+      }
+
+      i {
+        font-size: 14px;
+      }
+    }
+  }
+
+  .modal-content {
+    flex: 1;
+    overflow-y: auto;
+    padding: 16px 0;
+
+    .modal-item {
+      padding: 16px 24px;
+      border-bottom: 1px solid #f5f5f5;
+      transition: background-color 0.2s;
+
+      &:hover {
+        background-color: #fafafa;
+      }
+
+      &:last-child {
+        border-bottom: none;
+      }
+
+      .item-date {
+        font-weight: 600;
+        margin-bottom: 12px;
+        color: #333;
+        font-size: 15px;
+      }
+
+      .item-details {
+        display: grid;
+        grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+        gap: 12px;
+
+        .detail {
+          display: flex;
+          align-items: center;
+          font-size: 14px;
+
+          .label {
+            margin-right: 6px;
+            color: #666;
+            min-width: 60px;
+          }
+
+          .value {
+            font-weight: 500;
+            margin-right: 8px;
+            color: #333;
+          }
+
+          .trend {
+            font-size: 12px;
+            padding: 2px 6px;
+            border-radius: 10px;
+            background-color: rgba(24, 144, 255, 0.1);
+
+            &.up {
+              color: #52c41a;
+              background-color: rgba(82, 196, 26, 0.1);
+            }
+
+            &.down {
+              color: #f5222d;
+              background-color: rgba(245, 34, 45, 0.1);
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+  .data-record .record-list {
+    grid-template-columns: 1fr 1fr;
+  }
+
+  .daily-data-modal {
+    width: 95%;
+    max-height: 85vh;
+
+    .modal-content .item-details {
+      grid-template-columns: 1fr;
+    }
+  }
+}
+
+@media (max-width: 480px) {
+  .data-record .record-list {
+    grid-template-columns: 1fr;
+  }
+}
+
+/* 更新关闭按钮样式 - 添加文字和图标组合 */
+.daily-data-close {
+  position: absolute;
+  top: 16px;
+  right: 16px;
+  padding: 6px 12px 6px 8px;
+  background: rgba(0, 0, 0, 0.05);
+  border: none;
+  border-radius: 16px;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  cursor: pointer;
+  transition: all 0.2s;
+  z-index: 1;
+  font-size: 14px;
+  color: #666;
+
+  &:hover {
+    background: rgba(0, 0, 0, 0.1);
+    color: #333;
+    
+    &::after {
+      opacity: 1;
+    }
+  }
+
+  i {
+    font-size: 14px;
+    transition: transform 0.2s;
+  }
+
+  /* 添加文字标签(悬停时显示) */
+  &::after {
+    content: "关闭";
+    opacity: 0;
+    transition: opacity 0.2s;
+  }
+
+  &:hover i {
+    transform: rotate(90deg);
+  }
+}
+
+/* 添加确认按钮容器 */
+.modal-actions {
+  display: flex;
+  justify-content: flex-end;
+  padding: 16px 24px;
+  border-top: 1px solid #f0f0f0;
+  gap: 12px;
+
+  button {
+    padding: 8px 16px;
+    border-radius: 6px;
+    font-size: 14px;
+    cursor: pointer;
+    transition: all 0.2s;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+
+    &.cancel-btn {
+      background: #f5f5f5;
+      color: #666;
+      border: 1px solid #d9d9d9;
+
+      &:hover {
+        background: #eee;
+        color: #333;
+      }
+    }
+
+    &.confirm-btn {
+      background: #1890ff;
+      color: white;
+      border: none;
+
+      &:hover {
+        background: #40a9ff;
+      }
+    }
+
+    i {
+      font-size: 12px;
+    }
+  }
+}
+
+//数据
+/* 数据项样式 */
+.data-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 12px 0;
+  border-bottom: 1px solid #f0f0f0;
+  transition: background-color 0.2s;
+
+  &:hover {
+    background-color: #f9f9f9;
+  }
+}
+
+.data-main {
+  flex: 1;
+}
+
+.data-title {
+  font-weight: 500;
+  margin-bottom: 4px;
+  color: #333;
+}
+
+.data-meta {
+  display: flex;
+  gap: 15px;
+  font-size: 12px;
+  color: #666;
+
+  i {
+    margin-right: 4px;
+  }
+}
+
+.data-actions {
+  display: flex;
+  gap: 8px;
+
+  button {
+    background: none;
+    border: none;
+    color: #999;
+    cursor: pointer;
+    font-size: 16px;
+    transition: color 0.2s;
+
+    &:first-child:hover {
+      color: #1890ff;
+    }
+
+    &:last-child:hover {
+      color: #ff4d4f;
+    }
+  }
+}
+
+.no-data {
+  text-align: center;
+  padding: 20px;
+  color: #999;
+}
+
+/* 表单样式 */
+.form-group {
+  margin-bottom: 16px;
+
+  label {
+    display: block;
+    margin-bottom: 8px;
+    font-weight: 500;
+    color: #333;
+  }
+
+  input, select {
+    width: 100%;
+    padding: 8px 12px;
+    border: 1px solid #d9d9d9;
+    border-radius: 4px;
+  }
+
+  textarea {
+    width: 100%;
+    min-height: 100px;
+    padding: 8px 12px;
+    border: 1px solid #d9d9d9;
+    border-radius: 4px;
+    resize: vertical;
+  }
+}
+
+.form-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+  margin-top: 24px;
+}
+
+//7.3
+/* AI对话模态框样式 */
+.ai-dialog-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.7);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 1100;
+  backdrop-filter: blur(5px);
+}
+
+.ai-dialog-container {
+  width: 90%;
+  max-width: 600px;
+  height: 80vh;
+  background-color: white;
+  border-radius: 12px;
+  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.ai-dialog-header {
+  padding: 16px 24px;
+  background: linear-gradient(135deg, #4285f4, #3367d6);
+  color: white;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  
+  h3 {
+    margin: 0;
+    font-size: 18px;
+    font-weight: 600;
+  }
+}
+
+.ai-dialog-close {
+  background: rgba(255, 255, 255, 0.2);
+  border: none;
+  width: 32px;
+  height: 32px;
+  border-radius: 50%;
+  color: white;
+  font-size: 16px;
+  cursor: pointer;
+  transition: all 0.2s;
+  
+  &:hover {
+    background: rgba(255, 255, 255, 0.3);
+    transform: rotate(90deg);
+  }
+}
+
+.ai-dialog-content {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  padding: 0;
+  min-height: 0;  // 新增这行
+  overflow: hidden;  // 修改为hidden
+  background-color: #f5f7fa;
+}
+
+.ai-messages {
+  min-height: 0;  // 新增这行
+  flex: 1;
+  padding: 16px;
+  overflow-y: auto;
+  scroll-behavior: smooth;
+  
+  /* 自定义滚动条 */
+  &::-webkit-scrollbar {
+    width: 8px;
+  }
+  
+  &::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 4px;
+  }
+  
+  &::-webkit-scrollbar-thumb {
+    background: #c1c1c1;
+    border-radius: 4px;
+    
+    &:hover {
+      background: #a8a8a8;
+    }
+  }
+}
+
+.message {
+  display: flex;
+  margin-bottom: 20px;
+  animation: fadeIn 0.3s ease;
+  max-width: 100%;
+  box-sizing: border-box;
+  
+  &.user {
+    flex-direction: row-reverse;
+    
+    .message-content {
+      align-items: flex-end;
+    }
+    
+    .message-text {
+      background: linear-gradient(135deg, #4285f4, #3367d6);
+      color: white;
+      border-radius: 18px 18px 0 18px;
+    }
+  }
+}
+
+@keyframes fadeIn { 
+  from { opacity: 0; transform: translateY(10px); }
+  to { opacity: 1; transform: translateY(0); }
+}
+
+.message-avatar {
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  background-color: #e0e0e0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin: 0 12px;
+  flex-shrink: 0;
+  
+  i {
+    color: #666;
+    font-size: 18px;
+  }
+}
+
+.message-content {
+  display: flex;
+  flex-direction: column;
+  max-width: 75%;
+}
+
+.message-text {
+  padding: 12px 16px;
+  background-color: white;
+  border-radius: 18px 18px 18px 0;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  margin-bottom: 6px;
+  line-height: 1.5;
+  font-size: 15px;
+  
+  a {
+    color: #4285f4;
+    text-decoration: none;
+    
+    &:hover {
+      text-decoration: underline;
+    }
+  }
+}
+
+.message-time {
+  font-size: 12px;
+  color: #888;
+}
+
+.ai-thinking {
+  display: flex;
+  justify-content: center;
+  padding: 16px;
+}
+
+.typing-indicator {
+  display: flex;
+  align-items: center;
+  background-color: white;
+  padding: 12px 16px;
+  border-radius: 18px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  
+  span {
+    width: 8px;
+    height: 8px;
+    margin: 0 3px;
+    background-color: #4285f4;
+    border-radius: 50%;
+    display: inline-block;
+    animation: typing 1s infinite ease-in-out;
+    
+    &:nth-child(1) {
+      animation-delay: 0s;
+    }
+    
+    &:nth-child(2) {
+      animation-delay: 0.2s;
+    }
+    
+    &:nth-child(3) {
+      animation-delay: 0.4s;
+    }
+  }
+}
+
+@keyframes typing {
+  0%, 100% {
+    transform: translateY(0);
+    opacity: 0.6;
+  }
+  50% {
+    transform: translateY(-5px);
+    opacity: 1;
+  }
+}
+
+.ai-input-container {
+  display: flex;
+  padding: 16px;
+  border-top: 1px solid #e0e0e0;
+  background-color: white;
+  align-items: flex-end;
+}
+
+.ai-input {
+  flex: 1;
+  padding: 12px 16px;
+  border: 1px solid #ddd;
+  border-radius: 24px;
+  outline: none;
+  font-size: 15px;
+  resize: none;
+  max-height: 120px;
+  line-height: 1.5;
+  transition: all 0.2s;
+  
+  &:focus {
+    border-color: #4285f4;
+    box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.2);
+  }
+}
+
+.ai-send-btn {
+  width: 48px;
+  height: 48px;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #4285f4, #3367d6);
+  color: white;
+  border: none;
+  margin-left: 12px;
+  cursor: pointer;
+  transition: all 0.2s;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  
+  &:hover:not(:disabled) {
+    transform: translateY(-2px);
+    box-shadow: 0 4px 12px rgba(66, 133, 244, 0.3);
+  }
+  
+  &:disabled {
+    background: #ddd;
+    cursor: not-allowed;
+  }
+  
+  i {
+    font-size: 18px;
+  }
+}
+
+.ai-dialog-footer {
+  padding: 12px 16px;
+  border-top: 1px solid #e0e0e0;
+  background-color: #f9f9f9;
+}
+
+.ai-suggestions {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  align-items: center;
+  
+  span {
+    font-size: 13px;
+    color: #666;
+    margin-right: 8px;
+  }
+  
+  button {
+    padding: 6px 12px;
+    background-color: #e8f0fe;
+    border: none;
+    border-radius: 16px;
+    font-size: 13px;
+    color: #4285f4;
+    cursor: pointer;
+    transition: all 0.2s;
+    white-space: nowrap;
+    
+    &:hover {
+      background-color: #d2e3fc;
+      transform: translateY(-1px);
+    }
+  }
+}
+
+/* 修改训练结果项样式 */
+.result-item {
+  cursor: pointer;
+  transition: all 0.2s;
+  
+  &:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  }
+  
+  &[class*="status-completed"] {
+    &:hover {
+      background-color: rgba(52, 168, 83, 0.05);
+    }
+  }
+}
+
+/* 新增:自动调整输入框高度 */
+.ai-input {
+  min-height: 48px;
+  max-height: 120px;
+  overflow-y: auto;
+}
+
+/* 新增:响应式调整 */
+@media (max-width: 480px) {
+  .ai-dialog-container {
+    width: 95%;
+    height: 85vh;
+  }
+  
+  .message-content {
+    max-width: 85%;
+  }
+  
+  .message-avatar {
+    width: 32px;
+    height: 32px;
+    margin: 0 8px;
+    
+    i {
+      font-size: 16px;
+    }
+  }
+  
+  .message-text {
+    padding: 10px 14px;
+    font-size: 14px;
+  }
+  
+  .ai-input-container {
+    padding: 12px;
+  }
+  
+  .ai-send-btn {
+    width: 42px;
+    height: 42px;
+  }
+}
+.ai-dialog-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.7);
+  z-index: 9999; /* 临时设置为极高的值 */
+  /* 其他样式 */
+}
+.ai-messages {
+  flex: 1;
+  padding: 16px;
+  overflow-y: auto;  // 启用垂直滚动
+  scroll-behavior: smooth;  // 平滑滚动
+  
+  /* 自定义滚动条样式 */
+  &::-webkit-scrollbar {
+    width: 8px;  // 滚动条宽度
+  }
+  
+  &::-webkit-scrollbar-track {
+    background: #f1f1f1;  // 轨道颜色
+    border-radius: 4px;
+  }
+  
+  &::-webkit-scrollbar-thumb {
+    background: #c1c1c1;  // 滑块颜色
+    border-radius: 4px;
+    
+    &:hover {
+      background: #a8a8a8;  // 悬停颜色
+    }
+  }
+  
+  /* 确保消息不会超出容器 */
+  .message {
+    max-width: 100%;
+    box-sizing: border-box;
+  }
+  
+  /* 滚轮滚动支持 */
+  overscroll-behavior-y: contain;
+  -webkit-overflow-scrolling: touch;  // iOS平滑滚动
+}
+
+//

+ 758 - 5
ai-assisant/src/modules/crm/mobile/page-crm-data/page-crm-data.ts

@@ -1,11 +1,764 @@
-import { Component } from '@angular/core';
+// page-crm-data.component.ts
+import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { CloudObject, CloudQuery } from '../../../../lib/cloud/ncloud';
+import { HostListener } from '@angular/core';
 
 @Component({
   selector: 'app-page-crm-data',
-  imports: [],
-  templateUrl: './page-crm-data.html',
-  styleUrl: './page-crm-data.scss'
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './page-crm-data.html', // 确保路径正确
+  styleUrls: ['./page-crm-data.scss']
 })
-export class PageCrmData {
 
+export class PageCrmData implements AfterViewInit {
+  // 标签相关数据
+
+  //7.3
+  @ViewChild('messageInput') messageInput!: ElementRef<HTMLTextAreaElement>;
+  @ViewChild('messageContainer') messageContainer!: ElementRef<HTMLDivElement>;
+  //
+  // 标签相关数据
+sourceTags = [
+  { 
+    name: '会议记录', 
+    active: true, 
+    icon: 'calendar-alt',
+    type: 'meeting',
+    hover: false 
+  },
+  { 
+    name: '客户合同', 
+    active: false,
+    icon: 'file-signature',
+    type: 'contract',
+    hover: false 
+  },
+  { 
+    name: '反馈表', 
+    active: false,
+    icon: 'comment-dots',
+    type: 'feedback',
+    hover: false 
+  },
+  { 
+    name: '培训资料', 
+    active: false,
+    icon: 'chalkboard-teacher',
+    type: 'training',
+    hover: false 
+  }
+];
+
+// // 处理标签点击
+// handleTagClick(tag: any) {
+//   if (tag.type) {
+//     this.openModal(tag.type);
+//   } else {
+//     this.selectTag(tag); // 保持原有的selectTag行为
+//   }
+// }
+handleMouseEnter(tag: any) {
+  tag.hover = true;
+}
+
+handleMouseLeave(tag: any) {
+  tag.hover = false;
+}
+
+handleTagClick(tag: any) {
+  console.log('Tag clicked:', tag);  // 调试输出
+  
+  this.sourceTags.forEach(t => t.active = false);
+  tag.active = true;
+  
+  if (tag.type) {
+    console.log('Opening modal for type:', tag.type);  // 调试输出
+    this.openModal(tag.type);
+  }
+}
+  
+  newTag = '';
+  //7.1
+  // 新增模态框相关变量
+  showModal: boolean = false;
+  modalType: string = '';
+  modalTitle: string = '';
+  openModal(type: string): void {
+    this.modalType = type;
+    this.showModal = true;
+    
+    switch(type) {
+      case 'meeting':
+        this.modalTitle = '上传会议记录';
+        break;
+      case 'contract':
+        this.modalTitle = '上传客户合同';
+        break;
+      case 'feedback':
+        this.modalTitle = '上传反馈表';
+        break;
+      case 'training':
+        this.modalTitle = '上传培训资料';
+        break;
+    }
+  }
+  
+  closeModal(): void {
+    this.showModal = false;
+    this.modalType = '';
+    this.modalTitle = '';
+  }
+  
+  confirmUpload(): void {
+    // 这里处理上传逻辑
+    console.log(`上传${this.modalTitle}确认`);
+    
+    // 可以添加上传逻辑,然后关闭模态框
+    this.closeModal();
+    
+    // 可以在这里添加上传状态提示
+    this.uploadStatus = {
+      visible: true,
+      success: true,
+      message: `${this.modalTitle}上传成功!`,
+      progress: 100
+    };
+    
+    // 3秒后隐藏上传状态
+    setTimeout(() => {
+      this.uploadStatus.visible = false;
+    }, 3000);
+  }
+
+  //
+  // 在PageCrmData类中添加这些属性和方法
+
+// 可用的标签列表
+availableTags = [
+  { id: 1, name: '会议记录', icon: 'calendar-alt' },
+  { id: 2, name: '客户合同', icon: 'file-signature' },
+  { id: 3, name: '反馈表', icon: 'comment-dots' },
+  { id: 4, name: '培训资料', icon: 'chalkboard-teacher' },
+  { id: 5, name: '销售报告', icon: 'chart-line' },
+  { id: 6, name: '产品文档', icon: 'file-alt' },
+  { id: 7, name: '市场分析', icon: 'chart-pie' },
+  { id: 8, name: '客户反馈', icon: 'comments' },
+  { id: 9, name: '财务数据', icon: 'money-bill-wave' },
+  { id: 10, name: '项目计划', icon: 'project-diagram' },
+  { id: 11, name: '技术文档', icon: 'file-code' },
+  { id: 12, name: '用户手册', icon: 'book' },
+  { id: 13, name: '测试报告', icon: 'vial' },
+  { id: 14, name: '设计稿', icon: 'palette' },
+  { id: 15, name: '会议音频', icon: 'microphone' },
+  // 可以继续添加更多标签...
+];
+
+// 选中的标签
+selectedTags: any[] = [];
+
+// 控制标签选择器显示
+showTagSelector = false;
+
+openTagSelector(event?: Event) {
+  if (event) event.stopPropagation();
+  this.showModal = false;
+  this.showTagSelector = true;
+}
+
+closeTagSelector() {
+  this.showTagSelector = false;
+}
+
+toggleTagSelection(tag: any) {
+  const index = this.selectedTags.findIndex(t => t.id === tag.id);
+  if (index >= 0) {
+    this.selectedTags.splice(index, 1);
+  } else {
+    this.selectedTags.push({...tag});
+  }
+}
+
+isTagSelected(tag: any): boolean {
+  return this.selectedTags.some(t => t.id === tag.id);
+}
+
+confirmTagSelection() {
+  if (this.selectedTags.length > 0) {
+    this.selectedTags.forEach(tag => {
+      if (!this.sourceTags.some(t => t.name === tag.name)) {
+        this.sourceTags.push({
+          name: tag.name,
+          icon: tag.icon,
+          active: false,
+          type: tag.name.replace(/\s+/g, '-').toLowerCase(),
+          hover: false
+        });
+      }
+    });
+    this.selectedTags = [];
+    this.showTagSelector = false;
+  }
+}
+
+
+
+
+
+
+  // 上传方式
+  uploadMethods = [
+    { name: '拍照', icon: 'camera', active: false },
+    { name: '文件', icon: 'file', active: false },
+    { name: '录音', icon: 'microphone', active: false },
+    { name: '文本', icon: 'keyboard', active: false }
+  ];
+  
+  // 上传状态
+  uploadStatus = {
+    visible: false,
+    progress: 0,
+    message: '',
+    success: false
+  };
+
+  // 每日数据记录模态框相关
+// 每日数据模态框控制
+showDailyDataModal = false;  // 控制模态框显示
+searchQuery = '';           // 搜索关键词 (与模板中的[(ngModel)]="searchQuery"对应)
+
+// 每日详细数据 (与模板中的filteredDailyData对应)
+dailyData = [
+  {
+    date: '2023-10-01',
+    stats: [
+      { label: '新增客户', value: '24', trend: '12', trendUp: true },
+      { label: '订单量', value: '56', trend: '5', trendUp: true },
+      { label: '销售额', value: '¥12,450', trend: '8', trendUp: false },
+      { label: '转化率', value: '32%', trend: '2', trendUp: true }
+    ]
+  },
+  {
+    date: '2023-10-02',
+    stats: [
+      { label: '新增客户', value: '18', trend: '5', trendUp: false },
+      { label: '订单量', value: '42', trend: '3', trendUp: true },
+      { label: '销售额', value: '¥9,870', trend: '6', trendUp: true },
+      { label: '转化率', value: '28%', trend: '4', trendUp: false }
+    ]
+  },
+  // 可以添加更多数据...
+];
+
+// 获取过滤后的数据 (与模板中的*ngFor="let item of filteredDailyData"对应)
+get filteredDailyData() {
+  if (!this.searchQuery.trim()) {
+    return this.dailyData;
+  }
+  const query = this.searchQuery.toLowerCase();
+  return this.dailyData.filter(item => 
+    item.date.includes(query) || 
+    item.stats.some(stat => 
+      stat.label.toLowerCase().includes(query) || 
+      stat.value.toString().toLowerCase().includes(query)
+    )
+  );
+}
+
+// 打开模态框 (与模板中的(click)="openDailyDataModal()"对应)
+openDailyDataModal() {
+  this.showDailyDataModal = true;
+  this.searchQuery = ''; // 重置搜索条件
+}
+
+// 关闭模态框 (与模板中的(click)="closeDailyDataModal()"对应)
+closeDailyDataModal() {
+  this.showDailyDataModal = false;
+}
+
+// 搜索方法 (与模板中的(click)="searchDailyData()"对应)
+searchDailyData() {
+  // 搜索逻辑已经在getter中实现,这里可以添加额外逻辑
+  console.log('搜索关键词:', this.searchQuery);
+}
+
+
+// 在组件类中添加
+confirmDailyData() {
+  // 这里可以添加确认逻辑
+  console.log('数据已确认');
+  this.closeDailyDataModal();
+  
+  // 示例:显示确认提示
+  this.uploadStatus = {
+    visible: true,
+    success: true,
+    message: '每日数据已确认提交!',
+    progress: 100
+  };
+  
+  setTimeout(() => {
+    this.uploadStatus.visible = false;
+  }, 3000);
 }
+
+//数据库
+// 在组件类中添加这些方法
+// 数据相关属性
+allTrainingData: any[] = [];
+filteredTrainingData: any[] = [];
+currentTrainingItem = new CloudObject('TrainingData');
+
+// 模态框控制
+
+showDataModal = false;
+isEditing = false;
+
+// 仪表板数据
+dashboardStats = [
+  { label: '新增数据', value: 42, trend: 12, trendUp: true, icon: 'file-medical' },
+  { label: '处理中', value: 18, trend: 5, trendUp: false, icon: 'cogs' },
+  { label: '训练时间', value: '1.8h', trend: 23, trendUp: true, icon: 'clock' },
+  { label: '完成率', value: '76%', trend: 8, trendUp: true, icon: 'check-circle' }
+];
+
+constructor() {
+  this.loadTrainingData();
+}
+
+// 加载训练数据
+async loadTrainingData() {
+  const query = new CloudQuery('TrainingData');
+  this.allTrainingData = await query.find();
+  this.filteredTrainingData = [...this.allTrainingData];
+}
+
+// 打开训练数据模态框
+openTrainingDataModal() {
+  this.showDailyDataModal = true;
+  this.loadTrainingData();
+}
+
+// 关闭训练数据模态框
+closeTrainingDataModal() {
+  this.showDailyDataModal = false;
+  this.searchQuery = '';
+  this.filteredTrainingData = [...this.allTrainingData];
+}
+
+// 搜索训练数据
+searchTrainingData() {
+  if (!this.searchQuery.trim()) {
+    this.filteredTrainingData = [...this.allTrainingData];
+    return;
+  }
+  
+  const query = this.searchQuery.toLowerCase();
+  this.filteredTrainingData = this.allTrainingData.filter(item => 
+    (item.get('title') || '').toLowerCase().includes(query) || 
+    (item.get('type') || '').toLowerCase().includes(query)
+  );
+}
+
+// 准备新增项目
+prepareNewItem() {
+  this.currentTrainingItem = new CloudObject('TrainingData');
+  this.isEditing = false;
+  this.showDataModal = true;
+}
+
+// 准备编辑项目
+prepareEditItem(item: any) {
+  this.currentTrainingItem = new CloudObject('TrainingData');
+  this.currentTrainingItem.id = item.id;
+  this.currentTrainingItem.set(item);
+  this.isEditing = true;
+  this.showDataModal = true;
+}
+
+// 保存训练数据
+async saveTrainingItem() {
+  await this.currentTrainingItem.save();
+  await this.loadTrainingData();
+  this.showDataModal = false;
+}
+
+// 删除训练数据
+async deleteTrainingItem(item: any) {
+  const obj = new CloudObject('TrainingData');
+  obj.id = item.id;
+  await obj.destroy();
+  await this.loadTrainingData();
+}
+
+// 获取类型名称
+getTypeName(type: string): string {
+  const typeMap: Record<string, string> = {
+    'meeting': '会议记录',
+    'contract': '客户合同',
+    'feedback': '反馈表',
+    'training': '培训资料'
+  };
+  return typeMap[type] || '未知类型';
+}
+
+// 获取类型图标
+getTypeIcon(type: string): string {
+  const iconMap: Record<string, string> = {
+    'meeting': 'calendar-alt',
+    'contract': 'file-signature',
+    'feedback': 'comment-dots',
+    'training': 'chalkboard-teacher'
+  };
+  return iconMap[type] || 'file-alt';
+}
+
+
+//
+  // 历史记录
+  historyItems = [
+    { name: 'Q3销售报告.pdf', type: 'pdf', size: 4.2, date: '2023-10-15' },
+    { name: '客户数据.xlsx', type: 'excel', size: 3.1, date: '2023-10-14' },
+    { name: '会议纪要.docx', type: 'word', size: 1.7, date: '2023-10-13' },
+    { name: '用户反馈.csv', type: 'csv', size: 2.3, date: '2023-10-12' }
+  ];
+  
+  filteredHistory = [...this.historyItems];
+  searchTerm = '';
+  
+  // 训练结果
+  trainingResults = [
+    { title: '销售预测模型', time: '2.3小时', status: 'completed', icon: 'brain' },
+    { title: '情感分析模型', time: '1.5小时', status: 'training', icon: 'comments' },
+    { title: '趋势预测模型', time: '3.1小时', status: 'completed', icon: 'chart-line' },
+    { title: '客户服务模型', time: '4.2小时', status: 'error', icon: 'robot' }
+  ];
+  
+  // 数据看板
+  // dashboardStats:any = [
+  //   { label: '新增数据', value: 42, trend: 12, trendUp: true, icon: 'file-medical' },
+  //   { label: '处理中', value: 18, trend: 5, trendUp: false, icon: 'cogs' },
+  //   { label: '训练时间', value: '1.8h', trend: 23, trendUp: true, icon: 'clock' },
+  //   { label: '完成率', value: '76%', trend: 8, trendUp: true, icon: 'check-circle' }
+  // ];
+  
+  // 选择标签
+  selectTag(tag: any) {
+    this.sourceTags.forEach(t => t.active = false);
+    tag.active = true;
+  }
+  
+  // 添加新标签
+  addTag() {
+    if (this.newTag.trim()) {
+      this.sourceTags.push({
+        name: this.newTag.trim(), active: true,
+        icon: '',
+        type: '',
+        hover: false
+      });
+      this.selectTag(this.sourceTags[this.sourceTags.length - 1]);
+      this.newTag = '';
+    }
+  }
+  
+  // 选择上传方式
+  selectMethod(method: any) {
+    this.uploadMethods.forEach(m => m.active = false);
+    method.active = true;
+  }
+  
+  // 上传数据
+  uploadData() {
+    const selectedTag = this.sourceTags.find(t => t.active)?.name || '未知';
+    const selectedMethod = this.uploadMethods.find(m => m.active)?.name || '未选择';
+    
+    this.uploadStatus = {
+      visible: true,
+      progress: 0,
+      message: `正在上传: ${selectedTag} (方式: ${selectedMethod})`,
+      success: false
+    };
+    
+    // 模拟上传进度
+    const interval = setInterval(() => {
+      this.uploadStatus.progress += Math.floor(Math.random() * 10);
+      
+      if (this.uploadStatus.progress >= 100) {
+        this.uploadStatus.progress = 100;
+        clearInterval(interval);
+        
+        // 上传完成
+        setTimeout(() => {
+          this.uploadStatus.message = `<i class="fas fa-check-circle"></i> 上传成功: ${selectedTag} (方式: ${selectedMethod})`;
+          this.uploadStatus.success = true;
+          
+          // 添加新项目到历史记录
+          this.addHistoryItem(selectedTag, selectedMethod);
+          
+          // 3秒后隐藏状态
+          setTimeout(() => {
+            this.uploadStatus.visible = false;
+          }, 3000);
+        }, 500);
+      }
+    }, 200);
+  }
+  
+  // 添加历史记录
+  addHistoryItem(tagName: string, methodName: string) {
+    const fileType = this.getFileExtension(methodName);
+    
+    this.historyItems.unshift({
+      name: `${tagName}.${fileType}`,
+      type: fileType,
+      size: +(Math.random() * 5).toFixed(1),
+      date: this.getCurrentDate()
+    });
+    
+    this.filterHistory();
+  }
+  
+  // 获取文件扩展名
+  getFileExtension(method: string): string {
+    switch(method) {
+      case '拍照': return 'jpg';
+      case '文件': return 'pdf';
+      case '录音': return 'mp3';
+      case '文本': return 'txt';
+      default: return 'dat';
+    }
+  }
+  
+  // 获取当前日期
+  getCurrentDate(): string {
+    const now = new Date();
+    const year = now.getFullYear();
+    const month = String(now.getMonth() + 1).padStart(2, '0');
+    const day = String(now.getDate()).padStart(2, '0');
+    return `${year}-${month}-${day}`;
+  }
+  
+  // 搜索历史记录
+  filterHistory() {
+    if (!this.searchTerm.trim()) {
+      this.filteredHistory = [...this.historyItems];
+      return;
+    }
+    
+    const term = this.searchTerm.toLowerCase();
+    this.filteredHistory = this.historyItems.filter(item => 
+      item.name.toLowerCase().includes(term) || 
+      item.type.toLowerCase().includes(term) ||
+      item.date.includes(term)
+    );
+  }
+  
+  // 随机更新数据看板
+  ngAfterViewInit() {
+    setInterval(() => {
+      this.dashboardStats = this.dashboardStats.map(stat => {
+        if (typeof stat.value === 'number') {
+          const change = Math.floor(Math.random() * 5) - 2; // -2 到 2 之间的随机数
+          const newValue = Math.max(0, stat.value + change);
+          return { ...stat, value: newValue };
+        }
+        return stat;
+      });
+    }, 5000);
+  }
+
+
+
+  //7.2
+
+  // AI对话相关属性
+  showAIDialog = false;
+  currentAIModel: any = null;
+  userMessage = '';
+  aiMessages: any[] = [];
+  isAIThinking = false;
+  aiSuggestions = [
+    '这个模型的具体功能是什么?',
+    '模型使用了哪些训练数据?',
+    '模型的准确率和性能指标如何?',
+    '如何在实际业务中使用这个模型?'
+  ];
+
+  // 知识库 - 模拟真实AI回答
+  private knowledgeBase: { [key: string]: any } = {
+    '销售预测模型': {
+      description: '该模型基于历史销售数据和市场趋势,能够预测未来3-6个月的销售情况,准确率达到87.5%。',
+      dataUsed: '使用了2019-2023年的销售记录、客户购买行为数据和市场调研报告。',
+      accuracy: '测试集准确率87.5%,召回率82.3%,F1分数84.8%。',
+      usage: '通过API集成到CRM系统,每日自动生成销售预测报告。'
+    },
+    '情感分析模型': {
+      description: '分析客户反馈、评论和社交媒体中的情感倾向,识别正面、负面和中性情绪。',
+      dataUsed: '10万条客户评论、5万条客服对话记录和社交媒体数据。',
+      accuracy: '情感分类准确率92.1%,支持中文、英文和西班牙语。',
+      usage: '实时分析客户反馈,自动触发满意度低的客户跟进流程。'
+    },
+    '趋势预测模型': {
+      description: '识别产品需求变化和市场趋势,帮助调整库存和营销策略。',
+      dataUsed: '行业报告、搜索引擎趋势和产品销售数据。',
+      accuracy: '趋势预测准确率79.3%,提前3个月预警市场变化。',
+      usage: '每周生成趋势报告,指导产品开发和营销活动。'
+    }
+  };
+
+  // 打开AI对话框
+  openAIDialog(model: any) {
+    if (model.status !== 'completed') return;
+    
+    this.currentAIModel = model;
+    this.showAIDialog = true;
+    this.userMessage = '';
+    
+    // 初始化AI欢迎消息
+    this.aiMessages = [{
+      role: 'assistant',
+      content: `您好!我是${model.title} AI助手,专为销售团队设计。我可以回答关于这个模型的各种问题,包括:<br>
+      • 模型功能和应用场景<br>
+      • 训练数据和性能指标<br>
+      • 实际使用方法和集成方式<br><br>
+      请问您想了解什么?`,
+      time: new Date()
+    }];
+    
+    // 自动聚焦输入框
+    setTimeout(() => {
+      this.messageInput.nativeElement.focus();
+      this.scrollToBottom();
+    }, 100);
+  }
+
+  // 关闭AI对话框
+  closeAIDialog() {
+    this.showAIDialog = false;
+    this.currentAIModel = null;
+    this.userMessage = '';
+    this.aiMessages = [];
+  }
+
+  // 发送消息
+  sendMessage(event?: Event) {
+    if (event) event.preventDefault();
+    if (!this.userMessage.trim()) return;
+    
+    const question = this.userMessage.trim();
+    
+    // 添加用户消息
+    this.aiMessages.push({
+      role: 'user',
+      content: question,
+      time: new Date()
+    });
+    
+    this.userMessage = '';
+    this.isAIThinking = true;
+    this.scrollToBottom();
+    
+    // 获取AI响应
+    setTimeout(() => {
+      const response = this.generateAIResponse(question);
+      this.aiMessages.push({
+        role: 'assistant',
+        content: response,
+        time: new Date()
+      });
+      this.isAIThinking = false;
+      this.scrollToBottom();
+    }, 800); // 模拟网络延迟
+  }
+
+// 修改后的代码
+private generateAIResponse(question: string): string {
+  const model = this.currentAIModel.title;
+  const knowledge = this.knowledgeBase[model] || {};
+
+  // 定义关键词映射
+  const keywordMap: { [key: string]: string } = {
+    '功能': 'description',
+    '做什么': 'description',
+    '用途': 'description',
+    '训练数据': 'dataUsed',
+    '数据': 'dataUsed',
+    '样本': 'dataUsed',
+    '准确率': 'accuracy',
+    '性能指标': 'accuracy',
+    '准确': 'accuracy',
+    '性能': 'accuracy',
+    '使用方法': 'usage',
+    '怎么用': 'usage',
+    '集成': 'usage'
+  };
+
+  // 查找匹配的关键词
+  let answerKey = '';
+  for (const keyword in keywordMap) {
+    if (question.includes(keyword)) {
+      answerKey = keywordMap[keyword];
+      break;
+    }
+  }
+
+  // 如果找到匹配的关键词,返回相应的答案
+  if (answerKey) {
+    const answer = knowledge[answerKey];
+    if (answer) {
+      return `关于<strong>${model}</strong>的${Object.keys(keywordMap).find(key => keywordMap[key] === answerKey)}:<br><br>${answer}`;
+    }
+  }
+
+  // 如果没有匹配的关键词,返回默认回答
+  return `很抱歉,我不太理解您的问题。您可以换一种表达方式,或者问我一些关于模型功能、训练数据、准确率或使用方法的问题。`;
+}
+
+  // 选择建议问题
+  selectSuggestion(suggestion: string) {
+    this.userMessage = suggestion;
+    this.sendMessage();
+  }
+
+  
+
+  // 修改训练结果项的点击处理
+  onResultClick(result: any) {
+    if (result.status === 'completed') {
+      this.openAIDialog(result);
+    }
+  }
+  // 监听滚轮事件
+  @HostListener('wheel', ['$event'])
+  onWheel(event: WheelEvent) {
+    // 防止页面整体滚动
+    if (this.showAIDialog) {
+      const container = this.messageContainer?.nativeElement;
+      if (container && container.contains(event.target as Node)) {
+        event.preventDefault();
+        container.scrollTop += event.deltaY;
+      }
+    }
+  }
+  
+  // 确保每次新消息都滚动到底部
+  ngAfterViewChecked() {
+    this.scrollToBottom();
+  }
+  
+  private scrollToBottom(): void {
+    if (this.messageContainer) {
+      try {
+        const container = this.messageContainer.nativeElement;
+        container.scrollTop = container.scrollHeight;
+      } catch (err) {
+        console.error('滚动错误:', err);
+      }
+    }
+  }
+}
+
+

+ 227 - 1
ai-assisant/src/modules/crm/mobile/page-crm-decision/page-crm-decision.html

@@ -1 +1,227 @@
-<p>page-crm-decision works!</p>
+<div class="container">
+  <!-- 顶部导航 -->
+  <header class="app-header">
+    <div class="header-title">
+      <i class="fas fa-robot"></i>
+      <h1>九州宴会 - 销售话术决策助手</h1>
+    </div>
+  </header>
+  
+  <!-- 场景选择 -->
+  <section class="scene-selector">
+    <h2 class="input-title">
+      <i class="fas fa-dharmachakra"></i>
+      选择分析场景
+    </h2>
+    <div class="scene-options">
+      <div class="scene-option" [class.active]="selectedScene === 'first-contact'" 
+          (click)="selectScene('first-contact')">
+        <i class="fas fa-handshake"></i>
+        <h3>首次接触</h3>
+        <p>分析初次沟通策略</p>
+      </div>
+      <div class="scene-option" [class.active]="selectedScene === 'deep-talk'" 
+          (click)="selectScene('deep-talk')">
+        <i class="fas fa-comments"></i>
+        <h3>深度交流</h3>
+        <p>优化后续跟进策略</p>
+      </div>
+    </div>
+  </section>
+  
+  <!-- 文件上传区域 -->
+  <section class="upload-area">
+    <div class="upload-container">
+      <label for="file-upload" class="upload-label">
+        <i class="fas fa-cloud-upload-alt"></i>
+        <span>上传聊天记录文件</span>
+      </label>
+      <input type="file" id="file-upload" #fileInput (change)="onFileSelected($event)" accept=".json" class="hidden">
+      <p class="upload-hint">支持JSON格式的聊天记录文件</p>
+    </div>
+  </section>
+  
+  <!-- 客户对话分析 -->
+  <section class="input-area">
+    <h2 class="chat-title">
+      <i class="fas fa-comments"></i>
+      客户对话分析
+      <span class="current-role-indicator" [ngClass]="{'customer-indicator': currentRole === 'customer', 'assistant-indicator': currentRole === 'assistant'}">
+        当前角色: {{currentRole === 'customer' ? '客户' : '销售顾问'}}
+      </span>
+    </h2>
+    <div class="chat-area" #chatContainer>
+      <div *ngIf="!messages.length" class="empty-chat">
+        <i class="fas fa-comments"></i>
+        <p>请上传聊天记录或开始新的对话</p>
+      </div>
+      <div *ngFor="let msg of messages" class="chat-message" 
+          [class.customer-message]="msg.role === 'customer'"
+          [class.assistant-message]="msg.role === 'assistant'">
+        <div class="message-header" [class.customer-header]="msg.role === 'customer'"
+             [class.assistant-header]="msg.role === 'assistant'">
+          <div class="icon">
+            <i *ngIf="msg.role === 'customer'" class="fas fa-user"></i>
+            <i *ngIf="msg.role === 'assistant'" class="fas fa-headset"></i>
+          </div>
+          <div>{{msg.role === 'customer' ? '客户' : '销售顾问'}}</div>
+        </div>
+        <div class="message-content">{{msg.content}}</div>
+        <div class="message-time">{{msg.time}}</div>
+      </div>
+    </div>
+    
+    <!-- 客户标签编辑区 -->
+    <div class="tags-area">
+      <div class="tags-header">
+        <div class="tags-title">客户标签</div>
+        <div class="add-tag" (click)="addTag()">
+          <i class="fas fa-plus"></i>
+          <span>添加标签</span>
+        </div>
+      </div>
+      <div class="input-tags">
+        <div *ngFor="let tag of tags" class="tag" [style.background]="tag.background" 
+            [style.color]="tag.color">
+          <i [class]="tag.icon"></i>
+          <span>{{tag.text}}</span>
+          <span class="delete" (click)="removeTag($event, tag)">
+            <i class="fas fa-times"></i>
+          </span>
+        </div>
+      </div>
+    </div>
+  </section>
+  
+  <!-- 输入工作区 -->
+  <section class="input-area">
+    <h2 class="input-title">
+      <i class="fas fa-comment-dots"></i>
+      添加对话内容
+    </h2>
+    
+    <div class="role-selector">
+      <div class="role-btn" [class.active]="currentRole === 'customer'" 
+          (click)="setRole('customer')">客户消息</div>
+      <div class="role-btn" [class.active]="currentRole === 'assistant'" 
+          (click)="setRole('assistant')">销售回复</div>
+    </div>
+    
+    <div class="input-field">
+      <textarea class="text-input" placeholder="输入对话内容..." [(ngModel)]="newMessage" 
+                (keydown)="onTextareaKeydown($event)" [disabled]="isProcessing"></textarea>
+      <div *ngIf="isProcessing" class="processing-indicator">
+        <i class="fas fa-circle-notch fa-spin"></i>
+        <span>AI分析中...</span>
+      </div>
+    </div>
+    
+    <div class="action-buttons">
+      <div class="action-btn save-btn" (click)="sendMessage()" [class.disabled]="isProcessing" [style.pointer-events]="isProcessing ? 'none' : 'auto'">
+        <i class="fa fa-paper-plane"></i>
+        <span>发送消息</span>
+      </div>
+      <div class="action-btn history-btn" (click)="viewHistory()">
+        <i class="fa fa-history"></i>
+        <span>历史记录</span>
+      </div>
+      <div class="action-btn" (click)="generateAIResponse()" [class.disabled]="isProcessing" [style.pointer-events]="isProcessing ? 'none' : 'auto'">
+        <i class="fa fa-robot"></i>
+        <span>AI回答</span>
+      </div>
+    </div>    
+  </section>
+  
+  <!-- 策略卡片组 -->
+  <section class="strategy-area">
+    <h2 class="strategy-title">
+      <i class="fas fa-cards"></i>
+      {{sceneTitle}} - 推荐应对策略
+    </h2>
+    <div class="cards-container">
+      {{aicontent}}
+      <div *ngFor="let strategy of strategies; let i = index" 
+          class="strategy-card"
+          [class.card-aggressive]="strategy.type === 'aggressive'"
+          [class.card-neutral]="strategy.type === 'neutral'"
+          [class.card-conservative]="strategy.type === 'conservative'"
+          [style.transform]="getCardTransform(i)"
+          [style.zIndex]="getCardZIndex(i)"
+          (click)="activateCard(i)">
+        <div class="card-header">
+          <div class="card-title">{{strategy.title}}</div>
+          <div class="card-icon" [style.background]="strategy.iconBg" 
+              [style.color]="strategy.iconColor">
+            <i [class]="strategy.icon"></i>
+          </div>
+        </div>
+        <div class="card-content">
+          <p>{{strategy.description}}</p>
+          <div class="card-highlight">
+            {{strategy.highlight}}
+          </div>
+          <p><strong>适用场景:</strong>{{strategy.applicable}}</p>
+          <p><strong>优势:</strong>{{strategy.advantage}}</p>
+        </div>
+        <div class="card-footer">
+          <div class="card-hint">成功率: {{strategy.successRate}}</div>
+          <div class="card-hint"><i class="fas fa-clock"></i> 推荐指数: {{strategy.rating}}</div>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 卡片导航点 -->
+    <div class="card-navigation">
+      <div *ngFor="let strategy of strategies; let i = index" 
+          class="card-nav-btn" 
+          [class.active]="activeCardIndex === i"
+          (click)="activateCard(i)"></div>
+    </div>
+  </section>
+  
+  <!-- 操作按钮 -->
+  <div class="action-buttons fixed-buttons">
+    <div class="action-btn save-btn" (click)="saveRecord()">
+      <i class="fas fa-save"></i>
+      <span>保存记录</span>
+    </div>
+    <div class="action-btn history-btn" (click)="viewHistory()">
+      <i class="fas fa-history"></i>
+      <span>查看历史</span>
+    </div>
+  </div>
+  
+  <!-- 历史记录模态框 -->
+  <div *ngIf="showHistoryModal" class="history-modal">
+    <div class="modal-content">
+      <div class="modal-header">
+        <h3>历史记录</h3>
+        <button class="close-btn" (click)="showHistoryModal = false">
+          <i class="fas fa-times"></i>
+        </button>
+      </div>
+      <div class="modal-body">
+        <div *ngIf="!chatRecords.length" class="empty-history">
+          <i class="fas fa-folder-open"></i>
+          <p>暂无保存的记录</p>
+        </div>
+        <div *ngFor="let record of chatRecords" class="history-item" (click)="selectRecord(record)">
+          <div class="history-header">
+            <div class="history-title">
+              <span>{{record.scene === 'first-contact' ? '首次接触' : '深度交流'}}</span>
+              <span class="history-timestamp">{{formatDate(record.timestamp)}}</span>
+            </div>
+            <button class="delete-btn" (click)="deleteRecord(record); $event.stopPropagation()">
+              <i class="fas fa-trash"></i>
+            </button>
+          </div>
+          <div class="history-preview">
+            <p class="history-summary">
+              {{record.messages.length}}条消息 | 策略: {{record.strategies[activeCardIndex].title}}
+            </p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>  

+ 907 - 0
ai-assisant/src/modules/crm/mobile/page-crm-decision/page-crm-decision.scss

@@ -0,0 +1,907 @@
+:host {
+  display: block;
+  background-color: #f0f5ff;
+  color: var(--dark);
+  line-height: 1.6;
+  overflow-x: hidden;
+  background: linear-gradient(135deg, #e6f0ff 0%, #f5f9ff 100%);
+  min-height: 100vh;
+  padding: 20px 0;
+}
+
+
+/* 变量定义 */
+:root {
+  --primary: #64a0ff;
+  --primary-light: rgba(100, 160, 255, 0.1);
+  --dark: #0a192f;
+  --dark-light: #112240;
+  --gray: #8892b0;
+  --light-gray: #f8f9fa;
+  --white: #ffffff;
+  --blue: #64a0ff;
+  --blue-light: rgba(100, 160, 255, 0.15);
+  --yellow: #ffd43b;
+  --orange: #ff922b;
+  --purple: #9c36b5;
+  --shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
+  --shadow-primary: 0 4px 20px rgba(100, 160, 255, 0.2);
+}
+
+/* 基础样式 */
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  font-family: 'Segoe UI', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
+}
+
+/* 容器样式 */
+.container {
+  max-width: 480px;
+  margin: 0 auto;
+  padding: 0 16px 100px;
+  position: relative;
+}
+
+/* 顶部导航 */
+.app-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 15px 0;
+  margin-bottom: 15px;
+  border-bottom: 1px solid rgba(10, 25, 47, 0.05);
+}
+
+.header-title {
+  font-size: 22px;
+  font-weight: 700;
+  color: var(--dark);
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.header-title i {
+  color: var(--primary);
+  background: var(--primary-light);
+  width: 36px;
+  height: 36px;
+  border-radius: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+/* 场景选择 */
+.scene-selector {
+  background: var(--white);
+  border-radius: 20px;
+  padding: 20px;
+  margin-bottom: 20px;
+  box-shadow: var(--shadow);
+  position: relative;
+  overflow: hidden;
+  border: 1px solid rgba(100, 160, 255, 0.2);
+}
+
+.scene-options {
+  display: flex;
+  gap: 15px;
+  margin-top: 10px;
+}
+
+.scene-option {
+  flex: 1;
+  padding: 15px;
+  border-radius: 15px;
+  background: var(--light-gray);
+  text-align: center;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  border: 1px solid rgba(100, 160, 255, 0.1);
+}
+
+.scene-option.active {
+  background: var(--primary-light);
+  border-color: var(--primary);
+  box-shadow: var(--shadow-primary);
+}
+
+.scene-option i {
+  font-size: 28px;
+  margin-bottom: 10px;
+  color: var(--primary);
+}
+
+.scene-option h3 {
+  font-size: 16px;
+  margin-bottom: 5px;
+}
+
+.scene-option p {
+  font-size: 13px;
+  color: var(--gray);
+}
+
+/* 文件上传区域 */
+.upload-area {
+  margin-bottom: 20px;
+}
+
+.upload-container {
+  background: var(--white);
+  border-radius: 20px;
+  padding: 30px 20px;
+  text-align: center;
+  box-shadow: var(--shadow);
+  border: 1px solid rgba(100, 160, 255, 0.2);
+  cursor: pointer;
+  transition: all 0.3s ease;
+}
+
+.upload-container:hover {
+  transform: translateY(-2px);
+  box-shadow: var(--shadow-primary);
+}
+
+.upload-label {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 10px;
+  cursor: pointer;
+}
+
+.upload-label i {
+  font-size: 36px;
+  color: var(--primary);
+  background: var(--primary-light);
+  width: 60px;
+  height: 60px;
+  border-radius: 15px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.upload-label span {
+  font-size: 16px;
+  font-weight: 600;
+  color: var(--dark);
+}
+
+.upload-hint {
+  font-size: 14px;
+  color: var(--gray);
+  margin-top: 10px;
+}
+
+.hidden {
+  display: none;
+}
+
+/* 对话聊天区 */
+.chat-area {
+  background: var(--white);
+  border-radius: 20px;
+  padding: 20px;
+  margin-bottom: 20px;
+  box-shadow: var(--shadow);
+  height: 350px;
+  overflow-y: auto;
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  border: 1px solid rgba(100, 160, 255, 0.2);
+}
+
+.chat-title, .input-title, .strategy-title {
+  font-size: 18px;
+  font-weight: 600;
+  margin-bottom: 15px;
+  color: var(--dark);
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding-bottom: 10px;
+  border-bottom: 1px solid rgba(100, 160, 255, 0.1);
+}
+
+.current-role-indicator {
+  margin-left: auto;
+  font-size: 14px;
+  padding: 5px 10px;
+  border-radius: 8px;
+  font-weight: 500;
+}
+
+.customer-indicator {
+  background: var(--blue-light);
+  color: var(--blue);
+}
+
+.assistant-indicator {
+  background: var(--primary-light);
+  color: var(--primary);
+}
+
+.chat-title i, .input-title i, .strategy-title i {
+  color: var(--primary);
+}
+
+.chat-message {
+  max-width: 85%;
+  padding: 14px 18px;
+  border-radius: 18px;
+  position: relative;
+  animation: fadeIn 0.3s ease;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+@keyframes fadeIn {
+  from { opacity: 0; transform: translateY(10px); }
+  to { opacity: 1; transform: translateY(0); }
+}
+
+.customer-message {
+  background: linear-gradient(135deg, #e6f0ff, #ebf2ff);
+  align-self: flex-start;
+  border-bottom-left-radius: 5px;
+  border-left: 3px solid var(--blue);
+}
+
+.assistant-message {
+  background: linear-gradient(135deg, #e6f7ff, #ebf9ff);
+  border: 1px solid rgba(100, 160, 255, 0.3);
+  align-self: flex-end;
+  border-bottom-right-radius: 5px;
+  border-right: 3px solid var(--primary);
+}
+
+.message-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 8px;
+  font-weight: 600;
+}
+
+.customer-header .icon {
+  background: var(--blue-light);
+  color: var(--blue);
+}
+
+.assistant-header .icon {
+  background: var(--primary-light);
+  color: var(--primary);
+}
+
+.message-header .icon {
+  width: 28px;
+  height: 28px;
+  border-radius: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 14px;
+}
+
+.message-content {
+  line-height: 1.5;
+}
+
+.message-time {
+  font-size: 11px;
+  color: var(--gray);
+  margin-top: 8px;
+  text-align: right;
+}
+
+.empty-chat {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  color: var(--gray);
+}
+
+.empty-chat i {
+  font-size: 48px;
+  margin-bottom: 15px;
+  opacity: 0.5;
+}
+
+/* 客户标签编辑区 */
+.tags-area {
+  background: var(--light-gray);
+  border-radius: 15px;
+  padding: 15px;
+  margin-top: 10px;
+  border: 1px solid rgba(100, 160, 255, 0.1);
+}
+
+.tags-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
+}
+
+.tags-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: var(--dark);
+}
+
+.input-tags {
+  display: flex;
+  gap: 8px;
+  flex-wrap: wrap;
+}
+
+.tag {
+  background: var(--primary-light);
+  color: var(--primary);
+  padding: 6px 12px 6px 8px;
+  border-radius: 20px;
+  font-size: 13px;
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  cursor: pointer;
+  transition: all 0.2s ease;
+  border: 1px solid rgba(100, 160, 255, 0.2);
+}
+
+.tag:hover {
+  background: rgba(100, 160, 255, 0.3);
+}
+
+.tag .delete {
+  font-size: 12px;
+  margin-left: 5px;
+  opacity: 0.7;
+}
+
+.tag .delete:hover {
+  opacity: 1;
+}
+
+.add-tag {
+  background: rgba(136, 146, 176, 0.1);
+  color: var(--gray);
+  padding: 6px 12px;
+  border-radius: 20px;
+  font-size: 13px;
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  cursor: pointer;
+  transition: all 0.2s ease;
+}
+
+.add-tag:hover {
+  background: rgba(136, 146, 176, 0.2);
+}
+
+/* 输入工作区 */
+.input-area {
+  background: var(--white);
+  border-radius: 20px;
+  padding: 20px;
+  margin-bottom: 20px;
+  box-shadow: var(--shadow);
+  border: 1px solid rgba(100, 160, 255, 0.2);
+}
+
+.role-selector {
+  display: flex;
+  background: var(--light-gray);
+  border-radius: 12px;
+  padding: 4px;
+  margin-bottom: 15px;
+}
+
+.role-btn {
+  flex: 1;
+  padding: 10px;
+  text-align: center;
+  border-radius: 10px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  font-weight: 500;
+}
+
+.role-btn.active {
+  background: var(--white);
+  box-shadow: var(--shadow);
+  color: var(--primary);
+}
+
+.input-field {
+  position: relative;
+}
+
+.text-input {
+  width: 100%;
+  height: 120px;
+  padding: 15px;
+  border-radius: 15px;
+  border: 1px solid rgba(136, 146, 176, 0.2);
+  background: var(--light-gray);
+  resize: none;
+  font-size: 16px;
+  color: var(--dark);
+  transition: all 0.3s ease;
+}
+
+.text-input:focus {
+  outline: none;
+  border-color: var(--primary);
+  box-shadow: 0 0 0 2px rgba(100, 160, 255, 0.2);
+}
+
+.processing-indicator {
+  position: absolute;
+  bottom: 15px;
+  right: 15px;
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  color: var(--primary);
+  background: rgba(255, 255, 255, 0.8);
+  padding: 5px 10px;
+  border-radius: 8px;
+  backdrop-filter: blur(5px);
+  font-size: 14px;
+}
+
+.processing-indicator i {
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
+}
+
+/* 策略卡片组 */
+.strategy-area {
+  margin-bottom: 25px;
+}
+
+.cards-container {
+  position: relative;
+  height: 320px;
+  perspective: 1000px;
+}
+
+.strategy-card {
+  position: absolute;
+  width: 100%;
+  height: 280px;
+  border-radius: 20px;
+  padding: 25px;
+  box-shadow: var(--shadow);
+  display: flex;
+  flex-direction: column;
+  transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1), 
+              opacity 0.4s ease, 
+              box-shadow 0.3s ease;
+  backface-visibility: hidden;
+  cursor: pointer;
+  border: 1px solid rgba(100, 160, 255, 0.2);
+}
+
+.card-aggressive {
+  background: linear-gradient(135deg, #eef6ff, #f0f8ff);
+  border-top: 4px solid var(--blue);
+  z-index: 30;
+}
+
+.card-neutral {
+  background: linear-gradient(135deg, #fff9e6, #fffbf0);
+  border-top: 4px solid var(--yellow);
+  transform: translateY(20px) scale(0.95);
+  z-index: 20;
+}
+
+.card-conservative {
+  background: linear-gradient(135deg, #f9f0ff, #fdf5ff);
+  border-top: 4px solid var(--purple);
+  transform: translateY(40px) scale(0.9);
+  z-index: 10;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.card-title {
+  font-size: 18px;
+  font-weight: 700;
+}
+
+.card-icon {
+  width: 40px;
+  height: 40px;
+  border-radius: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 18px;
+}
+
+.card-content {
+  flex: 1;
+  overflow-y: auto;
+  padding-right: 10px;
+}
+
+.card-content p {
+  margin-bottom: 15px;
+  line-height: 1.7;
+  color: #333;
+}
+
+.card-highlight {
+  background: rgba(10, 25, 47, 0.05);
+  padding: 12px 15px;
+  border-radius: 12px;
+  border-left: 3px solid var(--primary);
+  margin: 15px 0;
+  font-weight: 500;
+  line-height: 1.6;
+}
+
+.card-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-top: 15px;
+  padding-top: 15px;
+  border-top: 1px solid rgba(136, 146, 176, 0.1);
+}
+
+.card-hint {
+  font-size: 14px;
+  color: var(--gray);
+}
+
+/* 卡片导航点 */
+.card-navigation {
+  display: flex;
+  justify-content: center;
+  gap: 10px;
+  margin-top: 15px;
+}
+
+.card-nav-btn {
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+  background: var(--light-gray);
+  cursor: pointer;
+  transition: all 0.2s ease;
+}
+
+.card-nav-btn.active {
+  background: var(--primary);
+  transform: scale(1.2);
+}
+
+/* 操作按钮 */
+.action-buttons {
+  display: flex;
+  gap: 15px;
+}
+
+.action-btn {
+  flex: 1;
+  height: 50px;
+  border-radius: 15px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 10px;
+  font-weight: 600;
+  cursor: pointer;
+  transition: all 0.3s ease;
+}
+
+.save-btn {
+  background: var(--primary);
+  color: var(--dark);
+  border: none;
+  box-shadow: 0 4px 15px rgba(100, 160, 255, 0.3);
+}
+
+.save-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 6px 20px rgba(100, 160, 255, 0.4);
+}
+
+.history-btn {
+  background: var(--white);
+  color: var(--dark);
+  border: 2px solid var(--primary);
+  box-shadow: 0 4px 15px rgba(100, 160, 255, 0.1);
+}
+
+.history-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 6px 20px rgba(100, 160, 255, 0.2);
+}
+
+.fixed-buttons {
+  position: fixed;
+  bottom: 20px;
+  left: 50%;
+  transform: translateX(-50%);
+  width: calc(100% - 40px);
+  max-width: 480px;
+  padding: 0 16px;
+  z-index: 50;
+}
+
+/* 角色指示器 */
+.current-role-indicator {
+  margin-left: auto;
+  font-size: 14px;
+  padding: 5px 10px;
+  border-radius: 8px;
+  font-weight: 500;
+  transition: all 0.3s ease;
+}
+
+.customer-indicator {
+  background: var(--blue-light);
+  color: var(--blue);
+}
+
+.assistant-indicator {
+  background: var(--primary-light);
+  color: var(--primary);
+}
+
+/* 策略卡片动画增强 */
+.strategy-card:hover {
+  box-shadow: 0 10px 30px rgba(100, 160, 255, 0.15);
+}
+
+.strategy-card:active {
+  transform: translateY(5px) scale(0.98);
+}
+
+/* 标签悬停效果 */
+.tag {
+  position: relative;
+  overflow: hidden;
+}
+
+.tag::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: -100%;
+  width: 100%;
+  height: 100%;
+  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+  transition: all 0.6s ease;
+}
+
+.tag:hover::before {
+  left: 100%;
+}
+
+/* 输入区域增强 */
+.text-input {
+  position: relative;
+}
+
+.text-input:focus {
+  border-color: var(--primary);
+  box-shadow: 0 0 0 3px rgba(100, 160, 255, 0.3);
+}
+
+/* 历史记录模态框 */
+.history-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 100;
+  backdrop-filter: blur(5px);
+}
+
+.modal-content {
+  background: var(--white);
+  border-radius: 20px;
+  width: 90%;
+  max-width: 450px;
+  max-height: 80vh;
+  overflow-y: auto;
+  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
+  animation: fadeInModal 0.3s ease;
+}
+
+@keyframes fadeInModal {
+  from { opacity: 0; transform: scale(0.95); }
+  to { opacity: 1; transform: scale(1); }
+}
+
+.modal-header {
+  padding: 20px;
+  border-bottom: 1px solid rgba(10, 25, 47, 0.05);
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  position: sticky;
+  top: 0;
+  background: var(--white);
+  z-index: 10;
+}
+
+.modal-header h3 {
+  font-size: 18px;
+  font-weight: 600;
+}
+
+.close-btn {
+  width: 30px;
+  height: 30px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  transition: all 0.2s ease;
+}
+
+.close-btn:hover {
+  background: var(--light-gray);
+}
+
+.modal-body {
+  padding: 20px;
+}
+
+.history-item {
+  background: var(--light-gray);
+  border-radius: 15px;
+  padding: 15px;
+  margin-bottom: 15px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  border: 1px solid rgba(100, 160, 255, 0.1);
+}
+
+.history-item:hover {
+  background: var(--primary-light);
+  transform: translateY(-2px);
+}
+
+.history-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
+}
+
+.history-title {
+  display: flex;
+  flex-direction: column;
+}
+
+.history-title span:first-child {
+  font-weight: 600;
+}
+
+.history-timestamp {
+  font-size: 12px;
+  color: var(--gray);
+}
+
+.delete-btn {
+  color: var(--gray);
+  cursor: pointer;
+  transition: all 0.2s ease;
+}
+
+.delete-btn:hover {
+  color: #ff4d4f;
+}
+
+.history-summary {
+  font-size: 14px;
+  color: var(--gray);
+}
+
+.empty-history {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 50px 0;
+  color: var(--gray);
+}
+
+.empty-history i {
+  font-size: 48px;
+  margin-bottom: 15px;
+  opacity: 0.5;
+}
+
+/* 响应式调整 */
+@media (max-width: 380px) {
+  .container {
+    padding: 0 12px 100px;
+  }
+  
+  .scene-options {
+    flex-direction: column;
+  }
+  
+  .cards-container {
+    height: 300px;
+  }
+  
+  .strategy-card {
+    height: 260px;
+    padding: 20px;
+  }
+  
+  .chat-area {
+    height: 320px;
+  }
+  
+  .current-role-indicator {
+    display: none;
+  }
+  
+  .chat-title, .input-title, .strategy-title {
+    font-size: 16px;
+  }
+  
+  .modal-content {
+    width: 95%;
+  }
+}    
+/* 添加禁用状态样式 */
+.save-btn.disabled {
+  opacity: 0.6;
+  cursor: not-allowed;
+  box-shadow: none;
+}
+/* 保持AI回答按钮与发送消息按钮风格一致 */
+.action-buttons .action-btn {
+  &:nth-child(3) {
+    background: var(--primary);
+    color: var(--dark);
+    transition: all 0.3s ease;
+    border: 1px solid rgba(100, 160, 255, 0.2);
+    
+    &:hover {
+      transform: translateY(-2px);
+      box-shadow: 0 6px 20px rgba(100, 160, 255, 0.4);
+    }
+    
+    &.disabled {
+      opacity: 0.6;
+      cursor: not-allowed;
+      box-shadow: none;
+      transform: none;
+    }
+  }
+}    

+ 763 - 5
ai-assisant/src/modules/crm/mobile/page-crm-decision/page-crm-decision.ts

@@ -1,11 +1,769 @@
-import { Component } from '@angular/core';
+import { 
+  Component, 
+  OnInit, 
+  ViewChild, 
+  ElementRef, 
+  AfterViewChecked 
+} from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { CommonModule } from '@angular/common';
+import { TestCompletion, TestMessage,extractJSON} from '../../../../lib/cloud/comletion';
+
+type SceneType = 'first-contact' | 'deep-talk';
+type MessageRole = 'customer' | 'assistant';
+
+interface StrategyAnalysis {
+  aggressive: {
+    title: string;
+    highlight: string;
+    applicable: string;
+    advantage: string;
+    successRate: string;
+  };
+  neutral: {
+    title: string;
+    highlight: string;
+    applicable: string;
+    advantage: string;
+    successRate: string;
+  };
+  conservative: {
+    title: string;
+    highlight: string;
+    applicable: string;
+    advantage: string;
+    successRate: string;
+  };
+}
+
+interface Message {
+  role: MessageRole;
+  content: string;
+  time: string;
+}
+
+interface Tag {
+  text: string;
+  icon: string;
+  background: string;
+  color: string;
+}
+
+interface Strategy {
+  type: string;
+  title: string;
+  icon: string;
+  iconBg: string;
+  iconColor: string;
+  description: string;
+  highlight: string;
+  applicable: string;
+  advantage: string;
+  successRate: string;
+  rating: string;
+  isDefault: boolean; // 新增:标记是否为默认策略
+}
+
+interface ChatRecord {
+  id: string;
+  scene: SceneType;
+  messages: Message[];
+  tags: Tag[];
+  strategies: Strategy[];
+  timestamp: number;
+}
 
 @Component({
   selector: 'app-page-crm-decision',
-  imports: [],
+  standalone: true,
+  imports: [CommonModule, FormsModule],
   templateUrl: './page-crm-decision.html',
-  styleUrl: './page-crm-decision.scss'
+  styleUrls: ['./page-crm-decision.scss']
 })
-export class PageCrmDecision {
+export class PageCrmDecision implements OnInit, AfterViewChecked {
+  @ViewChild('chatContainer') chatContainer!: ElementRef;
+  @ViewChild('fileInput') fileInput!: ElementRef;
 
-}
+  private aiAssistant!: TestCompletion;
+  private scenePrompts = {
+    'first-contact': `你是一名专业的酒店销售顾问,正在与一位潜在客户进行首次接触。
+客户可能对酒店服务、房型、价格等方面有疑问。你的任务是:
+1. 建立良好的沟通氛围
+2. 了解客户基本需求
+3. 介绍酒店核心优势
+4. 避免过于强硬的推销语气
+5. 为后续沟通留下空间`,
+    'deep-talk': `你是一名经验丰富的酒店销售顾问,正在与一位对酒店有一定兴趣的客户进行深度交流。
+客户可能已经了解了基本信息,正在比较选择或寻求更详细的信息。你的任务是:
+1. 深入挖掘客户具体需求和关注点
+2. 提供针对性的解决方案和建议
+3. 有效处理客户提出的异议和问题
+4. 展示酒店差异化竞争优势
+5. 推动客户向决策阶段发展`
+  };
+
+  openHistory() {
+    this.showHistoryModal = true;
+  }
+
+  selectedScene: SceneType = 'first-contact';
+  sceneTitle = '首次接触';
+  
+  messages: Message[] = [];
+  
+  tags: Tag[] = [
+    { text: 'VIP客户', icon: 'fas fa-user', background: 'rgba(100, 160, 255, 0.15)', color: '#64a0ff' },
+    { text: '旺季时段', icon: 'fas fa-calendar', background: 'rgba(255, 212, 59, 0.15)', color: '#ffd43b' },
+    { text: '价格敏感', icon: 'fas fa-percent', background: 'rgba(156, 54, 181, 0.15)', color: '#9c36b5' }
+  ];
+  
+  currentRole: MessageRole = 'customer';
+  newMessage = '';
+  
+  strategies: Strategy[] = [
+    {
+      type: 'aggressive',
+      title: '主动型策略',
+      icon: 'fas fa-bolt',
+      iconBg: 'var(--blue-light)',
+      iconColor: 'var(--blue)',
+      description: '直接强调酒店独特价值,制造紧迫感:',
+      highlight: '',
+      applicable: '客户表现出明确兴趣,决策周期短',
+      advantage: '快速成交,提升单笔订单价值',
+      successRate: '68%',
+      rating: '⭐⭐⭐⭐',
+      isDefault: true
+    },
+    {
+      type: 'neutral',
+      title: '平衡型策略',
+      icon: 'fas fa-balance-scale',
+      iconBg: 'rgba(255, 212, 59, 0.15)',
+      iconColor: 'var(--yellow)',
+      description: '提供阶梯式优惠,引导客户选择:',
+      highlight: '',
+      applicable: '客户在多个选项间犹豫',
+      advantage: '平衡双方利益,建立长期关系',
+      successRate: '82%',
+      rating: '⭐⭐⭐⭐⭐',
+      isDefault: true
+    },
+    {
+      type: 'conservative',
+      title: '保守型策略',
+      icon: 'fas fa-shield-alt',
+      iconBg: 'rgba(156, 54, 181, 0.15)',
+      iconColor: 'var(--purple)',
+      description: '提供灵活方案,降低决策风险:',
+      highlight: '',
+      applicable: '客户犹豫不决,决策周期长',
+      advantage: '降低客户风险感知,提高转化率',
+      successRate: '75%',
+      rating: '⭐⭐⭐⭐',
+      isDefault: true
+    }
+  ];
+  
+  activeCardIndex = 0;
+  isProcessing = false;
+  showHistoryModal = false;
+  chatRecords: ChatRecord[] = [];
+  
+  private strategiesContent: Record<SceneType, { aggressive: string; neutral: string; conservative: string }> = {
+    "first-contact": {
+      aggressive: "主动介绍酒店特色和限时优惠,强调独特卖点",
+      neutral: "提供套餐选择,引导客户了解不同房型",
+      conservative: "邀请客户参观虚拟酒店,提供详细资料"
+    },
+    "deep-talk": {
+      aggressive: "深度挖掘客户需求,提供定制化解决方案",
+      neutral: "建立信任关系,分享成功案例和客户评价",
+      conservative: "提供专业建议,解答客户深层疑问"
+    }
+  };
+  
+  ngOnInit() {
+    this.loadRecords();
+    this.selectScene('first-contact');
+  }
+  
+  ngAfterViewChecked() {
+    this.scrollToBottom();
+  }
+  
+  scrollToBottom(): void {
+    try {
+      if (this.chatContainer) {
+        this.chatContainer.nativeElement.scrollTop = this.chatContainer.nativeElement.scrollHeight;
+      }
+    } catch (err) {
+      console.error(err);
+    }
+  }
+  
+  onTextareaKeydown(event: KeyboardEvent): void {
+    if (event.key === 'Enter' && !event.shiftKey) {
+      event.preventDefault();
+      this.sendMessage();
+    }
+  }
+  
+  // 只在初始化时设置默认策略
+  initializeDefaultStrategies(): void {
+    const sceneStrategies = this.strategiesContent[this.selectedScene];
+    
+    this.strategies[0] = {
+      ...this.strategies[0],
+      highlight: sceneStrategies.aggressive,
+      isDefault: true
+    };
+    
+    this.strategies[1] = {
+      ...this.strategies[1],
+      highlight: sceneStrategies.neutral,
+      isDefault: true
+    };
+    
+    this.strategies[2] = {
+      ...this.strategies[2],
+      highlight: sceneStrategies.conservative,
+      isDefault: true
+    };
+  }
+  
+  selectScene(scene: SceneType): void {
+    this.selectedScene = scene;
+    this.sceneTitle = scene === 'first-contact' ? '首次接触' : '深度交流';
+    
+    // 初始化AI助手
+    this.messages = [];
+    const systemPrompt = this.scenePrompts[scene];
+    this.aiAssistant = new TestCompletion([
+      { role: "system", content: systemPrompt },
+      { role: "assistant", content: scene === 'first-contact' ? 
+        "您好!我是九州宴会的销售顾问,很高兴认识您。请问您今天想了解哪方面的信息呢?" : 
+        "感谢您继续深入交流。基于我们之前的沟通,我整理了几个可能适合您的方案,您想先了解哪个方面?"
+      }
+    ]);
+    
+    // 添加初始问候语
+    this.addMessage('assistant', scene === 'first-contact' ? 
+      "您好!我是九州宴会的销售顾问,很高兴认识您。请问您今天想了解哪方面的信息呢?" : 
+      "感谢您继续深入交流。基于我们之前的沟通,我整理了几个可能适合您的方案,您想先了解哪个方面?"
+    );
+    
+    // 重置并初始化策略
+    this.resetStrategies();
+    this.initializeDefaultStrategies();
+  }
+  
+  // 重置策略内容(切换场景时调用)
+  private resetStrategies() {
+    this.strategies = [
+      {
+        ...this.strategies[0],
+        highlight: "",
+        applicable: "客户表现出明确兴趣,决策周期短",
+        advantage: "快速成交,提升单笔订单价值",
+        successRate: "68%",
+        isDefault: true
+      },
+      {
+        ...this.strategies[1],
+        highlight: "",
+        applicable: "客户在多个选项间犹豫",
+        advantage: "平衡双方利益,建立长期关系",
+        successRate: "82%",
+        isDefault: true
+      },
+      {
+        ...this.strategies[2],
+        highlight: "",
+        applicable: "客户犹豫不决,决策周期长",
+        advantage: "降低客户风险感知,提高转化率",
+        successRate: "75%",
+        isDefault: true
+      }
+    ];
+  }
+  
+  setRole(role: MessageRole): void {
+    this.currentRole = role;
+  }
+  
+  addTag(): void {
+    const newTag: Tag = {
+      text: '新标签',
+      icon: 'fas fa-tag',
+      background: 'rgba(100, 160, 255, 0.15)',
+      color: '#64a0ff'
+    };
+    this.tags.push(newTag);
+  }
+  
+  removeTag(event: Event, tag: Tag): void {
+    event.stopPropagation();
+    this.tags = this.tags.filter(t => t !== tag);
+  }
+  
+  async sendMessage() {
+    if (!this.newMessage.trim() || !this.selectedScene) return;
+    
+    this.isProcessing = true;
+    
+    // 添加用户消息
+    const now = new Date();
+    this.addMessage(this.currentRole, this.newMessage.trim());
+    this.newMessage = '';
+    
+    // 如果是客户消息,更新策略分析
+    if (this.currentRole === 'customer') {
+      try {
+        // 显示加载状态
+        this.strategies.forEach(strategy => {
+          strategy.highlight = "分析中...";
+          strategy.applicable = "生成最佳策略...";
+          strategy.successRate = "--";
+          strategy.isDefault = false;
+        });
+        
+        // 更新策略分析(基于最新客户消息)
+        await this.updateStrategiesBasedOnMessage(this.messages[this.messages.length-1].content);
+      } catch (error) {
+        console.error('更新策略失败:', error);
+        // 失败时使用关键词匹配生成动态内容
+        const fallbackStrategies = this.generateDynamicStrategies(this.messages[this.messages.length-1].content);
+        this.updateStrategiesWithFallback(fallbackStrategies);
+      } finally {
+        this.isProcessing = false;
+      }
+    } else {
+      this.isProcessing = false;
+    }
+  }
+  
+  private addMessage(role: MessageRole, content: string): void {
+    const now = new Date();
+    this.messages.push({
+      role,
+      content,
+      time: `${now.getHours()}:${now.getMinutes().toString().padStart(2, '0')}`
+    });
+    this.scrollToBottom();
+  }
+  
+  // 根据客户消息动态更新策略卡片内容
+  private async updateStrategiesBasedOnMessage(customerMessage: string) {
+    try {
+      // 调用AI分析客户消息,生成策略建议
+      const analysis = await this.analyzeCustomerMessage(customerMessage);
+      
+      // 更新策略卡片内容
+      this.strategies[0] = {
+        ...this.strategies[0],
+        highlight: analysis.aggressive.highlight,
+        applicable: analysis.aggressive.applicable,
+        advantage: analysis.aggressive.advantage,
+        successRate: analysis.aggressive.successRate,
+        isDefault: false
+      };
+      
+      this.strategies[1] = {
+        ...this.strategies[1],
+        highlight: analysis.neutral.highlight,
+        applicable: analysis.neutral.applicable,
+        advantage: analysis.neutral.advantage,
+        successRate: analysis.neutral.successRate,
+        isDefault: false
+      };
+      
+      this.strategies[2] = {
+        ...this.strategies[2],
+        highlight: analysis.conservative.highlight,
+        applicable: analysis.conservative.applicable,
+        advantage: analysis.conservative.advantage,
+        successRate: analysis.conservative.successRate,
+        isDefault: false
+      };
+      
+      // 根据消息内容选择最合适的策略
+      this.selectBestStrategy(customerMessage);
+    } catch (error) {
+      console.error('更新策略失败:', error);
+      throw error; // 让调用者处理错误
+    }
+  }
+  
+  // 使用备用策略更新(AI调用失败时)
+  private updateStrategiesWithFallback(strategies: ReturnType<typeof this.generateDynamicStrategies>) {
+    this.strategies[0] = {
+      ...this.strategies[0],
+      highlight: strategies.aggressive.highlight,
+      applicable: "客户表现出明确兴趣,决策周期短",
+      successRate: "68%",
+      isDefault: false
+    };
+    
+    this.strategies[1] = {
+      ...this.strategies[1],
+      highlight: strategies.neutral.highlight,
+      applicable: "客户在多个选项间犹豫",
+      successRate: "82%",
+      isDefault: false
+    };
+    
+    this.strategies[2] = {
+      ...this.strategies[2],
+      highlight: strategies.conservative.highlight,
+      applicable: "客户犹豫不决,决策周期长",
+      successRate: "75%",
+      isDefault: false
+    };
+    
+    // 仍然选择最佳策略
+    this.selectBestStrategy(this.messages[this.messages.length-1].content);
+  }
+  
+  // 生成动态策略内容(关键词匹配)
+  private generateDynamicStrategies(customerMessage: string) {
+    // 关键词匹配,确保内容随客户消息变化
+    let focus = "酒店服务";
+    if (customerMessage.toLowerCase().includes("价格")) focus = "价格优势";
+    if (customerMessage.toLowerCase().includes("位置")) focus = "地理位置";
+    if (customerMessage.toLowerCase().includes("设施")) focus = "设施特色";
+    if (customerMessage.toLowerCase().includes("日期")) focus = "日期灵活性";
+    if (customerMessage.toLowerCase().includes("容量")) focus = "场地容量";
+    
+    return {
+      aggressive: {
+        highlight: `立即强调${focus},提供专属限时优惠`,
+        applicable: "客户表现出明确兴趣",
+        advantage: "快速促成交易",
+        successRate: "68%"
+      },
+      neutral: {
+        highlight: `提供${focus}的3种选择方案,满足不同需求`,
+        applicable: "客户需要比较选择",
+        advantage: "提高选择满意度",
+        successRate: "82%"
+      },
+      conservative: {
+        highlight: `详细说明${focus}的优势,提供案例参考`,
+        applicable: "客户需要更多决策信息",
+        advantage: "降低决策阻力",
+        successRate: "75%"
+      }
+    };
+  }
+  
+  activateCard(index: number): void {
+    this.activeCardIndex = index;
+  }
+  
+  getCardTransform(index: number): string {
+    if (index === this.activeCardIndex) {
+      return 'translateY(0) scale(1)';
+    } else if (Math.abs(index - this.activeCardIndex) === 1) {
+      return `translateY(${20 * Math.abs(index - this.activeCardIndex)}px) scale(${1 - 0.05 * Math.abs(index - this.activeCardIndex)})`;
+    } else {
+      return `translateY(${40 * Math.abs(index - this.activeCardIndex)}px) scale(${1 - 0.1 * Math.abs(index - this.activeCardIndex)})`;
+    }
+  }
+  
+  getCardZIndex(index: number): string {
+    if (index === this.activeCardIndex) {
+      return '30';
+    } else if (Math.abs(index - this.activeCardIndex) === 1) {
+      return '20';
+    } else {
+      return '10';
+    }
+  }
+  
+  // 文件上传处理
+  onFileSelected(event: Event) {
+    const input = event.target as HTMLInputElement;
+    if (!input.files || input.files.length === 0) return;
+    
+    const file = input.files[0];
+    if (file.type !== 'application/json' && !file.name.endsWith('.json')) {
+      alert('请上传JSON格式的文件');
+      return;
+    }
+    
+    const reader = new FileReader();
+    reader.onload = (e: ProgressEvent<FileReader>) => {
+      try {
+        const content = e.target?.result as string;
+        const data = JSON.parse(content);
+        
+        if (Array.isArray(data) && data.every(item => item.role && item.content && item.time)) {
+          this.messages = data as Message[];
+          this.updateStrategiesBasedOnMessages();
+          this.fileInput.nativeElement.value = ''; // 清空文件选择
+        } else {
+          alert('文件格式不正确,请上传有效的聊天记录JSON文件');
+        }
+      } catch (error) {
+        console.error('文件解析错误:', error);
+        alert('解析文件时出错');
+      }
+    };
+    reader.readAsText(file);
+  }
+  
+  // 根据完整对话更新策略
+  private updateStrategiesBasedOnMessages() {
+    // 从对话中提取最后一条客户消息
+    const lastCustomerMessage = this.messages
+      .filter(msg => msg.role === 'customer')
+      .pop();
+      
+    if (lastCustomerMessage) {
+      this.updateStrategiesBasedOnMessage(lastCustomerMessage.content);
+    }
+  }
+  
+  // 保存记录
+  saveRecord() {
+    if (!this.messages.length) {
+      alert('没有可保存的对话记录');
+      return;
+    }
+    
+    const record: ChatRecord = {
+      id: Date.now().toString(),
+      scene: this.selectedScene,
+      messages: [...this.messages],
+      tags: [...this.tags],
+      strategies: [...this.strategies],
+      timestamp: Date.now()
+    };
+    
+    this.chatRecords.unshift(record); // 添加到开头
+    this.saveRecords();
+    alert('记录已保存');
+  }
+  
+  // 加载记录
+  loadRecords() {
+    const records = localStorage.getItem('chatRecords');
+    if (records) {
+      this.chatRecords = JSON.parse(records);
+    }
+  }
+  
+  // 保存记录到本地存储
+  saveRecords() {
+    localStorage.setItem('chatRecords', JSON.stringify(this.chatRecords));
+  }
+  
+  // 查看历史
+  viewHistory() {
+    this.showHistoryModal = true;
+  }
+  
+  // 选择历史记录
+  selectRecord(record: ChatRecord) {
+    this.selectedScene = record.scene;
+    this.sceneTitle = record.scene === 'first-contact' ? '首次接触' : '深度交流';
+    this.messages = [...record.messages];
+    this.tags = [...record.tags];
+    this.strategies = [...record.strategies];
+    
+    // 初始化AI助手
+    const systemPrompt = this.scenePrompts[record.scene];
+    this.aiAssistant = new TestCompletion([
+      { role: "system", content: systemPrompt },
+      ...record.messages.map(msg => ({ 
+        role: msg.role === 'customer' ? 'user' : msg.role, 
+        content: msg.content 
+      }))
+    ]);
+    
+    this.showHistoryModal = false;
+  }
+  
+  // 删除历史记录
+  deleteRecord(record: ChatRecord) {
+    if (confirm('确定要删除这条记录吗?')) {
+      this.chatRecords = this.chatRecords.filter(r => r.id !== record.id);
+      this.saveRecords();
+    }
+  }
+  
+  // 格式化日期
+  formatDate(timestamp: number) {
+    const date = new Date(timestamp);
+    return `${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
+  }
+  
+  // 导出对话记录为JSON文件
+  exportChat() {
+    if (!this.messages.length) {
+      alert('没有可导出的对话记录');
+      return;
+    }
+    
+    const jsonContent = JSON.stringify(this.messages, null, 2);
+    const blob = new Blob([jsonContent], { type: 'application/json' });
+    const url = URL.createObjectURL(blob);
+    
+    const a = document.createElement('a');
+    a.href = url;
+    a.download = `chat-record-${new Date().toISOString().slice(0,10)}.json`;
+    document.body.appendChild(a);
+    a.click();
+    document.body.removeChild(a);
+    URL.revokeObjectURL(url);
+  }
+  
+  // 生成AI回复
+  async generateAIResponse() {
+    if (!this.messages.length || this.messages[this.messages.length - 1].role === 'assistant') {
+      alert('请先发送客户消息');
+      return;
+    }
+    
+    this.isProcessing = true;
+    
+    try {
+      const aiReply = await this.aiAssistant.sendMessage(
+        [...this.messages.map(msg => ({ 
+          role: msg.role === 'customer' ? 'user' : msg.role, 
+          content: msg.content 
+        }))],
+        (partialContent) => {
+          const lastMsg = this.messages[this.messages.length - 1];
+          if (lastMsg.role === 'assistant') {
+            lastMsg.content = partialContent;
+          } else {
+            this.addMessage('assistant', partialContent);
+          }
+        }
+      );
+      
+      if (typeof aiReply === 'string') {
+        this.addMessage('assistant', aiReply);
+        // 基于最新客户消息更新策略
+        const lastCustomerMsg = this.messages
+          .filter(msg => msg.role === 'customer')
+          .pop();
+          
+        if (lastCustomerMsg) {
+          this.updateStrategiesBasedOnMessage(lastCustomerMsg.content);
+        }
+      }
+    } catch (error) {
+      console.error('AI交互错误:', error);
+      this.addMessage('assistant', '抱歉,处理您的请求时出现了问题。');
+    } finally {
+      this.isProcessing = false;
+    }
+  }
+  
+  // 调用AI分析客户消息并生成策略建议
+  private async analyzeCustomerMessage(customerMessage: string): Promise<StrategyAnalysis> {
+    // 构建提示词,引导AI生成三种策略
+    const prompt = `
+    客户消息: "${customerMessage}"
+    
+    请针对此客户消息,为酒店销售顾问生成三种应对策略:
+    1. 主动型策略: 直接、果断的销售方法
+    2. 平衡型策略: 中等强度的销售方法
+    3. 保守型策略: 温和、信息丰富的销售方法
+    
+    请以JSON格式返回,包含以下字段:
+    {
+      "aggressive": {
+        "title": "主动型策略标题",
+        "highlight": "核心建议",
+        "applicable": "适用场景",
+        "advantage": "主要优势",
+        "successRate": "成功率百分比"
+      },
+      "neutral": {
+        "title": "平衡型策略标题",
+        "highlight": "核心建议",
+        "applicable": "适用场景",
+        "advantage": "主要优势",
+        "successRate": "成功率百分比"
+      },
+      "conservative": {
+        "title": "保守型策略标题",
+        "highlight": "核心建议",
+        "applicable": "适用场景",
+        "advantage": "主要优势",
+        "successRate": "成功率百分比"
+      }
+    }
+    `;
+    
+    // 使用TestCompletion调用AI
+    const strategyAnalyzer = new TestCompletion([
+      { role: "system", content: "你是一位专业的酒店销售策略顾问,擅长根据客户消息生成针对性的销售策略。" },
+      { role: "user", content: prompt }
+    ]);
+    
+    // 获取AI响应
+    const response = await strategyAnalyzer.sendMessage([
+      { role: "user", content: prompt }
+    ],(content)=>{
+      this.aicontent = content
+    });
+    
+    // 解析JSON响应
+    try {
+      let json = extractJSON(response)
+      return json;
+    } catch (parseError) {
+      console.error('解析策略分析失败:', parseError);
+      // 返回默认策略
+      return this.getDefaultStrategies();
+    }
+  }
+  aicontent:string = ""
+  // 根据客户消息选择最合适的策略
+  private selectBestStrategy(customerMessage: string) {
+    const lowerCaseMsg = customerMessage.toLowerCase();
+    
+    if (lowerCaseMsg.includes('价格') || lowerCaseMsg.includes('优惠')) {
+      this.activeCardIndex = 1; // 平衡型策略通常最适合价格讨论
+    } else if (lowerCaseMsg.includes('兴趣') || lowerCaseMsg.includes('考虑')) {
+      this.activeCardIndex = 0; // 主动型策略适合有兴趣的客户
+    } else if (lowerCaseMsg.includes('犹豫') || lowerCaseMsg.includes('比较')) {
+      this.activeCardIndex = 2; // 保守型策略适合犹豫的客户
+    } else {
+      // 默认选择平衡型策略
+      this.activeCardIndex = 1;
+    }
+  }
+  
+  // 返回默认策略(当AI分析失败时使用)
+  private getDefaultStrategies(): StrategyAnalysis {
+    return {
+      aggressive: {
+        title: "主动型策略",
+        highlight: "直接介绍酒店特色和限时优惠",
+        applicable: "客户表现出明确兴趣,决策周期短",
+        advantage: "快速成交,提升单笔订单价值",
+        successRate: "68%"
+      },
+      neutral: {
+        title: "平衡型策略",
+        highlight: "提供套餐选择,引导客户了解不同房型",
+        applicable: "客户在多个选项间犹豫",
+        advantage: "平衡双方利益,建立长期关系",
+        successRate: "82%"
+      },
+      conservative: {
+        title: "保守型策略",
+        highlight: "邀请客户参观虚拟酒店,提供详细资料",
+        applicable: "客户犹豫不决,决策周期长",
+        advantage: "降低客户风险感知,提高转化率",
+        successRate: "75%"
+      }
+    };
+  }
+}

+ 1 - 1
package-lock.json

@@ -1,5 +1,5 @@
 {
-  "name": "AI-Salesperson Assistant",
+  "name": "ai-salesperson-assistant",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {}