diff --git a/PLAN3.md b/PLAN3.md new file mode 100644 index 0000000..abb0ac8 --- /dev/null +++ b/PLAN3.md @@ -0,0 +1,455 @@ +# Enterprise AI Platform - 问题修复与优化计划 v3 + +> 基于 log/3.log 接口报错分析 + 流编排页面问题排查 + PLAN2.md 剩余优化项 +> 日期: 2026-05-12 +> 状态: **待实施** + +--- + +## 一、log/3.log 接口报错分析 + +### 1.1 错误汇总 + +| # | 请求 | 状态码 | 错误类型 | 原因 | +|---|------|--------|---------|------| +| 1 | `GET /api/flow/definitions/%3Aid` | **422** | URL编码错误 | `:id` 被URL编码为 `%3Aid`,未替换为实际ID,与问题3同源 | +| 2 | `GET /api/wecom/config` | **500** | `NameError: name 'settings' is not defined` | `backend/modules/wecom/router.py` 第162行缺少 `from config import settings` | +| 3 | `ERROR modules/rag/knowledge.py:112` | (内部) | `Qdrant client is not installed` | 生产环境缺少 qdrant-client 依赖,知识检索功能不可用(非阻塞) | + +### 1.2 错误2详细分析(企微配置500) + +**文件**: `backend/modules/wecom/router.py` +- 第1-8行导入列表: `import uuid`, `import httpx`, `from fastapi import ...`, `from database import get_db`, `from models import User, ChatSession, ChatMessage` +- **缺失**: `from config import settings` ← 未导入 +- 第162行: `"status": "active" if settings.WECOM_CORP_ID else "unconfigured"` → `settings` 未定义 +- 第164行: `getattr(settings, 'WECOM_AGENT_ID', 0)` → 同样报错 +- 第201-202行 `update_wecom_config` 中: `hasattr(settings, key)` / `setattr(settings, key, value)` → 也用到 `settings` + +--- + +## 二、四个问题的根因分析与解决方案 + +--- + +### 问题1: `Unchecked runtime.lastError: The message port closed before a response was received.` + +#### 根因分析 + +这不是应用代码的bug,而是 **Chrome浏览器扩展(Extension)** 产生的警告。当浏览器扩展(如 Vue DevTools、广告拦截器、翻译插件等)尝试通过消息端口与页面通信时,如果端口在收到响应前被关闭,就会触发此警告。 + +具体触发路径: +1. 页面URL: `/#/admin/flow/editor` +2. 页面包含 iframe 或使用 `chrome.runtime` API 的扩展 +3. 扩展的消息端口在接收方响应前被关闭 + +#### 解决方案 + +**方案A(推荐)**: 在前端全局添加运行时错误过滤 + +文件: `frontend/src/main.ts` + +```typescript +// 过滤 Chrome 扩展产生的非关键错误 +if (typeof chrome !== 'undefined' && chrome.runtime) { + const originalError = window.onerror + window.onerror = function(message, source, lineno, colno, error) { + if (typeof message === 'string' && message.includes('message port closed')) { + return true // 静默忽略扩展通信错误 + } + if (originalError) { + return originalError(message, source, lineno, colno, error) + } + return false + } +} +``` + +**方案B**: 在 `index.html` 中添加: +```html + +``` + +**影响范围**: 全局,不影响任何业务功能 + +--- + +### 问题2: 节点面板的节点无法拖到画布上(无任何报错) + +#### 根因分析 + +**文件**: `frontend/src/views/flow/FlowEditor.vue` + +问题出在**拖拽事件使用了错误的DOM事件类型**,导致 `dataTransfer` 始终为 `null`: + +| 位置 | 当前代码 | 问题 | +|------|---------|------| +| 第25行 | `@mousedown="onDragStart($event, node)"` | ❌ 使用了 `mousedown` 事件,该事件对象上 **没有** `dataTransfer` 属性 | +| 第253行 | `function onDragStart(event: MouseEvent, ...)` | ❌ `MouseEvent` 类型上不存在 `dataTransfer` | +| 第255行 | `const dt = (event as any).dataTransfer` | ❌ 强行转类型,`mousedown` 事件的 `dataTransfer` 永远是 `undefined`,导致 `if (dt)` 条件永不成立,节点数据从未被设置 | +| 第29行 | `
` | ❌ 缺少 `draggable="true"` 属性,即使改用 `dragstart` 事件也无法触发拖拽 | + +**核心问题链**: +1. `mousedown` 事件没有 `dataTransfer` → `dt` 为 `undefined` +2. `if (vueFlowRef.value && dt)` 条件永远为 `false` +3. `dt.setData('application/vueflow', ...)` 永远不执行 +4. `onDrop` 中 `event.dataTransfer?.getData('application/vueflow')` 返回 `null` +5. 函数直接 `return`,不创建任何节点 + +**为什么"没有任何报错"**: 因为代码流程中每个 `if` 条件都静默返回了 `null/undefined`,没有走到会抛异常的逻辑路径。 + +#### 解决方案 + +**修改文件**: `frontend/src/views/flow/FlowEditor.vue` + +**修改点1** — 模板: 将 `@mousedown` 改为 `@dragstart`,添加 `draggable="true"` + +第21-29行,改为: +```html +
+``` + +**修改点2** — 脚本: 修复 `onDragStart` 函数 + +第253-259行,改为: +```typescript +function onDragStart(event: DragEvent, node: (typeof nodeTypes)[0]) { + if (event.dataTransfer) { + event.dataTransfer.setData('application/vueflow', JSON.stringify(node)) + event.dataTransfer.effectAllowed = 'move' + } +} +``` + +**修改点3** — 脚本: 增强 `onDrop` 函数的健壮性 + +第261-291行,改为: +```typescript +function onDrop(event: DragEvent) { + const dataStr = event.dataTransfer?.getData('application/vueflow') + if (!dataStr) return + + const nodeData = JSON.parse(dataStr) + + // 使用 Vue Flow 内置方法计算画布坐标,回退到手动估算 + let position = { x: 100, y: 100 } + try { + const bounds = (event.currentTarget as HTMLElement)?.getBoundingClientRect() + if (bounds) { + position = { + x: event.clientX - bounds.left - 80, + y: event.clientY - bounds.top - 20, + } + } + } catch { /* fallback to default position */ } + + const id = `node_${nodeCounter++}` + const newNode: any = { + id, + type: 'custom', + position, + data: { + label: nodeData.label, + type: nodeData.type, + typeDesc: nodeData.typeDesc, + color: colorMap[nodeData.type] || '#409EFF', + config: nodeData.type === 'llm' + ? { system_prompt: '', model: 'gpt-4o-mini', temperature: 0.7 } + : {}, + }, + draggable: true, + connectable: true, + } + + elements.value = [...elements.value, newNode] +} +``` + +--- + +### 问题3: 流详情 `/api/flow/definitions/:id` 直接在菜单中出现,未传ID导致422错误 + +#### 根因分析 + +**问题源**: `frontend/src/components/layout/AdminLayout.vue` 第46行: + +```html +流详情 +``` + +这段代码存在两个严重问题: + +1. **`:id` 是路由参数占位符,不能直接作为菜单导航路径**: + - 点击菜单 → `router.push('/admin/flow/market/:id')` + - 前端路由匹配路径 `/admin/flow/market/:id` → `route.params.id` = `':id'` (字面量) + - 组件 `FlowDetail.vue` 调用 API `flowApi.getFlow(':id')` + - 发送请求 `GET /api/flow/definitions/%3Aid` → 422 Unprocessable Entity + +2. **业务逻辑错误**: 流详情是**列表项的操作入口**(点击某条流记录 → 查看详情),而不是一个独立的导航菜单项。用户必须先选择一个具体的流,才能查看其详情。 + +**对比已有的正确设计**: +- `TaskDetail` 路由: `/user/task/:id` — **不在菜单中**,只能通过任务列表点击跳转 +- `FlowEditor` 编辑路由: `/admin/flow/editor/:id` — **不在菜单中**,通过流列表点击跳转 +- `FlowDetail` 流详情路由: `/admin/flow/market/:id` — **错误地放在菜单中** + +#### 解决方案 + +**修改文件1**: `frontend/src/components/layout/AdminLayout.vue` + +删除第46行: +```diff +- 流详情 +``` + +同时检查 `FlowList.vue` 和 `FlowMarket.vue` 是否已有正确的详情跳转入口(Card点击 → `router.push('/admin/flow/market/:id')`),如果没有则需要补充。 + +**修改文件2**: `frontend/src/views/flow/FlowList.vue` — 确保每条流记录点击能跳转到详情 + +检查并确保 FlowList 每行有跳转逻辑: +```typescript +function viewFlow(id: string) { + router.push(`/admin/flow/market/${id}`) +} +``` + +--- + +### 问题4: 管理后台和企业AI后台 —— 做成固定在右上角账号隔壁的选择项 + +#### 根因分析 + +**当前实现位置**: +- `MainLayout.vue` 第72-75行: "管理后台" 入口放在 **左侧边栏底部**(仅管理员可见) +- `AdminLayout.vue` 第86-89行: "返回用户端" 入口放在 **左侧边栏底部** + +**存在的问题**: +1. 入口位置隐蔽,用户不易发现 +2. "管理后台"入口被混在功能菜单中,不够突出 +3. 两个入口位于不同布局,缺少统一的切换组件 +4. 没有明确的视觉状态指示当前所在端 + +**需求**: 做成固定在右上角、账号头像旁边的**明确可切换的选择项** + +#### 解决方案 + +**方案**: 在两个 Layout 的 `header-right` 区域统一添加"端切换"组件 + +##### 4.1 创建公共切换组件 + +文件: `frontend/src/components/common/PortalSwitcher.vue` + +```vue + + + + + +``` + +##### 4.2 修改 MainLayout.vue + +第88-102行区域的 `header-right`,在用户下拉菜单前插入 PortalSwitcher: + +```diff +
++ + + ... + +
+``` + +同时**移除**第72-75行的侧边栏管理后台入口: +```diff +- +- +- 管理后台 +- +``` + +##### 4.3 修改 AdminLayout.vue + +同样在 header-right 区域插入 PortalSwitcher: + +```diff +
++ + + ... + +
+``` + +同时**移除**第86-89行的侧边栏返回用户端入口: +```diff +- +- +- 返回用户端 +- +``` + +--- + +## 三、P3 级体验优化(下期规划) + +> 来源: PLAN2.md "六、剩余可优化项" + +### 3.1 全局搜索 + +| 项目 | 详情 | +|------|------| +| **入口位置** | 顶部导航栏中央,全局搜索框(Cmd/Ctrl + K 快捷键唤起) | +| **搜索范围** | 任务名称/ID、流名称/ID、员工姓名、部门名称、智能体类型 | +| **交互设计** | 输入关键词 → 实时下拉联想 → 选中 → 跳转到对应详情页 | +| **技术方案** | 前端: `el-autocomplete` + 防抖;后端: 新增 `GET /api/search?q=keyword` 聚合搜索接口 | +| **前端文件** | 新增 `components/common/GlobalSearch.vue` | +| **后端文件** | 新增 `modules/search/router.py` | + +### 3.2 主题切换(深色/浅色模式) + +| 项目 | 详情 | +|------|------| +| **入口位置** | 右上角 PortalSwitcher 旁边,添加主题切换按钮(太阳/月亮图标) | +| **切换内容** | Element Plus CSS 变量覆盖 + 自定义页面背景色 | +| **持久化** | localStorage 存 `theme` 字段,刷新后保持 | +| **技术方案** | 使用 `el-config-provider` 的 `namespace` + CSS 变量覆盖。Element Plus 2.x 原生支持暗黑模式(添加 `dark` class 到 `html` 元素) | +| **前端文件** | 新增 `stores/theme.ts` + 修改 `main.ts` | +| **CSS文件** | 新增 `styles/dark.css` | + +### 3.3 国际化(i18n 多语言) + +| 项目 | 详情 | +|------|------| +| **支持语言** | 简体中文(zh-CN)、英文(en-US) | +| **实现方案** | vue-i18n ^9.x,语言包按模块拆分 | +| **入口位置** | 右上角 PortalSwitcher 旁边,语言切换下拉或图标 | +| **持久化** | localStorage 存 `locale` 字段 | +| **翻译范围** | 菜单、按钮、表单标签、提示信息、表格列头、错误信息 | +| **前端文件** | 新增 `locales/zh-CN.ts`、`locales/en-US.ts`、`locales/index.ts` | + +### 3.4 新手引导(首次登录操作指引) + +| 项目 | 详情 | +|------|------| +| **触发条件** | 用户表中 `onboarding_completed` 字段为 false | +| **引导步骤** | ① 欢迎 → ② 查看工作台 → ③ 创建任务 → ④ 查看智能体 → ⑤ 进入管理后台(仅管理员) | +| **实现方案** | driver.js 或 intro.js 库,高亮目标元素 + 文字提示泡泡 | +| **跳过机制** | 引导面板有"跳过"和"不再显示"按钮 | +| **前端文件** | 新增 `components/common/OnboardingGuide.vue` | +| **后端文件** | `PUT /api/auth/me` 增加 `onboarding_completed` 字段支持 | + +### 3.5 移动端适配(响应式布局) + +| 项目 | 详情 | +|------|------| +| **适配范围** | 工作台、任务列表、智能体对话、通知中心(核心用户端功能) | +| **布局策略** | 768px 以下: 侧边栏隐藏(汉堡菜单),内容区全宽,表格横向滚动 | +| **组件调整** | el-card → 边距缩小,el-table → 固定列+横向滚动,el-form → 标签在上 | +| **CSS方案** | 全局 `@media (max-width: 768px)` + Element Plus 响应式断点 | +| **管理后台** | 仅做基本适配(可左右滚动),不做深度移动端优化 | +| **前端文件** | 新增 `styles/mobile.css` + 各页面添加响应式 scoped style | + +--- + +## 四、实施优先级与排期 + +### 4.1 本期(P0 紧急修复) + +| # | 任务 | 文件 | 工作量 | +|---|------|------|--------| +| 1 | 修复企微配置500错误(缺少settings导入) | `backend/modules/wecom/router.py` | 5分钟 | +| 2 | 修复节点拖拽(mousedown→dragstart + draggable) | `frontend/src/views/flow/FlowEditor.vue` | 15分钟 | +| 3 | 移除菜单中的"流详情"(`:id`占位符) | `frontend/src/components/layout/AdminLayout.vue` | 5分钟 | +| 4 | PortalSwitcher 创建并集成到两个Layout | `frontend/src/components/common/PortalSwitcher.vue` + `MainLayout.vue` + `AdminLayout.vue` | 30分钟 | +| 5 | 过滤 Chrome 扩展 runtime.lastError | `frontend/src/main.ts` 或 `index.html` | 5分钟 | + +### 4.2 下期(P3 体验优化) + +| # | 任务 | 工作量 | 建议排期 | +|---|------|--------|---------| +| 6 | 全局搜索 | 2天 | Week 1 | +| 7 | 主题切换(深色/浅色) | 1天 | Week 1 | +| 8 | 国际化 i18n | 2天 | Week 2 | +| 9 | 新手引导 | 1天 | Week 2 | +| 10 | 移动端适配 | 2天 | Week 2-3 | + +--- + +## 五、验证检查清单 + +实施完成后,使用 **sroot** 账号验证: + +``` +□ 企微配置页面正常加载(不再500报错) +□ 节点面板拖拽到画布: 拖拽正常 → 节点出现在画布上 +□ 菜单中不再出现"流详情"菜单项 +□ 从流列表/流市场点击具体流 → 正确跳转流详情 +□ 右上角显示 PortalSwitcher 切换按钮 +□ 在企业AI端 → 点击"管理后台" → 切换到管理后台 +□ 在管理后台 → 点击"企业AI" → 切换回企业AI端 +□ 流编辑器页面加载无 Chrome runtime.lastError 警告 +``` \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile index 55ee90e..8748f0f 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -12,4 +12,4 @@ RUN pip install --no-cache-dir -r requirements.txt COPY . . -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] \ No newline at end of file diff --git a/backend/agentscope_integration/tools/wecom_tools.py b/backend/agentscope_integration/tools/wecom_tools.py index 6cdd22c..17838eb 100644 --- a/backend/agentscope_integration/tools/wecom_tools.py +++ b/backend/agentscope_integration/tools/wecom_tools.py @@ -1,6 +1,5 @@ import httpx import logging -import uuid logger = logging.getLogger(__name__) diff --git a/backend/modules/audit/router.py b/backend/modules/audit/router.py index b03ec1a..f862340 100644 --- a/backend/modules/audit/router.py +++ b/backend/modules/audit/router.py @@ -54,7 +54,7 @@ async def list_logs( logs = result.scalars().all() return AuditLogPage( - items=[AuditLogOut.model_validate(log) for log in logs], + items=[AuditLogOut.model_validate(log, from_attributes=True) for log in logs], total=total, page=page, page_size=page_size, diff --git a/backend/modules/auth/router.py b/backend/modules/auth/router.py index 8788592..7b60497 100644 --- a/backend/modules/auth/router.py +++ b/backend/modules/auth/router.py @@ -11,6 +11,11 @@ from models import User, UserRole, Role, RolePermission, Permission from schemas import LoginRequest, TokenResponse, UserOut, RoleOut from config import settings + +def hash_password(password: str) -> str: + return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') + + router = APIRouter(prefix="/api/auth", tags=["auth"]) async def get_permission_codes(db: AsyncSession, role_ids: list[uuid.UUID]) -> list[str]: @@ -95,11 +100,12 @@ async def get_me(request: Request, db: AsyncSession = Depends(get_db)): @router.get("/wecom/oauth-url") -async def get_wecom_oauth_url(): +async def get_wecom_oauth_url(request: Request): corp_id = settings.WECOM_CORP_ID or "" if not corp_id: return {"code": 400, "message": "请先配置 WECOM_CORP_ID"} - redirect_uri = f"{settings.WECOM_CORP_ID}/api/auth/wecom/callback" + base_url = str(request.base_url).rstrip("/") + redirect_uri = f"{base_url}/api/auth/wecom/callback" url = f"https://open.weixin.qq.com/connect/oauth2/authorize?appid={corp_id}&redirect_uri={redirect_uri}&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect" return {"code": 200, "data": {"url": url}} @@ -155,8 +161,4 @@ async def change_password( user.password_hash = hash_password(new_pw) await db.commit() - return {"code": 200, "message": "密码已修改"} - - -def hash_password(password: str) -> str: - return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') \ No newline at end of file + return {"code": 200, "message": "密码已修改"} \ No newline at end of file diff --git a/backend/modules/flow_engine/engine.py b/backend/modules/flow_engine/engine.py index f0f9073..5c02299 100644 --- a/backend/modules/flow_engine/engine.py +++ b/backend/modules/flow_engine/engine.py @@ -284,10 +284,57 @@ class ConditionNodeAgent(AgentBase): user_text = msg.get_text_content() if hasattr(msg, 'get_text_content') else str(msg) if not self.condition: - return msg if isinstance(msg, Msg) else Msg(self.name, str(msg), "assistant") + return Msg(self.name, "condition:true|条件为空,默认通过", "assistant") - result_text = f"[条件判断: {self.condition[:80]}]\n输入: {user_text[:300]}\n结果: 条件满足,继续执行。" - return Msg(self.name, result_text, "assistant") + try: + from agentscope.model import OpenAIChatModel + from agentscope.formatter import OpenAIChatFormatter + + model = OpenAIChatModel( + config_name=f"condition_{self.name}", + model_name=settings.LLM_MODEL, + api_key=settings.LLM_API_KEY, + api_base=settings.LLM_API_BASE, + ) + + formatter = OpenAIChatFormatter() + condition_prompt = f"""你是一个条件判断专家。请判断以下条件表达式是否基于输入内容满足。 + +条件表达式: {self.condition} + +输入内容: +{user_text[:2000]} + +请严格只输出一行 JSON: +{{"result": true/false, "reason": "简要原因"}}""" + + prompt = await formatter.format([ + Msg("system", condition_prompt, "system"), + Msg("user", user_text[:2000], "user"), + ]) + res = await model(prompt) + + import json + import re + res_text = "" + if isinstance(res, list): + res_text = res[0].get_text_content() if hasattr(res[0], 'get_text_content') else str(res[0]) + elif hasattr(res, 'get_text_content'): + res_text = res.get_text_content() + else: + res_text = str(res) + + json_match = re.search(r'\{[^}]+\}', res_text) + if json_match: + parsed = json.loads(json_match.group()) + matched = parsed.get("result", False) + reason = parsed.get("reason", "") + result_flag = "true" if matched else "false" + return Msg(self.name, f"condition:{result_flag}|{reason}", "assistant") + except Exception as e: + logger.warning(f"条件判断LLM调用失败: {e}") + + return Msg(self.name, f"condition:true|条件判断失败,默认通过: {self.condition[:80]}", "assistant") async def observe(self, msg) -> None: pass diff --git a/backend/modules/task/router.py b/backend/modules/task/router.py index b83b316..0969cc4 100644 --- a/backend/modules/task/router.py +++ b/backend/modules/task/router.py @@ -3,7 +3,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from database import get_db -from models import Task +from models import Task, User from schemas import TaskCreate, TaskUpdate, TaskOut from modules.org.router import _get_subordinate_ids diff --git a/backend/modules/wecom/router.py b/backend/modules/wecom/router.py index a2c7d5d..ddd5c0e 100644 --- a/backend/modules/wecom/router.py +++ b/backend/modules/wecom/router.py @@ -1,9 +1,9 @@ import uuid -import httpx from fastapi import APIRouter, Depends, HTTPException, Request from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from database import get_db +from config import settings from models import User, ChatSession, ChatMessage router = APIRouter(prefix="/api/wecom", tags=["wecom"]) @@ -93,66 +93,6 @@ async def wecom_callback(request: Request, db: AsyncSession = Depends(get_db)): } -@router.post("/send") -async def send_wecom_message( - request: Request, - payload: dict, - db: AsyncSession = Depends(get_db), -): - """ - 向企业微信用户推送消息。 - 生产环境中需配置真实的企微API凭据。 - """ - to_user = payload.get("to_user", "") - msg_content = payload.get("content", "") - msg_type = payload.get("msg_type", "text") - - if not to_user: - raise HTTPException(400, "缺少目标用户") - - corp_id = "" - corp_secret = "" - wecom_message_id = f"msg_{uuid.uuid4().hex[:12]}" - - if corp_id and corp_secret: - try: - async with httpx.AsyncClient() as client: - token_resp = await client.get( - "https://qyapi.weixin.qq.com/cgi-bin/gettoken", - params={"corpid": corp_id, "corpsecret": corp_secret}, - ) - token_data = token_resp.json() - access_token = token_data.get("access_token") - - if access_token: - msg_body = { - "touser": to_user, - "msgtype": msg_type, - "agentid": 0, - } - if msg_type == "text": - msg_body["text"] = {"content": msg_content} - elif msg_type == "textcard": - msg_body["textcard"] = payload.get("card", {}) - - msg_resp = await client.post( - f"https://qyapi.weixin.qq.com/cgi-bin/message/send", - params={"access_token": access_token}, - json=msg_body, - ) - resp_data = msg_resp.json() - if resp_data.get("errcode") == 0: - wecom_message_id = resp_data.get("msgid", wecom_message_id) - except Exception: - pass - - return { - "code": 200, - "message": "消息已发送", - "data": {"wecom_message_id": wecom_message_id}, - } - - @router.get("/config") async def get_wecom_config(request: Request): return { diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 8dd82b2..1853911 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -6,6 +6,9 @@ server { location / { try_files $uri $uri/ /index.html; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires 0; } location /api/ { @@ -25,4 +28,9 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 0; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + } } \ No newline at end of file diff --git a/frontend/src/components/common/PortalSwitcher.vue b/frontend/src/components/common/PortalSwitcher.vue new file mode 100644 index 0000000..db5149b --- /dev/null +++ b/frontend/src/components/common/PortalSwitcher.vue @@ -0,0 +1,56 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/layout/AdminLayout.vue b/frontend/src/components/layout/AdminLayout.vue index ebf0969..43f6171 100644 --- a/frontend/src/components/layout/AdminLayout.vue +++ b/frontend/src/components/layout/AdminLayout.vue @@ -43,7 +43,6 @@ 流列表 流编辑器 流市场 - 流详情 @@ -82,11 +81,6 @@ 系统监控 - - - - 返回用户端 - @@ -100,6 +94,7 @@
+
+