From 54481525f4241549eb5a9205ce3aeb9a75f7ae43 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 18 Jul 2026 00:05:59 +0800 Subject: [PATCH] =?UTF-8?q?P0-3/P0-4/P0-5:=20=E5=BF=AB=E6=8D=B7=E7=94=B3?= =?UTF-8?q?=E8=AF=B7=E5=85=A8=E9=87=8F=E5=8D=A1=E7=89=87=E7=AB=AF=E7=82=B9?= =?UTF-8?q?+RecommendCard=E5=B4=A9=E6=BA=83=E4=BF=AE=E5=A4=8D+WS=E5=85=A8?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E9=80=8F=E4=BC=A0=EF=BC=88=E9=83=A8=E7=BD=B2?= =?UTF-8?q?v16=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/approval.py | 24 +++++++++++ frontend-h5/src/api/conversation.ts | 35 +++++++++++++++ .../components/assistant/RecommendCard.vue | 11 +++-- frontend-h5/src/stores/conversation.ts | 43 ++++++++++++++++--- 4 files changed, 103 insertions(+), 10 deletions(-) diff --git a/backend/app/api/approval.py b/backend/app/api/approval.py index ae43865..cde1853 100644 --- a/backend/app/api/approval.py +++ b/backend/app/api/approval.py @@ -760,6 +760,30 @@ async def get_approval_templates(): return list(APPROVAL_TEMPLATES.values()) +@router.get("/approval/all-categories-card") +async def get_all_categories_card(): + """v4.0 P0-3: 获取全量审批卡片数据(快捷申请按钮用) + + 返回标准化 card_data,前端纯渲染(无需前端再做匹配)。 + 与 ApprovalCardModal 的 CardData 接口结构一致。 + """ + from app.services.approval_matcher import get_approval_matcher + matcher = get_approval_matcher() + categories = matcher.get_all_categories() + + # 扁平化所有分类的 options + all_options = [] + for cat in categories: + all_options.extend(cat.get("options", [])) + + return { + "card_type": "multiple", + "title": "审批申请", + "description": "请选择审批类型", + "options": all_options, + } + + @router.get("/approval/templates/{template_id}", response_model=ApprovalTemplateResponse) async def get_approval_template(template_id: str): """获取指定审批模板详情""" diff --git a/frontend-h5/src/api/conversation.ts b/frontend-h5/src/api/conversation.ts index ad9c83d..1672b06 100644 --- a/frontend-h5/src/api/conversation.ts +++ b/frontend-h5/src/api/conversation.ts @@ -403,6 +403,41 @@ export async function getApprovalLinks(): Promise { return (data?.items || data || []) as ApprovalLink[] } +// ============================================================================= +// v4.0 P0-3: 全量审批卡片 API(快捷申请按钮用) +// ============================================================================= + +/** 审批卡片选项 */ +export interface ApprovalCardOption { + name: string + icon: string + desc: string + url: string +} + +/** 标准化审批卡片数据(后端 ApprovalMatcher 直出,前端纯渲染) */ +export interface ApprovalCardData { + card_type: 'single' | 'multiple' + title: string + description?: string + options: ApprovalCardOption[] +} + +/** + * 获取全量审批卡片(快捷申请按钮用) + * 注意:approval 路由挂在根路径(非 /api 前缀),需用绝对路径 + * @returns 标准化 card_data + */ +export async function getAllCategoriesCard(): Promise { + // approval 路由在 /approval(根路径),不走 apiClient 的 /api baseURL + const token = localStorage.getItem('employee_token') || '' + const resp = await fetch('/approval/all-categories-card', { + headers: { Authorization: `Bearer ${token}` }, + }) + if (!resp.ok) throw new Error(`HTTP ${resp.status}`) + return resp.json() +} + // ============================================================================= // 审批意图检测 API(Dify 意图置信度 — 替代纯关键词匹配) // ============================================================================= diff --git a/frontend-h5/src/components/assistant/RecommendCard.vue b/frontend-h5/src/components/assistant/RecommendCard.vue index 395f859..d417a56 100644 --- a/frontend-h5/src/components/assistant/RecommendCard.vue +++ b/frontend-h5/src/components/assistant/RecommendCard.vue @@ -144,7 +144,8 @@ const sourceLabel = computed(() => { // 判断操作项是否可点击 function isItemClickable(item: RecommendItem): boolean { - return !!(item.url || item.action || item.type === 'info' || item.copyable) + // v3.2 修复:删除接口中不存在的 item.action 死引用 + return !!(item.url || item.type === 'info' || item.copyable) } // 获取操作项图标 @@ -174,8 +175,12 @@ function handleItemClick(item: RecommendItem) { break case 'approval': - // 打开审批表单 - invokeApproval(item.approval_type || '') + // v3.2 修复:invokeApproval 未定义(ReferenceError),改为与 download 一致的 url 打开 + if (item.url) { + window.open(item.url, '_blank') + } else { + showToast('链接缺失') + } break case 'info': diff --git a/frontend-h5/src/stores/conversation.ts b/frontend-h5/src/stores/conversation.ts index cec6030..eebf8e9 100644 --- a/frontend-h5/src/stores/conversation.ts +++ b/frontend-h5/src/stores/conversation.ts @@ -400,6 +400,13 @@ export const useConversationStore = defineStore('conversation', () => { sender_name?: string content: string msg_type?: string + // v3.2 P0-5: 全字段透传(后端 messages.py:278 已下发,修复坐席图片/文件不渲染) + media_url?: string + file_name?: string + file_size?: number + extra_data?: Record + reply_to_id?: string + created_at?: string }): void { // WS-06 消息去重:检查 message_id 是否已处理过 if (processedMessageIds.value.has(data.message_id)) { @@ -421,7 +428,13 @@ export const useConversationStore = defineStore('conversation', () => { // ★ 防御性类型保护:确保 content 始终是 String content: typeof data.content === 'string' ? data.content : (data.content ? JSON.stringify(data.content) : ''), sender_name: data.sender_name || '', - created_at: new Date().toISOString(), + // v3.2 P0-5: 全字段透传(created_at 用服务端值,不用 new Date()) + media_url: data.media_url, + file_name: data.file_name, + file_size: data.file_size, + extra_data: data.extra_data, + reply_to_id: data.reply_to_id, + created_at: data.created_at || new Date().toISOString(), }) // 更新最后消息ID @@ -891,14 +904,29 @@ export const useConversationStore = defineStore('conversation', () => { approvalCardTriggerText.value = '' } + // v4.0 P0-3: 缓存全量审批卡片(首次点击拉取,后续复用) + let _allCategoriesCardCache: any = null + /** * 显示审批卡片(快捷按钮触发) - * 新流程中审批卡片作为消息流内联卡片,此函数直接在消息列表中插入一条审批卡片消息。 + * v4.0 P0-3: 改为 async 拉取后端 /approval/all-categories-card, + * 修复旧版构造 {approval_type:'',confidence:0} 导致的空白气泡(F1) * * @param _triggerText 触发文本(可选,新流程中不再使用关键词匹配) */ - function showApprovalCard(_triggerText: string = ''): void { - // 插入一条审批卡片消息到消息列表(approval_type 为空时组件展示全部选项) + async function showApprovalCard(_triggerText: string = ''): Promise { + // 首次拉取并缓存 + if (!_allCategoriesCardCache) { + try { + const { getAllCategoriesCard } = await import('@/api/conversation') + _allCategoriesCardCache = await getAllCategoriesCard() + } catch (e) { + console.error('[Store] 拉取全量审批卡片失败:', e) + return + } + } + + // 插入审批卡片消息(action.card_data 为后端标准化数据,前端纯渲染) const approvalCardMessage: Message = { message_id: `approval_card_${Date.now()}`, conversation_id: currentConversation.value?.conversation_id || '', @@ -908,12 +936,13 @@ export const useConversationStore = defineStore('conversation', () => { sender_name: '', created_at: new Date().toISOString(), extra_data: { - approval_type: '', - confidence: 0, + action: { + card_data: _allCategoriesCardCache, + }, }, } messages.value.push(approvalCardMessage) - console.log('[Store] 手动插入审批卡片消息(快捷按钮触发)') + console.log('[Store] 手动插入审批卡片消息(快捷按钮触发,v4.0 全量卡片)') } /**