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
8a629996
Commit
8a629996
authored
Dec 23, 2025
by
高如斌
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
1
parent
8bcd41e5
Changes
7
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
4700 additions
and
552 deletions
+4700
-552
.gitignore
.gitignore
+3
-0
package-lock.json
frontend/package-lock.json
+4046
-138
package.json
frontend/package.json
+3
-0
ChatArea.vue
frontend/src/components/ChatArea.vue
+474
-363
FormRender.vue
frontend/src/components/FormRender.vue
+102
-0
WorkArea.vue
frontend/src/components/WorkArea.vue
+67
-51
main.ts
frontend/src/main.ts
+5
-0
No files found.
.gitignore
View file @
8a629996
...
@@ -202,6 +202,9 @@ frontend/.env.development.local
...
@@ -202,6 +202,9 @@ frontend/.env.development.local
frontend/.env.test.local
frontend/.env.test.local
frontend/.env.production.local
frontend/.env.production.local
# Kiro IDE files
.kiro/
# OS generated files
# OS generated files
Thumbs.db
Thumbs.db
.DS_Store
.DS_Store
...
...
frontend/package-lock.json
View file @
8a629996
This source diff could not be displayed because it is too large. You can
view the blob
instead.
frontend/package.json
View file @
8a629996
...
@@ -19,6 +19,7 @@
...
@@ -19,6 +19,7 @@
"highlight.js"
:
"^11.9.0"
,
"highlight.js"
:
"^11.9.0"
,
"marked"
:
"^17.0.1"
,
"marked"
:
"^17.0.1"
,
"pako"
:
"^2.1.0"
,
"pako"
:
"^2.1.0"
,
"pangea-ui"
:
"^0.14.2-beta.9"
,
"pinia"
:
"^2.1.7"
,
"pinia"
:
"^2.1.7"
,
"snabbdom"
:
"^3.6.3"
,
"snabbdom"
:
"^3.6.3"
,
"vue"
:
"^3.4.0"
,
"vue"
:
"^3.4.0"
,
...
@@ -26,11 +27,13 @@
...
@@ -26,11 +27,13 @@
"vue-router"
:
"^4.3.0"
"vue-router"
:
"^4.3.0"
},
},
"devDependencies"
:
{
"devDependencies"
:
{
"@arco-design/web-vue"
:
"^2.55.3"
,
"@types/dompurify"
:
"^3.0.5"
,
"@types/dompurify"
:
"^3.0.5"
,
"@types/node"
:
"^20.10.0"
,
"@types/node"
:
"^20.10.0"
,
"@vitejs/plugin-vue"
:
"^5.0.0"
,
"@vitejs/plugin-vue"
:
"^5.0.0"
,
"eslint"
:
"^8.55.0"
,
"eslint"
:
"^8.55.0"
,
"eslint-plugin-vue"
:
"^9.19.0"
,
"eslint-plugin-vue"
:
"^9.19.0"
,
"less"
:
"^4.2.0"
,
"terser"
:
"^5.44.1"
,
"terser"
:
"^5.44.1"
,
"typescript"
:
"^5.9.3"
,
"typescript"
:
"^5.9.3"
,
"vite"
:
"^5.0.0"
,
"vite"
:
"^5.0.0"
,
...
...
frontend/src/components/ChatArea.vue
View file @
8a629996
This diff is collapsed.
Click to expand it.
frontend/src/components/FormRender.vue
0 → 100644
View file @
8a629996
<
template
>
<hi-page-template
ref=
"templateRef"
:json=
"json"
:open-intl=
"false"
></hi-page-template>
<div
class=
"button-wrap"
>
<a-button
type=
"primary"
@
click=
"submit"
>
提交
</a-button>
</div>
</
template
>
<
script
lang=
"ts"
setup
>
import
{
ref
}
from
"vue"
;
import
HiPageTemplate
from
"pangea-ui/hi-page-template"
;
const
templateRef
=
ref
();
const
submit
=
()
=>
{
templateRef
.
value
?.
ctx
.
validate
(
1
,
(
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
>
<
style
scoped
>
.button-wrap
{
display
:
flex
;
justify-content
:
center
;
}
</
style
>
frontend/src/components/WorkArea.vue
View file @
8a629996
<
template
>
<
template
>
<div
class=
"work-area"
>
<div
class=
"work-area"
>
<el-tabs
v-model=
"activeTab"
class=
"work-tabs"
>
<el-tabs
v-model=
"activeTab"
class=
"work-tabs"
>
<el-tab-pane
label=
"表单"
name=
"form"
>
<form-render
ref=
"formRender"
/>
</el-tab-pane>
<el-tab-pane
label=
"📋 时间轴"
name=
"timeline"
>
<el-tab-pane
label=
"📋 时间轴"
name=
"timeline"
>
<timeline-container
ref=
"timelineContainerRef"
/>
<timeline-container
ref=
"timelineContainerRef"
/>
</el-tab-pane>
</el-tab-pane>
...
@@ -12,108 +15,121 @@
...
@@ -12,108 +15,121 @@
</
template
>
</
template
>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
ref
,
onMounted
,
onUnmounted
}
from
'vue'
import
{
ref
,
onMounted
,
onUnmounted
}
from
"vue"
;
import
TimelineContainer
from
'./TimelineContainer.vue'
import
FormRender
from
"./FormRender.vue"
;
import
WebpageBrowser
from
'./WebpageBrowser.vue'
import
TimelineContainer
from
"./TimelineContainer.vue"
;
import
{
TimelineService
}
from
'../services/TimelineService'
import
WebpageBrowser
from
"./WebpageBrowser.vue"
;
import
{
TimelineService
}
from
"../services/TimelineService"
;
const
activeTab
=
ref
(
'timeline'
)
const
activeTab
=
ref
(
"form"
);
const
timelineContainerRef
=
ref
<
InstanceType
<
typeof
TimelineContainer
>
|
null
>
(
null
)
const
formRender
=
ref
();
const
webBrowser
=
ref
()
const
timelineContainerRef
=
ref
<
InstanceType
<
typeof
TimelineContainer
>
|
null
>
(
let
timelineService
:
TimelineService
|
null
=
null
null
);
const
webBrowser
=
ref
();
let
timelineService
:
TimelineService
|
null
=
null
;
// 添加事件到时间轴
// 添加事件到时间轴
const
addEvent
=
(
event
:
any
):
void
=>
{
const
addEvent
=
(
event
:
any
):
void
=>
{
timelineContainerRef
.
value
?.
addEvent
(
event
)
timelineContainerRef
.
value
?.
addEvent
(
event
)
;
}
}
;
// 初始化Timeline服务
// 初始化Timeline服务
const
initTimelineService
=
()
=>
{
const
initTimelineService
=
()
=>
{
if
(
timelineContainerRef
.
value
)
{
if
(
timelineContainerRef
.
value
)
{
timelineService
=
new
TimelineService
((
event
:
any
)
=>
{
timelineService
=
new
TimelineService
((
event
:
any
)
=>
{
addEvent
(
event
)
addEvent
(
event
)
;
})
})
;
timelineService
.
connectSSE
()
timelineService
.
connectSSE
()
;
}
}
}
}
;
// 清除时间轴
// 清除时间轴
const
clearTimeline
=
():
void
=>
{
const
clearTimeline
=
():
void
=>
{
timelineContainerRef
.
value
?.
clearTimeline
()
timelineContainerRef
.
value
?.
clearTimeline
()
;
}
}
;
// 定义embed事件的详细信息类型
// 定义embed事件的详细信息类型
interface
EmbedEventDetail
{
interface
EmbedEventDetail
{
url
:
string
url
:
string
;
type
:
string
type
:
string
;
title
:
string
title
:
string
;
htmlContent
?:
string
htmlContent
?:
string
;
}
}
// 监听embed事件
// 监听embed事件
const
handleEmbedEvent
=
(
e
:
Event
)
=>
{
const
handleEmbedEvent
=
(
e
:
Event
)
=>
{
const
customEvent
=
e
as
CustomEvent
<
EmbedEventDetail
>
const
customEvent
=
e
as
CustomEvent
<
EmbedEventDetail
>
;
const
{
url
,
type
,
title
,
htmlContent
}
=
customEvent
.
detail
const
{
url
,
type
,
title
,
htmlContent
}
=
customEvent
.
detail
;
// 验证URL有效性
// 验证URL有效性
if
(
!
url
||
typeof
url
!==
'string'
||
url
.
trim
()
===
''
)
{
if
(
!
url
||
typeof
url
!==
"string"
||
url
.
trim
()
===
""
)
{
console
.
error
(
'[WorkArea] embed事件URL验证失败:'
,
{
console
.
error
(
"[WorkArea] embed事件URL验证失败:"
,
{
url
:
url
,
url
:
url
,
type
:
typeof
url
,
type
:
typeof
url
,
isEmpty
:
url
?.
trim
()
===
''
,
isEmpty
:
url
?.
trim
()
===
""
,
detail
:
customEvent
.
detail
detail
:
customEvent
.
detail
,
})
})
;
return
return
;
}
}
// 自动切换到浏览器标签页
// 自动切换到浏览器标签页
activeTab
.
value
=
'browser'
activeTab
.
value
=
"browser"
;
// 调用WebpageBrowser的导航方法,传递完整信息
// 调用WebpageBrowser的导航方法,传递完整信息
if
(
webBrowser
.
value
&&
typeof
webBrowser
.
value
.
navigateToUrl
===
'function'
)
{
if
(
webBrowser
.
value
&&
typeof
webBrowser
.
value
.
navigateToUrl
===
"function"
)
{
webBrowser
.
value
.
navigateToUrl
(
url
,
{
webBrowser
.
value
.
navigateToUrl
(
url
,
{
htmlContent
:
htmlContent
,
htmlContent
:
htmlContent
,
embedType
:
type
,
embedType
:
type
,
embedTitle
:
title
embedTitle
:
title
,
})
})
;
}
else
{
}
else
{
console
.
error
(
'[WorkArea] webBrowser引用无效或navigateToUrl方法不存在'
,
{
console
.
error
(
"[WorkArea] webBrowser引用无效或navigateToUrl方法不存在"
,
{
hasWebBrowser
:
!!
webBrowser
.
value
,
hasWebBrowser
:
!!
webBrowser
.
value
,
hasFn
:
webBrowser
.
value
?
typeof
webBrowser
.
value
.
navigateToUrl
:
'undefined'
hasFn
:
webBrowser
.
value
})
?
typeof
webBrowser
.
value
.
navigateToUrl
:
"undefined"
,
});
}
}
}
}
;
onMounted
(()
=>
{
onMounted
(()
=>
{
// 监听embed事件
// 监听embed事件
window
.
addEventListener
(
'embed-event'
,
handleEmbedEvent
as
EventListener
)
window
.
addEventListener
(
"embed-event"
,
handleEmbedEvent
as
EventListener
);
// 初始化Timeline服务
// 初始化Timeline服务
initTimelineService
()
initTimelineService
()
;
})
})
;
onUnmounted
(()
=>
{
onUnmounted
(()
=>
{
// 移除事件监听
// 移除事件监听
window
.
removeEventListener
(
'embed-event'
,
handleEmbedEvent
as
EventListener
)
window
.
removeEventListener
(
"embed-event"
,
handleEmbedEvent
as
EventListener
);
// 清理Timeline服务
// 清理Timeline服务
if
(
timelineService
)
{
if
(
timelineService
)
{
timelineService
.
cleanup
()
timelineService
.
cleanup
()
;
}
}
})
// 暴露方法供父组件调用
})
;
// 暴露方法供父组件调用
defineExpose
({
defineExpose
({
formRender
,
timelineContainerRef
,
timelineContainerRef
,
webBrowser
,
webBrowser
,
activeTab
,
activeTab
,
// 提供切换tab的方法
// 提供切换tab的方法
switchToForm
:
()
=>
{
activeTab
.
value
=
"form"
;
},
switchToTimeline
:
()
=>
{
switchToTimeline
:
()
=>
{
activeTab
.
value
=
'timeline'
activeTab
.
value
=
"timeline"
;
},
},
switchToBrowser
:
()
=>
{
switchToBrowser
:
()
=>
{
activeTab
.
value
=
'browser'
activeTab
.
value
=
"browser"
;
},
},
// 提供时间轴操作方法
// 提供时间轴操作方法
addEvent
,
addEvent
,
clearTimeline
clearTimeline
,
})
})
;
</
script
>
</
script
>
<
style
scoped
>
<
style
scoped
>
...
...
frontend/src/main.ts
View file @
8a629996
...
@@ -4,6 +4,9 @@ import router from './router'
...
@@ -4,6 +4,9 @@ import router from './router'
import
{
createPinia
}
from
'pinia'
import
{
createPinia
}
from
'pinia'
import
ElementPlus
from
'element-plus'
import
ElementPlus
from
'element-plus'
import
'element-plus/dist/index.css'
import
'element-plus/dist/index.css'
import
ArcoVue
from
"@arco-design/web-vue"
;
import
ArcoVueIcon
from
"@arco-design/web-vue/es/icon"
;
import
'@arco-design/web-vue/dist/arco.less'
;
import
'highlight.js/styles/atom-one-dark.css'
import
'highlight.js/styles/atom-one-dark.css'
import
'./styles/variables.css'
import
'./styles/variables.css'
import
'./styles/global.css'
import
'./styles/global.css'
...
@@ -17,6 +20,8 @@ const app = createApp(App)
...
@@ -17,6 +20,8 @@ const app = createApp(App)
app
.
use
(
createPinia
())
app
.
use
(
createPinia
())
app
.
use
(
router
)
app
.
use
(
router
)
app
.
use
(
ElementPlus
)
app
.
use
(
ElementPlus
)
app
.
use
(
ArcoVue
)
app
.
use
(
ArcoVueIcon
)
// 全局注册所有Element Plus图标
// 全局注册所有Element Plus图标
for
(
const
[
key
,
component
]
of
Object
.
entries
(
ElementPlusIconsVue
))
{
for
(
const
[
key
,
component
]
of
Object
.
entries
(
ElementPlusIconsVue
))
{
...
...
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