Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
00694c49
提交
00694c49
authored
1月 23, 2026
作者:
朱亚刚
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
完成pdf写报接口接入
上级
3c267bc1
显示空白字符变更
内嵌
并排
正在显示
1 个修改的文件
包含
75 行增加
和
196 行删除
+75
-196
index.vue
src/views/writtingAsstaint/index.vue
+75
-196
没有找到文件。
src/views/writtingAsstaint/index.vue
浏览文件 @
00694c49
...
...
@@ -294,17 +294,84 @@ const getStreamChat = async (search, inputValue) => {
ElMessage
.
error
(
'文件解析失败,请重新选择'
);
return
;
}
callSse
Api
(
rawFile
)
callSse
WithPdf
(
rawFile
)
}
else
{
abortController
.
value
=
new
AbortController
();
const
params
=
{
query
:
writtingTitle
.
value
,
// "输出一篇报文"
desc
:
descText
.
value
,
topic
:
curTempTitle
.
value
// 政令、智库、法案、清单
};
callSseWithAi
(
params
)
}
};
const
callSseWithPdf
=
async
(
selectedFile
)
=>
{
abortController
.
value
=
new
AbortController
();
try
{
// 构造FormData(和后端字段名保持一致)
const
formData
=
new
FormData
();
formData
.
append
(
'pdf'
,
selectedFile
);
// 调用fetchEventSource(核心:支持POST+FormData+SSE)
await
fetchEventSource
(
'/pdfSse/api/v1/order/pdf/extract/report/sse'
,
{
method
:
'POST'
,
// 关键:设置POST方法
body
:
formData
,
// 关键:传递PDF文件的FormData
signal
:
abortController
.
value
.
signal
,
// 中断信号
headers
:
{
// 禁用默认的SSE协议头(避免和文件上传冲突)
'Accept'
:
'text/event-stream'
,
'Cache-Control'
:
'no-cache'
,
'Connection'
:
'keep-alive'
},
// 禁用自动重连(可选,根据后端配置)
retry
:
0
,
// 核心:原生onmessage回调(无需手动分割/解析)
async
onopen
(
res
)
{
console
.
log
(
"流式回答开始"
,
res
);
isGenerating
.
value
=
true
;
isShowProcess
.
value
=
true
;
},
async
onmessage
(
res
)
{
const
{
data
,
event
}
=
res
const
jsonData
=
JSON
.
parse
(
data
)
switch
(
event
)
{
case
"progress"
:
processContent
.
value
+=
`
${
getFormattedTime
()}
:
${
jsonData
.
message
}
\r\n`
;
updateProcess
(
processContent
.
value
,
scrollProcessContainer
.
value
);
break
;
case
"result"
:
callSseWithAi
({
query
:
writtingTitle
.
value
,
// "输出一篇报文"
desc
:
descText
.
value
,
topic
:
"政令"
,
result
:
data
// 政令、智库、法案、清单
})
default
:
break
;
}
},
onerror
(
error
)
{
ElMessage
({
message
:
"写报生成报错!"
,
type
:
"warning"
});
abortController
.
value
.
abort
();
abortController
.
value
=
new
AbortController
();
throw
new
Error
(
error
);
}
});
}
catch
(
error
)
{
if
(
error
.
name
!==
'AbortError'
)
{
ElMessage
.
error
(
`请求失败:
${
error
.
message
}
`
);
isLoading
.
value
=
false
;
}
}
};
const
callSseWithAi
=
async
(
params
)
=>
{
abortController
.
value
=
new
AbortController
();
fetchEventSource
(
"/sseWrite/api/v1/workflow/invoke"
,
{
method
:
"POST"
,
headers
:
{
...
...
@@ -367,204 +434,16 @@ const getStreamChat = async (search, inputValue) => {
abortController
.
value
=
new
AbortController
();
throw
new
Error
(
error
);
});
}
};
const
callSseApi
=
async
(
selectedFile
)
=>
{
abortController
.
value
=
new
AbortController
();
try
{
const
formData
=
new
FormData
();
formData
.
append
(
'pdf'
,
selectedFile
);
const
response
=
await
fetch
(
'/pdfSse/api/v1/order/pdf/extract/report/sse'
,
{
method
:
'POST'
,
body
:
formData
,
signal
:
abortController
.
value
.
signal
,
// 绑定中断信号
});
if
(
!
response
.
ok
)
{
throw
new
Error
(
`请求失败:
${
response
.
status
}
${
response
.
statusText
}
`
);
}
console
.
log
(
"流式回答开始"
,
res
);
isGenerating
.
value
=
true
;
isShowProcess
.
value
=
true
;
// 读取流式响应
const
reader
=
response
.
body
.
getReader
();
const
decoder
=
new
TextDecoder
();
let
buffer
=
''
;
// 消息缓冲区
// 定义SSE消息回调(和原生onmessage用法一致)
const
onmessage
=
(
res
)
=>
{
const
{
type
,
data
}
=
res
if
(
type
===
"progress"
)
{
processContent
.
value
+=
data
.
message
;
updateProcess
(
processContent
.
value
,
scrollProcessContainer
.
value
);
}
};
// 错误回调
const
onerror
=
(
error
)
=>
{
};
// 连接关闭回调
const
onclose
=
()
=>
{
};
// 循环读取并解析SSE消息(核心修复换行符兼容)
while
(
true
)
{
try
{
const
{
done
,
value
}
=
await
reader
.
read
();
// 连接正常关闭
if
(
done
)
{
onclose
();
break
;
}
// 1. 解码二进制流为文本,追加到缓冲区
buffer
+=
decoder
.
decode
(
value
,
{
stream
:
true
});
// 2. 分割完整消息(兼容 \r\n\r\n 和 \n\n 换行符)
const
fullMessages
=
buffer
.
split
(
/
(\r\n
|
\n){2}
/
);
// 过滤空字符串,只保留有效消息
const
validMessages
=
fullMessages
.
filter
(
msg
=>
msg
.
trim
()
!==
''
);
// 3. 最后一条可能是不完整消息,放回缓冲区
let
remainingBuffer
=
''
;
if
(
validMessages
.
length
>
0
)
{
remainingBuffer
=
validMessages
.
pop
()
||
''
;
}
// 4. 逐条解析有效消息
validMessages
.
forEach
(
fullMsg
=>
{
const
sseMsg
=
parseSSEMessage
(
fullMsg
);
// 过滤心跳/注释消息(以:开头的消息)
if
(
sseMsg
.
isComment
)
return
;
// 触发onmessage回调
if
(
sseMsg
.
data
)
{
debugger
onmessage
({
type
:
sseMsg
.
event
||
'message'
,
data
:
JSON
.
parse
(
sseMsg
.
data
),
// 解析为JSON对象
timeStamp
:
Date
.
now
()
});
}
});
// 重置缓冲区(只保留不完整的消息)
buffer
=
remainingBuffer
;
}
catch
(
error
)
{
// 排除主动中断的情况
if
(
error
.
name
!==
'AbortError'
)
{
onerror
(
error
);
}
break
;
}
}
}
catch
(
error
)
{
// 捕获请求级别的错误
if
(
error
.
name
!==
'AbortError'
)
{
ElMessage
.
error
(
`请求异常:
${
error
.
message
}
`
);
isLoading
.
value
=
false
;
}
}
};
// 工具函数:严格按SSE协议解析单条消息
const
parseSSEMessage
=
(
rawMsg
)
=>
{
// 兼容 \r\n 和 \n 分割行
const
lines
=
rawMsg
.
split
(
/
\r\n
|
\n
/
);
const
result
=
{
event
:
'progress'
,
// 默认事件类型
data
:
''
,
// 消息数据
isComment
:
false
// 是否是注释/心跳消息
};
lines
.
forEach
(
line
=>
{
line
=
line
.
trimEnd
();
// 去掉行尾空白符
// 注释行(以:开头):心跳消息,标记为注释
if
(
line
.
startsWith
(
':'
))
{
result
.
isComment
=
true
;
return
;
}
// 解析event行(event: xxx)
if
(
line
.
startsWith
(
'event:'
))
{
result
.
event
=
line
.
slice
(
6
).
trim
();
return
;
}
// 解析data行(data: xxx),支持多行data拼接
if
(
line
.
startsWith
(
'data:'
))
{
result
.
data
+=
line
.
slice
(
5
).
trim
();
return
;
}
}
// 空行忽略
if
(
line
===
''
)
return
;
});
const
getFormattedTime
=
()
=>
{
const
now
=
new
Date
();
// 补零函数:确保单个数字补为两位(如 1 → 01,9 → 09)
const
pad
=
n
=>
n
.
toString
().
padStart
(
2
,
'0'
);
return
result
;
return
`
${
now
.
getFullYear
()}
-
${
pad
(
now
.
getMonth
()
+
1
)}
-
${
pad
(
now
.
getDate
())}
${
pad
(
now
.
getHours
())}
:
${
pad
(
now
.
getMinutes
())}
:
${
pad
(
now
.
getSeconds
())}
`
;
};
const
ele
=
()
=>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
// 转换FormData为Blob(适配SSE的POST请求体)
const
formDataBlob
=
new
Blob
([
// 模拟multipart/form-data的边界符(简化版,实际可复用浏览器自动生成的)
...
Array
.
from
(
formData
.
entries
()).
map
(([
key
,
value
])
=>
{
return
`--boundary\r\nContent-Disposition: form-data; name="
${
key
}
"
${
value
instanceof
File
?
`; filename="
${
value
.
name
}
"\r\nContent-Type:
${
value
.
type
}
`
:
''
}
\r\n\r\n
${
value
instanceof
File
?
value
:
value
}
\r\n`
;
}),
'--boundary--
\
r
\
n'
]);
fetchEventSource
(
'/pdfSse/api/v1/order/pdf/extract/report/sse'
,
{
method
:
'POST'
,
headers
:
{
// 关键:设置multipart/form-data头,包含边界符
'Content-Type'
:
`multipart/form-data; boundary=boundary`
,
},
// 注意:fetchEventSource的body仅支持字符串/ArrayBuffer/Blob,这里传FormData转换后的Blob
body
:
formDataBlob
,
signal
:
abortController
.
value
.
signal
,
openWhenHidden
:
true
,
// SSE连接开启
async
onopen
(
res
)
{
console
.
log
(
"流式回答开始"
,
res
);
isGenerating
.
value
=
true
;
isShowProcess
.
value
=
true
;
},
async
onmessage
(
res
)
{
debugger
const
{
event
,
data
}
=
res
let
jsonData
=
JSON
.
parse
(
data
);
if
(
event
===
"progress"
)
{
processContent
.
value
+=
jsonData
.
message
;
updateProcess
(
processContent
.
value
,
scrollProcessContainer
.
value
);
}
},
onerror
(
error
)
{
ElMessage
({
message
:
"写报生成报错!"
,
type
:
"warning"
});
abortController
.
value
.
abort
();
abortController
.
value
=
new
AbortController
();
throw
new
Error
(
error
);
}
}).
catch
((
error
)
=>
{
reject
(
error
);
isProcessing
.
value
=
false
;
});
});
}
const
writtingTitle
=
ref
(
""
);
const
descText
=
ref
(
""
);
const
tabList
=
ref
([
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论