Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
P
Pangea-Agent
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
2
Merge Requests
2
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Gavin-Group
Pangea-Agent
Commits
fb1f853d
Commit
fb1f853d
authored
Dec 24, 2025
by
高如斌
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
模拟后端返回formJson渲染逻辑实现
parent
8a629996
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
221 additions
and
109 deletions
+221
-109
package-lock.json
frontend/package-lock.json
+2
-1
package.json
frontend/package.json
+1
-0
ChatArea.vue
frontend/src/components/ChatArea.vue
+40
-1
FormRender.vue
frontend/src/components/FormRender.vue
+16
-82
form.ts
frontend/src/stores/form.ts
+128
-0
vite.config.ts
frontend/vite.config.ts
+34
-25
No files found.
frontend/package-lock.json
View file @
fb1f853d
...
@@ -15,6 +15,7 @@
...
@@ -15,6 +15,7 @@
"dompurify"
:
"^3.3.1"
,
"dompurify"
:
"^3.3.1"
,
"element-plus"
:
"^2.4.0"
,
"element-plus"
:
"^2.4.0"
,
"highlight.js"
:
"^11.9.0"
,
"highlight.js"
:
"^11.9.0"
,
"lodash-es"
:
"^4.17.21"
,
"marked"
:
"^17.0.1"
,
"marked"
:
"^17.0.1"
,
"pako"
:
"^2.1.0"
,
"pako"
:
"^2.1.0"
,
"pangea-ui"
:
"^0.14.2-beta.9"
,
"pangea-ui"
:
"^0.14.2-beta.9"
,
...
@@ -4739,7 +4740,7 @@
...
@@ -4739,7 +4740,7 @@
},
},
"node_modules/lodash-es"
:
{
"node_modules/lodash-es"
:
{
"version"
:
"4.17.21"
,
"version"
:
"4.17.21"
,
"resolved"
:
"http
s://registry.npmjs.org
/lodash-es/-/lodash-es-4.17.21.tgz"
,
"resolved"
:
"http
://nexus.hisense.com/repository/npm-public
/lodash-es/-/lodash-es-4.17.21.tgz"
,
"integrity"
:
"sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
,
"integrity"
:
"sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
,
"license"
:
"MIT"
"license"
:
"MIT"
},
},
...
...
frontend/package.json
View file @
fb1f853d
...
@@ -17,6 +17,7 @@
...
@@ -17,6 +17,7 @@
"dompurify"
:
"^3.3.1"
,
"dompurify"
:
"^3.3.1"
,
"element-plus"
:
"^2.4.0"
,
"element-plus"
:
"^2.4.0"
,
"highlight.js"
:
"^11.9.0"
,
"highlight.js"
:
"^11.9.0"
,
"lodash-es"
:
"^4.17.21"
,
"marked"
:
"^17.0.1"
,
"marked"
:
"^17.0.1"
,
"pako"
:
"^2.1.0"
,
"pako"
:
"^2.1.0"
,
"pangea-ui"
:
"^0.14.2-beta.9"
,
"pangea-ui"
:
"^0.14.2-beta.9"
,
...
...
frontend/src/components/ChatArea.vue
View file @
fb1f853d
...
@@ -88,6 +88,7 @@ import { ref, nextTick, onMounted, defineExpose } from "vue";
...
@@ -88,6 +88,7 @@ import { ref, nextTick, onMounted, defineExpose } from "vue";
import
{
ElMessage
,
ElMessageBox
}
from
"element-plus"
;
import
{
ElMessage
,
ElMessageBox
}
from
"element-plus"
;
import
MessageItem
from
"./MessageItem.vue"
;
import
MessageItem
from
"./MessageItem.vue"
;
import
request
from
"@/utils/request"
;
import
request
from
"@/utils/request"
;
import
{
useFormStore
}
from
"@/stores/form"
;
import
{
useRoute
}
from
"vue-router"
;
import
{
useRoute
}
from
"vue-router"
;
interface
Message
{
interface
Message
{
...
@@ -112,9 +113,12 @@ const messages = ref<Message[]>([]);
...
@@ -112,9 +113,12 @@ const messages = ref<Message[]>([]);
const
inputMessage
=
ref
(
""
);
const
inputMessage
=
ref
(
""
);
const
isLoading
=
ref
(
false
);
const
isLoading
=
ref
(
false
);
const
messagesContainer
=
ref
<
HTMLElement
>
();
const
messagesContainer
=
ref
<
HTMLElement
>
();
const
formJson
=
ref
<
any
>
(
null
);
// 获取当前路由
// 获取当前路由
const
route
=
useRoute
();
const
route
=
useRoute
();
// 表单store
const
formStore
=
useFormStore
();
// 全局维护SSE流超时计时器引用,确保能够正确清除
// 全局维护SSE流超时计时器引用,确保能够正确清除
let
streamTimeoutTimer
:
ReturnType
<
typeof
setTimeout
>
|
null
=
null
;
let
streamTimeoutTimer
:
ReturnType
<
typeof
setTimeout
>
|
null
=
null
;
...
@@ -649,6 +653,37 @@ const processSSELine = async (
...
@@ -649,6 +653,37 @@ const processSSELine = async (
break;
break;
}
}
// 模拟后端返回form类型的时间,并将form表单的json存到store
formJson.value = {
coms: [
{
key: 1766473421208,
name: "
输入框
",
code: "
HiInput
",
props: {
title: "
输入框
",
status: "
default
",
placeholder: "
请输入
",
name: "
INPUT_6CP8HIBK
",
},
bindProps: {},
},
{
key: 1766476676439,
name: "
日期
",
code: "
HiDatePicker
",
props: {
title: "
日期
",
type: "
date
",
format: "
YYYY
-
MM
-
DD
",
status: "
default
",
name: "
DATE_PA9TUPQQ
",
},
bindProps: {},
},
],
};
// 重置当前事件类型
// 重置当前事件类型
currentEventRef.value = "";
currentEventRef.value = "";
} catch (err) {
} catch (err) {
...
@@ -740,7 +775,7 @@ const sendMessage = async () => {
...
@@ -740,7 +775,7 @@ const sendMessage = async () => {
const decoder = new TextDecoder();
const decoder = new TextDecoder();
let buffer = "";
let buffer = "";
let isStreamComplete = false; // 标记流是否已完成
let isStreamComplete = false; // 标记流是否已完成
const STREAM_TIMEOUT =
60000; // 6
0秒无流式消息则为超时
const STREAM_TIMEOUT =
120000; // 12
0秒无流式消息则为超时
// 设置超时检查
// 设置超时检查
const resetStreamTimeout = () => {
const resetStreamTimeout = () => {
...
@@ -832,6 +867,10 @@ const sendMessage = async () => {
...
@@ -832,6 +867,10 @@ const sendMessage = async () => {
messages.value[aiMessageIndex].content = accumulatedContentRef.value;
messages.value[aiMessageIndex].content = accumulatedContentRef.value;
}
}
if (formJson.value) {
formStore.openForm(formJson.value);
}
// 确保最终状态正确
// 确保最终状态正确
messages.value[aiMessageIndex].isStreaming = false;
messages.value[aiMessageIndex].isStreaming = false;
// 设置isLoading为false,结束加载状态
// 设置isLoading为false,结束加载状态
...
...
frontend/src/components/FormRender.vue
View file @
fb1f853d
<
template
>
<
template
>
<hi-page-template
<div
v-if=
"formStore.showForm"
>
ref=
"templateRef"
<hi-page-template
:json=
"json"
ref=
"templateRef"
:open-intl=
"false"
:json=
"formStore.formJson"
></hi-page-template>
:open-intl=
"false"
<div
class=
"button-wrap"
>
></hi-page-template>
<a-button
type=
"primary"
@
click=
"submit"
>
提交
</a-button>
<div
class=
"button-wrap"
>
<a-button
type=
"primary"
@
click=
"submit"
>
提交
</a-button>
</div>
</div>
</div>
</
template
>
</
template
>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
ref
}
from
"vue"
;
import
{
ref
}
from
"vue"
;
import
{
useFormStore
}
from
"@/stores/form"
;
import
HiPageTemplate
from
"pangea-ui/hi-page-template"
;
import
HiPageTemplate
from
"pangea-ui/hi-page-template"
;
// 获取表单 store
const
formStore
=
useFormStore
();
// 表单组件ref
const
templateRef
=
ref
();
const
templateRef
=
ref
();
// 表单提交回调
const
submit
=
()
=>
{
const
submit
=
()
=>
{
templateRef
.
value
?.
ctx
.
validate
(
1
,
(
res
,
data
)
=>
{
templateRef
.
value
?.
ctx
.
validate
(
1
,
(
res
:
boolean
,
data
:
any
)
=>
{
console
.
log
(
res
,
data
);
console
.
log
(
res
,
data
);
});
});
};
};
const
json
=
{
pages
:
[
{
key
:
0
,
type
:
"default"
,
name
:
"默认页"
,
code
:
""
,
display
:
""
,
props
:
{
margin
:
"16px"
,
padding
:
"12px"
,
backgroundColor
:
"white"
,
display
:
{},
},
bindProps
:
{},
coms
:
[
{
key
:
1
,
type
:
"node"
,
name
:
"表单容器"
,
code
:
"HiFormContainer"
,
display
:
""
,
props
:
{
status
:
"default"
,
backgroundColor
:
"transparent"
,
layout
:
"horizontal"
,
size
:
"medium"
,
labelAlign
:
"right"
,
display
:
{},
borderRadius
:
{},
boxShadow
:
{},
loop
:
{
data
:
[],
},
},
bindProps
:
{},
coms
:
[
{
key
:
1766473421208
,
name
:
"输入框"
,
code
:
"HiInput"
,
props
:
{
title
:
"输入框"
,
status
:
"default"
,
placeholder
:
"请输入"
,
name
:
"INPUT_6CP8HIBK"
,
},
bindProps
:
{},
coms
:
[],
},
{
key
:
1766476676439
,
name
:
"日期"
,
code
:
"HiDatePicker"
,
props
:
{
title
:
"日期"
,
type
:
"date"
,
format
:
"YYYY-MM-DD"
,
status
:
"default"
,
name
:
"DATE_PA9TUPQQ"
,
},
bindProps
:
{},
},
],
},
],
},
],
params
:
[],
apis
:
[],
funcs
:
[],
pageTemplate
:
{},
};
</
script
>
</
script
>
<
style
scoped
>
<
style
scoped
>
.button-wrap
{
.button-wrap
{
...
...
frontend/src/stores/form.ts
0 → 100644
View file @
fb1f853d
/*
* @Description: 表单页store
* @Author: gaorubin
* @Date: 2025-12-24 16:12:58
* @LastEditors: gaorubin
* @LastEditTime: 2025-12-24 17:26:54
*/
import
{
defineStore
}
from
"pinia"
;
import
{
cloneDeep
}
from
"lodash-es"
;
// 初始页面json外层
const
INIT_FORM_JSON
=
{
pages
:
[
{
key
:
0
,
type
:
"default"
,
name
:
"默认页"
,
code
:
""
,
display
:
""
,
props
:
{
margin
:
"16px"
,
padding
:
"12px"
,
backgroundColor
:
"white"
,
display
:
{},
},
bindProps
:
{},
coms
:
[
{
key
:
1
,
type
:
"node"
,
name
:
"表单容器"
,
code
:
"HiFormContainer"
,
display
:
""
,
props
:
{
status
:
"default"
,
backgroundColor
:
"transparent"
,
layout
:
"horizontal"
,
size
:
"medium"
,
labelAlign
:
"right"
,
display
:
{},
borderRadius
:
{},
boxShadow
:
{},
loop
:
{
data
:
[],
},
},
bindProps
:
{},
coms
:
[],
},
],
},
],
params
:
[],
apis
:
[],
funcs
:
[],
pageTemplate
:
{},
};
export
const
useFormStore
=
defineStore
(
"form"
,
{
state
:
()
=>
({
// 表单 JSON 配置(使用深拷贝避免共享引用)
formJson
:
cloneDeep
(
INIT_FORM_JSON
),
// 是否显示表单页面
showForm
:
false
,
}),
actions
:
{
/**
* 设置表单 JSON 配置
* @param json 表单配置对象
*/
setFormJson
(
json
:
any
)
{
this
.
formJson
=
json
;
},
/**
* 清空表单 JSON 配置
*/
clearFormJson
()
{
this
.
formJson
=
cloneDeep
(
INIT_FORM_JSON
);
},
/**
* 显示表单页面
* @param json 可选的表单配置对象,格式为 { coms: [] }
*/
openForm
(
json
?:
any
)
{
if
(
json
)
{
// 验证 json 的有效性
if
(
!
json
.
coms
||
!
Array
.
isArray
(
json
.
coms
))
{
console
.
error
(
"无效的表单配置:缺少 coms 数组"
,
json
);
return
;
}
// 深拷贝 INIT_FORM_JSON 避免修改原始对象
const
formConfig
=
cloneDeep
(
INIT_FORM_JSON
);
// 将接收到的 coms 放入表单容器的 coms 数组中
// 表单容器位于 pages[0].coms[0](HiFormContainer)
if
(
formConfig
.
pages
&&
formConfig
.
pages
[
0
]
&&
formConfig
.
pages
[
0
].
coms
&&
formConfig
.
pages
[
0
].
coms
[
0
]
)
{
formConfig
.
pages
[
0
].
coms
[
0
].
coms
=
json
.
coms
;
}
else
{
console
.
error
(
"INIT_FORM_JSON 结构异常,无法找到表单容器"
);
return
;
}
// 设置表单配置
this
.
setFormJson
(
formConfig
);
this
.
showForm
=
true
;
}
},
/**
* 隐藏表单页面
*/
closeForm
()
{
this
.
showForm
=
false
;
this
.
clearFormJson
();
},
},
});
frontend/vite.config.ts
View file @
fb1f853d
import
{
defineConfig
}
from
'vite'
/*
import
vue
from
'@vitejs/plugin-vue'
* @Description:
import
path
from
'path'
* @Author: gaorubin
* @Date: 2025-12-22 14:30:43
* @LastEditors: gaorubin
* @LastEditTime: 2025-12-24 18:11:39
*/
import
{
defineConfig
}
from
"vite"
;
import
vue
from
"@vitejs/plugin-vue"
;
import
path
from
"path"
;
export
default
defineConfig
({
export
default
defineConfig
({
plugins
:
[
vue
()],
plugins
:
[
vue
()],
resolve
:
{
resolve
:
{
alias
:
{
alias
:
{
'@'
:
path
.
resolve
(
__dirname
,
'./src'
)
"@"
:
path
.
resolve
(
__dirname
,
"./src"
),
}
}
,
},
},
server
:
{
server
:
{
// port: 8081, // 注释掉固定端口,让Vite自动选择可用端口
// port: 8081, // 注释掉固定端口,让Vite自动选择可用端口
...
@@ -15,40 +22,42 @@ export default defineConfig({
...
@@ -15,40 +22,42 @@ export default defineConfig({
fs
:
{
fs
:
{
// 允许访问文件系统,解决Windows平台上的动态导入问题
// 允许访问文件系统,解决Windows平台上的动态导入问题
allow
:
[
allow
:
[
'..'
,
".."
,
// 明确允许项目根目录
// 明确允许项目根目录
path
.
resolve
(
__dirname
),
path
.
resolve
(
__dirname
),
// 明确允许工作区根目录
// 明确允许工作区根目录
path
.
resolve
(
__dirname
,
'..'
)
path
.
resolve
(
__dirname
,
".."
),
],
],
// 禁用严格的文件系统限制
// 禁用严格的文件系统限制
strict
:
false
strict
:
false
,
},
},
// 添加headers配置以允许iframe加载
// 添加headers配置以允许iframe加载
headers
:
{
headers
:
{
'X-Frame-Options'
:
'SAMEORIGIN'
"X-Frame-Options"
:
"SAMEORIGIN"
,
},
},
proxy
:
{
proxy
:
{
'/api'
:
{
"/api"
:
{
target
:
'http://localhost:8080'
,
target
:
"http://localhost:8080"
,
// target: "http://agent-backend.clouddev.hisense.com",
changeOrigin
:
true
,
changeOrigin
:
true
,
rewrite
:
(
path
)
=>
path
.
replace
(
/^
\/
api/
,
'/api'
)
rewrite
:
(
path
)
=>
path
.
replace
(
/^
\/
api/
,
"/api"
),
},
},
'/ws'
:
{
"/ws"
:
{
target
:
'http://localhost:8080'
,
target
:
"http://localhost:8080"
,
// target: "http://agent-backend.clouddev.hisense.com",
ws
:
true
,
// 启用WebSocket代理
ws
:
true
,
// 启用WebSocket代理
changeOrigin
:
true
changeOrigin
:
true
,
}
}
,
}
}
,
},
},
build
:
{
build
:
{
target
:
'esnext'
,
target
:
"esnext"
,
minify
:
'terser'
,
minify
:
"terser"
,
sourcemap
:
false
sourcemap
:
false
,
},
},
esbuild
:
{
esbuild
:
{
jsxFactory
:
'h'
,
jsxFactory
:
"h"
,
jsxFragment
:
'Fragment'
,
jsxFragment
:
"Fragment"
,
tsconfigRaw
:
'{}'
tsconfigRaw
:
"{}"
,
}
},
})
});
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment