P0-3/P0-4/P0-5: 快捷申请全量卡片端点+RecommendCard崩溃修复+WS全字段透传(部署v16)

This commit is contained in:
Simon
2026-07-18 00:05:59 +08:00
parent 18a639919c
commit 54481525f4
4 changed files with 103 additions and 10 deletions
+24
View File
@@ -760,6 +760,30 @@ async def get_approval_templates():
return list(APPROVAL_TEMPLATES.values()) 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) @router.get("/approval/templates/{template_id}", response_model=ApprovalTemplateResponse)
async def get_approval_template(template_id: str): async def get_approval_template(template_id: str):
"""获取指定审批模板详情""" """获取指定审批模板详情"""
+35
View File
@@ -403,6 +403,41 @@ export async function getApprovalLinks(): Promise<ApprovalLink[]> {
return (data?.items || data || []) as ApprovalLink[] 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<ApprovalCardData> {
// 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 意图置信度 — 替代纯关键词匹配) // 审批意图检测 API(Dify 意图置信度 — 替代纯关键词匹配)
// ============================================================================= // =============================================================================
@@ -144,7 +144,8 @@ const sourceLabel = computed(() => {
// 判断操作项是否可点击 // 判断操作项是否可点击
function isItemClickable(item: RecommendItem): boolean { 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 break
case 'approval': case 'approval':
// 打开审批表单 // v3.2 修复:invokeApproval 未定义(ReferenceError),改为与 download 一致的 url 打开
invokeApproval(item.approval_type || '') if (item.url) {
window.open(item.url, '_blank')
} else {
showToast('链接缺失')
}
break break
case 'info': case 'info':
+36 -7
View File
@@ -400,6 +400,13 @@ export const useConversationStore = defineStore('conversation', () => {
sender_name?: string sender_name?: string
content: string content: string
msg_type?: string msg_type?: string
// v3.2 P0-5: 全字段透传(后端 messages.py:278 已下发,修复坐席图片/文件不渲染)
media_url?: string
file_name?: string
file_size?: number
extra_data?: Record<string, any>
reply_to_id?: string
created_at?: string
}): void { }): void {
// WS-06 消息去重:检查 message_id 是否已处理过 // WS-06 消息去重:检查 message_id 是否已处理过
if (processedMessageIds.value.has(data.message_id)) { if (processedMessageIds.value.has(data.message_id)) {
@@ -421,7 +428,13 @@ export const useConversationStore = defineStore('conversation', () => {
// ★ 防御性类型保护:确保 content 始终是 String // ★ 防御性类型保护:确保 content 始终是 String
content: typeof data.content === 'string' ? data.content : (data.content ? JSON.stringify(data.content) : ''), content: typeof data.content === 'string' ? data.content : (data.content ? JSON.stringify(data.content) : ''),
sender_name: data.sender_name || '', 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 // 更新最后消息ID
@@ -891,14 +904,29 @@ export const useConversationStore = defineStore('conversation', () => {
approvalCardTriggerText.value = '' 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 触发文本(可选,新流程中不再使用关键词匹配) * @param _triggerText 触发文本(可选,新流程中不再使用关键词匹配)
*/ */
function showApprovalCard(_triggerText: string = ''): void { async function showApprovalCard(_triggerText: string = ''): Promise<void> {
// 插入一条审批卡片消息到消息列表(approval_type 为空时组件展示全部选项) // 首次拉取并缓存
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 = { const approvalCardMessage: Message = {
message_id: `approval_card_${Date.now()}`, message_id: `approval_card_${Date.now()}`,
conversation_id: currentConversation.value?.conversation_id || '', conversation_id: currentConversation.value?.conversation_id || '',
@@ -908,12 +936,13 @@ export const useConversationStore = defineStore('conversation', () => {
sender_name: '', sender_name: '',
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
extra_data: { extra_data: {
approval_type: '', action: {
confidence: 0, card_data: _allCategoriesCardCache,
},
}, },
} }
messages.value.push(approvalCardMessage) messages.value.push(approvalCardMessage)
console.log('[Store] 手动插入审批卡片消息(快捷按钮触发)') console.log('[Store] 手动插入审批卡片消息(快捷按钮触发v4.0 全量卡片')
} }
/** /**