P0-3/P0-4/P0-5: 快捷申请全量卡片端点+RecommendCard崩溃修复+WS全字段透传(部署v16)
This commit is contained in:
@@ -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):
|
||||
"""获取指定审批模板详情"""
|
||||
|
||||
@@ -403,6 +403,41 @@ export async function getApprovalLinks(): Promise<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 意图置信度 — 替代纯关键词匹配)
|
||||
// =============================================================================
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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<string, any>
|
||||
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<void> {
|
||||
// 首次拉取并缓存
|
||||
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 全量卡片)')
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user