From a2b4fcdf36d2327da250671b06346a5f995a6c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=9D=B0=20Jie=20Zhao=20=EF=BC=88=E9=9B=84?= =?UTF-8?q?=E7=8B=AE=E6=B1=BD=E8=BD=A6=E7=A7=91=E6=8A=80=EF=BC=89?= <00061074@chery.local> Date: Mon, 22 Sep 2025 11:54:14 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BF=AB=E9=80=9F=E6=8F=90=E4=BA=A4=20?= =?UTF-8?q?-=20=E5=91=A8=E4=B8=80=202025/09/22=2011:54:13.80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AI建议权限问题最终解决方案.md | 218 ++++++++++++++ auto_push.bat | 99 ++++++- quick_push.bat | 86 +++++- src/integrations/feishu_client.py | 10 + src/integrations/feishu_permission_checker.py | 271 ++++++++++++++++++ src/integrations/workorder_sync.py | 3 +- src/web/blueprints/feishu_sync.py | 17 ++ src/web/static/js/dashboard.js | 166 +++++++++-- src/web/templates/dashboard.html | 3 + test.md | Bin 0 -> 80 bytes 10 files changed, 848 insertions(+), 25 deletions(-) create mode 100644 AI建议权限问题最终解决方案.md create mode 100644 src/integrations/feishu_permission_checker.py create mode 100644 test.md diff --git a/AI建议权限问题最终解决方案.md b/AI建议权限问题最终解决方案.md new file mode 100644 index 0000000..0ed77c9 --- /dev/null +++ b/AI建议权限问题最终解决方案.md @@ -0,0 +1,218 @@ +# AI建议权限问题最终解决方案 + +## 🔍 问题确认 + +通过深度诊断工具确认: +- ✅ **访问令牌获取正常**:tenant_access_token获取成功 +- ✅ **表格访问正常**:可以成功访问表格和字段信息 +- ✅ **记录读取正常**:可以成功读取表格记录 +- ✅ **AI建议字段存在**:确认表格中有"AI建议"字段 +- ❌ **记录写入失败**:403 Forbidden,权限不足 + +**根本原因**:飞书应用缺少**记录写入权限** + +## 🛠️ 最终解决方案 + +### 步骤1:飞书开放平台权限配置 + +#### 1.1 登录飞书开放平台 +1. 访问:https://open.feishu.cn/ +2. 使用管理员账号登录 +3. 进入"应用管理" → "我的应用" + +#### 1.2 找到您的应用 +- 应用ID:`cli_a8b50ec0eed1500d` +- 应用名称:您的TSP智能助手应用 + +#### 1.3 添加必需权限 +在"权限管理"页面,确保应用具有以下权限: + +**核心权限**: +``` +bitable:app # 多维表格应用权限 +bitable:app:readonly # 多维表格只读权限 +bitable:app:readwrite # 多维表格读写权限 ⭐ 关键权限 +base:record:write # 记录写入权限 ⭐ 关键权限 +``` + +#### 1.4 重新发布应用 +- 权限修改后,点击"发布"或"上线" +- 等待权限生效(通常需要1-5分钟) + +### 步骤2:飞书多维表格协作者权限 + +#### 2.1 打开飞书多维表格 +- 使用浏览器打开您的飞书多维表格 +- 确保您有表格的管理权限 + +#### 2.2 添加应用为协作者 +1. 点击表格右上角的"分享"按钮 +2. 点击"添加协作者" +3. 搜索您的飞书应用名称或应用ID +4. 将应用添加为协作者 + +#### 2.3 设置协作者权限 +**重要**:将权限设置为以下之一: +- **编辑者** ✅ (推荐) +- **管理员** ✅ (完全权限) + +**不要设置为**: +- **查看者** ❌ (只能读取,无法写入) + +### 步骤3:验证修复结果 + +#### 3.1 使用Web界面验证 +1. 打开TSP智能助手主页面 +2. 点击"飞书同步"标签页 +3. 点击"权限检查"按钮 +4. 查看检查结果 + +#### 3.2 测试AI建议功能 +1. 点击"同步+AI建议"按钮 +2. 查看是否还有403错误 +3. 检查飞书表格中是否出现AI建议 + +## 📋 详细修复步骤 + +### 🔧 飞书开放平台操作 + +1. **登录飞书开放平台** + ``` + URL: https://open.feishu.cn/ + 账号: 管理员账号 + ``` + +2. **进入应用管理** + ``` + 路径: 应用管理 → 我的应用 + 应用ID: cli_a8b50ec0eed1500d + ``` + +3. **权限配置** + ``` + 页面: 权限管理 + 操作: 添加权限 + 权限列表: + - bitable:app + - bitable:app:readonly + - bitable:app:readwrite ⭐ + - base:record:write ⭐ + ``` + +4. **发布应用** + ``` + 操作: 点击"发布"按钮 + 等待: 1-5分钟权限生效 + ``` + +### 🔧 飞书多维表格操作 + +1. **打开表格** + ``` + 方式: 浏览器访问飞书多维表格 + 权限: 确保有管理权限 + ``` + +2. **添加协作者** + ``` + 操作: 点击右上角"分享"按钮 + 步骤: 添加协作者 → 搜索应用名称 + 应用: 您的TSP智能助手应用 + ``` + +3. **设置权限** + ``` + 权限级别: 编辑者 或 管理员 + 不要选择: 查看者 + 保存: 确认设置 + ``` + +## 🚨 常见问题解决 + +### 问题1:找不到权限设置 +**解决方案**: +- 确保使用管理员账号登录飞书开放平台 +- 检查应用是否已发布 +- 联系飞书技术支持 + +### 问题2:权限添加后仍然失败 +**解决方案**: +- 等待5-10分钟让权限生效 +- 重新发布应用 +- 清除浏览器缓存后重试 + +### 问题3:找不到应用名称 +**解决方案**: +- 使用应用ID:`cli_a8b50ec0eed1500d` +- 在飞书开放平台搜索应用ID +- 确认应用状态为"已发布" + +### 问题4:表格分享设置找不到 +**解决方案**: +- 确保您有表格的管理权限 +- 使用表格创建者账号 +- 联系表格管理员协助设置 + +## 📊 权限配置检查清单 + +### ✅ 飞书开放平台 +- [ ] 应用已启用并发布 +- [ ] 已添加`bitable:app`权限 +- [ ] 已添加`bitable:app:readonly`权限 +- [ ] 已添加`bitable:app:readwrite`权限 ⭐ +- [ ] 已添加`base:record:write`权限 ⭐ +- [ ] 权限修改后已重新发布 + +### ✅ 飞书多维表格 +- [ ] 应用已添加为表格协作者 +- [ ] 协作者权限设置为"编辑者"或"管理员" +- [ ] 表格未被锁定或设置为只读 +- [ ] "AI建议"字段存在且类型正确 + +## 🎯 验证成功标志 + +修复成功后,您应该看到: + +1. **权限检查通过** + ``` + ✅ 访问令牌获取成功 + ✅ 表格访问权限正常 + ✅ 记录读取权限正常 + ✅ 记录写入权限正常 + ✅ AI建议字段存在 + ``` + +2. **AI建议功能正常** + ``` + - 点击"同步+AI建议"无403错误 + - 飞书表格中出现AI建议内容 + - 日志显示"更新飞书AI建议成功" + ``` + +3. **系统日志正常** + ``` + 2025-09-22 XX:XX:XX - INFO - 更新飞书AI建议成功 + 2025-09-22 XX:XX:XX - INFO - 飞书同步完成 + ``` + +## 📞 技术支持 + +如果按照以上步骤仍然无法解决问题,请: + +1. **收集信息**: + - 飞书应用ID:`cli_a8b50ec0eed1500d` + - 表格ID:`tblnl3vJPpgMTSiP` + - 完整的错误日志 + - 权限检查结果截图 + +2. **联系支持**: + - 飞书开放平台技术支持 + - TSP智能助手技术支持 + +3. **检查企业设置**: + - 确认是否有企业级权限限制 + - 联系企业飞书管理员 + +--- + +**重要提醒**:403权限错误通常需要飞书管理员权限才能解决,建议联系相关技术人员协助配置。修复完成后,AI建议功能将可以正常工作!🎉 diff --git a/auto_push.bat b/auto_push.bat index 6de3b7d..4cf32a9 100644 --- a/auto_push.bat +++ b/auto_push.bat @@ -38,15 +38,104 @@ if %errorlevel% neq 0 ( ) echo ✅ 文件已添加到暂存区 -:: 生成提交信息 +:: 检查markdown文件修改并生成智能提交信息 echo. -echo [3/4] 生成提交信息... -for /f "tokens=*" %%i in ('git log --oneline -1') do set last_commit=%%i -set /p commit_msg="请输入提交信息 (直接回车使用默认): " -if "%commit_msg%"=="" ( +echo [3/4] 分析markdown文件并生成提交信息... + +:: 检查是否有markdown文件修改 +set md_files= +for /f "tokens=*" %%f in ('git diff --name-only --cached 2^>nul ^| findstr /i "\.md$"') do ( + set md_files=!md_files! %%f +) +for /f "tokens=*" %%f in ('git diff --name-only 2^>nul ^| findstr /i "\.md$"') do ( + set md_files=!md_files! %%f +) + +set commit_msg= +if not "%md_files%"=="" ( + echo 📝 检测到markdown文件修改: + echo %md_files% + echo. + + :: 提取markdown文件的主要内容 + set commit_title= + set commit_type=docs + + :: 检查是否有修复相关内容 + for %%f in (%md_files%) do ( + if exist "%%f" ( + for /f "tokens=*" %%l in ('type "%%f" ^| findstr /i "修复\|解决\|问题\|错误"') do ( + set commit_type=fix + set commit_title=修复问题 + goto :found_fix + ) + ) + ) + + :: 检查是否有新功能相关内容 + for %%f in (%md_files%) do ( + if exist "%%f" ( + for /f "tokens=*" %%l in ('type "%%f" ^| findstr /i "功能\|新增\|添加\|实现"') do ( + set commit_type=feat + set commit_title=新增功能 + goto :found_feature + ) + ) + ) + + :: 检查是否有优化相关内容 + for %%f in (%md_files%) do ( + if exist "%%f" ( + for /f "tokens=*" %%l in ('type "%%f" ^| findstr /i "优化\|性能\|改进\|提升"') do ( + set commit_type=perf + set commit_title=性能优化 + goto :found_optimization + ) + ) + ) + + :: 提取文件标题 + for %%f in (%md_files%) do ( + if exist "%%f" ( + for /f "tokens=*" %%l in ('type "%%f" ^| findstr /n "^#" ^| head -1') do ( + set line=%%l + set line=!line:*:=! + set line=!line:# =! + set line=!line:## =! + if "!line!" neq "" ( + set commit_title=!line! + goto :found_title + ) + ) + ) + ) + + :found_fix + :found_feature + :found_optimization + :found_title + + if "%commit_title%"=="" ( + set commit_title=更新文档记录 + ) + + :: 生成提交信息 + set commit_msg=%commit_type%: %commit_title% + echo 📋 生成的提交信息: %commit_msg% + echo. +) else ( + echo ℹ️ 没有检测到markdown文件修改 set commit_msg=feat: 自动提交 - %date% %time% ) +:: 询问是否使用生成的提交信息 +set /p confirm="是否使用此提交信息? (y/n/e编辑): " +if /i "%confirm%"=="e" ( + set /p commit_msg="请输入自定义提交信息: " +) else if /i "%confirm%" neq "y" ( + set /p commit_msg="请输入提交信息: " +) + :: 提交更改 echo 提交信息: %commit_msg% git commit -m "%commit_msg%" diff --git a/quick_push.bat b/quick_push.bat index 93597d5..dbaebda 100644 --- a/quick_push.bat +++ b/quick_push.bat @@ -1,11 +1,95 @@ @echo off chcp 65001 >nul +setlocal enabledelayedexpansion echo 🚀 TSP智能助手 - 快速推送 echo. :: 检查是否有参数 if "%1"=="" ( - set commit_msg=feat: 快速提交 - %date% %time% + :: 智能生成提交信息 + echo 📝 分析markdown文件并生成提交信息... + + :: 检查是否有markdown文件修改 + set md_files= + for /f "tokens=*" %%f in ('git diff --name-only --cached 2^>nul ^| findstr /i "\.md$"') do ( + set md_files=!md_files! %%f + ) + for /f "tokens=*" %%f in ('git diff --name-only 2^>nul ^| findstr /i "\.md$"') do ( + set md_files=!md_files! %%f + ) + + set commit_msg= + if not "%md_files%"=="" ( + echo 📄 检测到markdown文件修改: %md_files% + + :: 提取markdown文件的主要内容 + set commit_title= + set commit_type=docs + + :: 检查是否有修复相关内容 + for %%f in (%md_files%) do ( + if exist "%%f" ( + for /f "tokens=*" %%l in ('type "%%f" ^| findstr /i "修复\|解决\|问题\|错误"') do ( + set commit_type=fix + set commit_title=修复问题 + goto :found_fix + ) + ) + ) + + :: 检查是否有新功能相关内容 + for %%f in (%md_files%) do ( + if exist "%%f" ( + for /f "tokens=*" %%l in ('type "%%f" ^| findstr /i "功能\|新增\|添加\|实现"') do ( + set commit_type=feat + set commit_title=新增功能 + goto :found_feature + ) + ) + ) + + :: 检查是否有优化相关内容 + for %%f in (%md_files%) do ( + if exist "%%f" ( + for /f "tokens=*" %%l in ('type "%%f" ^| findstr /i "优化\|性能\|改进\|提升"') do ( + set commit_type=perf + set commit_title=性能优化 + goto :found_optimization + ) + ) + ) + + :: 提取文件标题 + for %%f in (%md_files%) do ( + if exist "%%f" ( + for /f "tokens=*" %%l in ('type "%%f" ^| findstr /n "^#" ^| head -1') do ( + set line=%%l + set line=!line:*:=! + set line=!line:# =! + set line=!line:## =! + if "!line!" neq "" ( + set commit_title=!line! + goto :found_title + ) + ) + ) + ) + + :found_fix + :found_feature + :found_optimization + :found_title + + if "%commit_title%"=="" ( + set commit_title=更新文档记录 + ) + + :: 生成提交信息 + set commit_msg=%commit_type%: %commit_title% + ) else ( + echo ℹ️ 没有检测到markdown文件修改 + set commit_msg=feat: 快速提交 - %date% %time% + ) ) else ( set commit_msg=%1 ) diff --git a/src/integrations/feishu_client.py b/src/integrations/feishu_client.py index 4433fee..079762a 100644 --- a/src/integrations/feishu_client.py +++ b/src/integrations/feishu_client.py @@ -88,6 +88,16 @@ class FeishuClient: response = requests.request(method, url, timeout=30, **kwargs) logger.info(f"飞书API响应状态码: {response.status_code}") + # 处理403权限错误 + if response.status_code == 403: + try: + error_data = response.json() + logger.error(f"飞书API权限错误: {error_data}") + raise Exception(f"飞书API权限不足: {error_data.get('msg', '未知权限错误')}") + except: + logger.error(f"飞书API权限错误,无法解析响应内容") + raise Exception(f"飞书API权限不足,状态码: {response.status_code}") + response.raise_for_status() result = response.json() logger.info(f"飞书API响应内容: {result}") diff --git a/src/integrations/feishu_permission_checker.py b/src/integrations/feishu_permission_checker.py new file mode 100644 index 0000000..f0363d3 --- /dev/null +++ b/src/integrations/feishu_permission_checker.py @@ -0,0 +1,271 @@ +# -*- coding: utf-8 -*- +""" +飞书权限检查工具 +用于诊断和解决飞书API权限问题 +""" + +import logging +from typing import Dict, Any, List +from src.integrations.feishu_client import FeishuClient +from src.integrations.config_manager import config_manager + +logger = logging.getLogger(__name__) + +class FeishuPermissionChecker: + """飞书权限检查器""" + + def __init__(self): + self.feishu_config = config_manager.get_feishu_config() + self.client = None + + if self.feishu_config.get("app_id") and self.feishu_config.get("app_secret"): + self.client = FeishuClient( + self.feishu_config["app_id"], + self.feishu_config["app_secret"] + ) + + def check_permissions(self) -> Dict[str, Any]: + """ + 检查飞书应用权限 + + Returns: + 权限检查结果 + """ + result = { + "success": False, + "checks": {}, + "recommendations": [], + "errors": [] + } + + if not self.client: + result["errors"].append("飞书客户端未初始化,请检查配置") + return result + + # 1. 检查访问令牌 + try: + token = self.client._get_access_token() + if token: + result["checks"]["access_token"] = { + "status": "success", + "message": "访问令牌获取成功" + } + else: + result["checks"]["access_token"] = { + "status": "failed", + "message": "无法获取访问令牌" + } + result["errors"].append("无法获取访问令牌") + except Exception as e: + result["checks"]["access_token"] = { + "status": "failed", + "message": f"访问令牌获取失败: {e}" + } + result["errors"].append(f"访问令牌获取失败: {e}") + + # 2. 检查应用权限 + try: + app_token = self.feishu_config.get("app_token") + table_id = self.feishu_config.get("table_id") + + if not app_token or not table_id: + result["errors"].append("缺少app_token或table_id配置") + return result + + # 尝试获取表格信息 + table_info = self._get_table_info(app_token, table_id) + if table_info: + result["checks"]["table_access"] = { + "status": "success", + "message": "可以访问表格" + } + else: + result["checks"]["table_access"] = { + "status": "failed", + "message": "无法访问表格" + } + result["errors"].append("无法访问表格") + + except Exception as e: + result["checks"]["table_access"] = { + "status": "failed", + "message": f"表格访问失败: {e}" + } + result["errors"].append(f"表格访问失败: {e}") + + # 3. 检查记录读取权限 + try: + records = self.client.get_table_records(app_token, table_id, page_size=1) + if records.get("code") == 0: + result["checks"]["read_records"] = { + "status": "success", + "message": "可以读取记录" + } + else: + result["checks"]["read_records"] = { + "status": "failed", + "message": f"读取记录失败: {records.get('msg', '未知错误')}" + } + result["errors"].append(f"读取记录失败: {records.get('msg', '未知错误')}") + except Exception as e: + result["checks"]["read_records"] = { + "status": "failed", + "message": f"读取记录失败: {e}" + } + result["errors"].append(f"读取记录失败: {e}") + + # 4. 检查记录更新权限 + try: + # 先获取一条记录进行测试 + records = self.client.get_table_records(app_token, table_id, page_size=1) + if records.get("code") == 0 and records.get("data", {}).get("items"): + test_record = records["data"]["items"][0] + record_id = test_record["record_id"] + + # 尝试更新一个测试字段 + update_result = self.client.update_table_record( + app_token, + table_id, + record_id, + {"测试字段": "权限测试"} + ) + + if update_result.get("code") == 0: + result["checks"]["update_records"] = { + "status": "success", + "message": "可以更新记录" + } + else: + result["checks"]["update_records"] = { + "status": "failed", + "message": f"更新记录失败: {update_result.get('msg', '未知错误')}" + } + result["errors"].append(f"更新记录失败: {update_result.get('msg', '未知错误')}") + else: + result["checks"]["update_records"] = { + "status": "failed", + "message": "没有记录可用于测试更新权限" + } + result["errors"].append("没有记录可用于测试更新权限") + + except Exception as e: + result["checks"]["update_records"] = { + "status": "failed", + "message": f"更新记录测试失败: {e}" + } + result["errors"].append(f"更新记录测试失败: {e}") + + # 5. 检查AI建议字段权限 + try: + # 检查AI建议字段是否存在 + table_fields = self._get_table_fields(app_token, table_id) + if table_fields: + ai_field_exists = any( + field.get("field_name") == "AI建议" + for field in table_fields.get("data", {}).get("items", []) + ) + + if ai_field_exists: + result["checks"]["ai_field"] = { + "status": "success", + "message": "AI建议字段存在" + } + else: + result["checks"]["ai_field"] = { + "status": "warning", + "message": "AI建议字段不存在" + } + result["recommendations"].append("请在飞书表格中添加'AI建议'字段") + else: + result["checks"]["ai_field"] = { + "status": "failed", + "message": "无法获取表格字段信息" + } + result["errors"].append("无法获取表格字段信息") + + except Exception as e: + result["checks"]["ai_field"] = { + "status": "failed", + "message": f"检查AI建议字段失败: {e}" + } + result["errors"].append(f"检查AI建议字段失败: {e}") + + # 生成建议 + self._generate_recommendations(result) + + # 判断整体状态 + failed_checks = [check for check in result["checks"].values() + if check["status"] == "failed"] + if not failed_checks: + result["success"] = True + + return result + + def _get_table_info(self, app_token: str, table_id: str) -> Dict[str, Any]: + """获取表格信息""" + try: + url = f"{self.client.base_url}/bitable/v1/apps/{app_token}/tables/{table_id}" + return self.client._make_request("GET", url) + except Exception as e: + logger.error(f"获取表格信息失败: {e}") + return None + + def _get_table_fields(self, app_token: str, table_id: str) -> Dict[str, Any]: + """获取表格字段信息""" + try: + url = f"{self.client.base_url}/bitable/v1/apps/{app_token}/tables/{table_id}/fields" + return self.client._make_request("GET", url) + except Exception as e: + logger.error(f"获取表格字段失败: {e}") + return None + + def _generate_recommendations(self, result: Dict[str, Any]): + """生成修复建议""" + recommendations = result["recommendations"] + + # 基于检查结果生成建议 + if "access_token" in result["checks"] and result["checks"]["access_token"]["status"] == "failed": + recommendations.append("检查飞书应用的app_id和app_secret是否正确") + recommendations.append("确认飞书应用已启用并获取了必要的权限") + + if "table_access" in result["checks"] and result["checks"]["table_access"]["status"] == "failed": + recommendations.append("检查app_token和table_id是否正确") + recommendations.append("确认应用有访问该表格的权限") + + if "update_records" in result["checks"] and result["checks"]["update_records"]["status"] == "failed": + recommendations.append("检查飞书应用是否有'编辑'权限") + recommendations.append("确认表格没有被锁定或只读") + recommendations.append("检查应用是否被添加到表格的协作者中") + + if "ai_field" in result["checks"] and result["checks"]["ai_field"]["status"] == "warning": + recommendations.append("在飞书表格中添加'AI建议'字段") + recommendations.append("确保字段类型为'多行文本'或'单行文本'") + + # 通用建议 + if result["errors"]: + recommendations.append("查看飞书开放平台文档了解权限配置") + recommendations.append("联系飞书管理员确认应用权限设置") + + def get_permission_summary(self) -> str: + """获取权限检查摘要""" + result = self.check_permissions() + + summary = "飞书权限检查结果:\n" + summary += f"整体状态: {'✅ 正常' if result['success'] else '❌ 异常'}\n\n" + + summary += "检查项目:\n" + for check_name, check_result in result["checks"].items(): + status_icon = "✅" if check_result["status"] == "success" else "⚠️" if check_result["status"] == "warning" else "❌" + summary += f" {status_icon} {check_name}: {check_result['message']}\n" + + if result["recommendations"]: + summary += "\n修复建议:\n" + for i, rec in enumerate(result["recommendations"], 1): + summary += f" {i}. {rec}\n" + + if result["errors"]: + summary += "\n错误信息:\n" + for i, error in enumerate(result["errors"], 1): + summary += f" {i}. {error}\n" + + return summary diff --git a/src/integrations/workorder_sync.py b/src/integrations/workorder_sync.py index a6c1f8d..dc4fff6 100644 --- a/src/integrations/workorder_sync.py +++ b/src/integrations/workorder_sync.py @@ -437,12 +437,13 @@ class WorkOrderSyncService: def _update_feishu_ai_suggestion(self, record_id: str, ai_suggestion: str) -> bool: """更新飞书表格中的AI建议""" try: - result = self.feishu_client.update_record( + result = self.feishu_client.update_table_record( self.app_token, self.table_id, record_id, {"AI建议": ai_suggestion} ) + logger.info(f"更新飞书AI建议结果: {result}") return result.get("code") == 0 except Exception as e: logger.error(f"更新飞书AI建议失败: {e}") diff --git a/src/web/blueprints/feishu_sync.py b/src/web/blueprints/feishu_sync.py index 6794f81..f0b3f10 100644 --- a/src/web/blueprints/feishu_sync.py +++ b/src/web/blueprints/feishu_sync.py @@ -8,6 +8,7 @@ from flask import Blueprint, request, jsonify, render_template from src.integrations.feishu_client import FeishuClient from src.integrations.workorder_sync import WorkOrderSyncService from src.integrations.config_manager import config_manager +from src.integrations.feishu_permission_checker import FeishuPermissionChecker import logging logger = logging.getLogger(__name__) @@ -322,6 +323,22 @@ def remove_field_mapping(): logger.error(f"移除字段映射失败: {e}") return jsonify({"error": str(e)}), 500 +@feishu_sync_bp.route('/check-permissions') +def check_permissions(): + """检查飞书权限""" + try: + checker = FeishuPermissionChecker() + result = checker.check_permissions() + + return jsonify({ + "success": True, + "permission_check": result, + "summary": checker.get_permission_summary() + }) + except Exception as e: + logger.error(f"权限检查失败: {e}") + return jsonify({"error": str(e)}), 500 + @feishu_sync_bp.route('/field-mapping') def field_mapping_page(): """字段映射管理页面""" diff --git a/src/web/static/js/dashboard.js b/src/web/static/js/dashboard.js index 39c17a0..2b5d168 100644 --- a/src/web/static/js/dashboard.js +++ b/src/web/static/js/dashboard.js @@ -15,6 +15,11 @@ class TSPDashboard { this.init(); this.restorePageState(); + + // 添加页面卸载时的清理逻辑 + window.addEventListener('beforeunload', () => { + this.destroyAllCharts(); + }); } async generateAISuggestion(workorderId) { @@ -335,6 +340,14 @@ class TSPDashboard { this.currentTab = tabName; + // 如果切换到分析页面,重新初始化图表 + if (tabName === 'analytics') { + // 延迟一点时间确保DOM已更新 + setTimeout(() => { + this.initializeCharts(); + }, 100); + } + // 保存当前页面状态 this.savePageState(); @@ -3160,9 +3173,63 @@ class TSPDashboard { // 初始化图表 initializeCharts() { - this.charts = {}; + if (!this.charts) { + this.charts = {}; + } this.updateCharts(); } + + // 销毁所有图表 + destroyAllCharts() { + if (!this.charts) return; + + Object.keys(this.charts).forEach(chartId => { + if (this.charts[chartId]) { + try { + this.charts[chartId].destroy(); + } catch (e) { + console.warn(`Error destroying chart ${chartId}:`, e); + } + this.charts[chartId] = null; + } + }); + + // 清理charts对象 + this.charts = {}; + } + + // 安全的图表创建方法 + createChart(canvasId, chartConfig) { + const canvas = document.getElementById(canvasId); + if (!canvas) { + console.error(`Canvas element '${canvasId}' not found`); + return null; + } + + // 确保charts对象存在 + if (!this.charts) { + this.charts = {}; + } + + // 销毁现有图表 + if (this.charts[canvasId]) { + try { + this.charts[canvasId].destroy(); + } catch (e) { + console.warn(`Error destroying chart ${canvasId}:`, e); + } + this.charts[canvasId] = null; + } + + try { + const ctx = canvas.getContext('2d'); + this.charts[canvasId] = new Chart(ctx, chartConfig); + return this.charts[canvasId]; + } catch (e) { + console.error(`Error creating chart ${canvasId}:`, e); + return null; + } + } // 更新所有图表 async updateCharts() { @@ -3215,16 +3282,9 @@ class TSPDashboard { // 更新主图表 updateMainChart(data, chartType) { - const ctx = document.getElementById('mainChart').getContext('2d'); - - // 销毁现有图表 - if (this.charts.mainChart) { - this.charts.mainChart.destroy(); - } - const chartData = this.prepareChartData(data, chartType); - this.charts.mainChart = new Chart(ctx, { + const chartConfig = { type: chartType, data: chartData, options: { @@ -3257,22 +3317,18 @@ class TSPDashboard { } } } - }); + }; + + this.createChart('mainChart', chartConfig); } // 更新分布图表 updateDistributionChart(data) { - const ctx = document.getElementById('distributionChart').getContext('2d'); - - if (this.charts.distributionChart) { - this.charts.distributionChart.destroy(); - } - const categories = data.workorders?.by_category || {}; const labels = Object.keys(categories); const values = Object.values(categories); - this.charts.distributionChart = new Chart(ctx, { + const chartConfig = { type: 'doughnut', data: { labels: labels, @@ -3302,7 +3358,9 @@ class TSPDashboard { } } } - }); + }; + + this.createChart('distributionChart', chartConfig); } // 更新趋势图表 @@ -4901,6 +4959,78 @@ class FeishuSyncManager { } } + async checkPermissions() { + try { + this.showNotification('正在检查飞书权限...', 'info'); + + const response = await fetch('/api/feishu-sync/check-permissions'); + const data = await response.json(); + + if (data.success) { + this.displayPermissionCheck(data.permission_check, data.summary); + this.showNotification('权限检查完成', 'success'); + } else { + this.showNotification('权限检查失败: ' + data.error, 'error'); + } + } catch (error) { + this.showNotification('权限检查失败: ' + error.message, 'error'); + } + } + + displayPermissionCheck(permissionCheck, summary) { + const container = document.getElementById('fieldMappingContent'); + + let html = '
飞书权限检查结果
'; + + // 整体状态 + const statusClass = permissionCheck.success ? 'success' : 'danger'; + const statusIcon = permissionCheck.success ? 'check-circle' : 'exclamation-triangle'; + html += `
+ + 整体状态: ${permissionCheck.success ? '正常' : '异常'} +
`; + + // 检查项目 + html += '
检查项目:
'; + for (const [checkName, checkResult] of Object.entries(permissionCheck.checks)) { + const statusClass = checkResult.status === 'success' ? 'success' : + checkResult.status === 'warning' ? 'warning' : 'danger'; + const statusIcon = checkResult.status === 'success' ? 'check-circle' : + checkResult.status === 'warning' ? 'exclamation-triangle' : 'times-circle'; + + html += `
+ + ${checkName}: ${checkResult.message} +
`; + } + + // 修复建议 + if (permissionCheck.recommendations && permissionCheck.recommendations.length > 0) { + html += '
修复建议:
'; + } + + // 错误信息 + if (permissionCheck.errors && permissionCheck.errors.length > 0) { + html += '
错误信息:
'; + } + + html += '
'; + + container.innerHTML = html; + + // 显示字段映射管理区域 + const section = document.getElementById('fieldMappingSection'); + section.style.display = 'block'; + } + showNotification(message, type = 'info') { const container = document.getElementById('notificationContainer'); const alert = document.createElement('div'); diff --git a/src/web/templates/dashboard.html b/src/web/templates/dashboard.html index 416c965..46a0ebb 100644 --- a/src/web/templates/dashboard.html +++ b/src/web/templates/dashboard.html @@ -1148,6 +1148,9 @@ + diff --git a/test.md b/test.md new file mode 100644 index 0000000000000000000000000000000000000000..0d05c55e6de18021a13d51805a8c50a7e77319ca GIT binary patch literal 80 zcmezW&pY>OcP>LBLlHwZLkdGaLpehpLwo8sKVAkd24w~X29P{^#*7&dK1k%