Commit 108ccd6d authored by youxiaoji's avatar youxiaoji

Merge remote-tracking branch 'refs/remotes/origin/feature/chat-form' into develop_yxj

parents fa515e2b 1582b695
...@@ -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
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -17,8 +17,10 @@ ...@@ -17,8 +17,10 @@
"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": "1.0.1-beta.2",
"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 +28,13 @@ ...@@ -26,11 +28,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",
......
This diff is collapsed.
<template>
<div v-if="formStore.showForm">
<hi-page-template
ref="templateRef"
:json="formStore.formJson"
:open-intl="false"
></hi-page-template>
<div class="button-wrap">
<a-button type="primary" @click="submit">提交</a-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useFormStore } from "@/stores/form";
import { ElMessage } from "element-plus";
import HiPageTemplate from "pangea-ui/hi-page-template";
// 获取表单 store
const formStore = useFormStore();
// 表单组件ref
const templateRef = ref();
// 表单提交回调
const submit = () => {
templateRef.value?.ctx.validate(1, (isValid: boolean, formData: any) => {
if (isValid) {
// 临时处理:追加隐藏字段到表单数据
const hiddenFields = formStore.hiddenFields;
hiddenFields.forEach((field) => {
if (field.name) {
formData[field.name] = "none";
}
});
console.log("表单验证通过,提交数据:", formData);
// 调用 store 的 submitForm 方法,触发回调
formStore.submitForm(formData);
ElMessage.success("表单提交成功");
} else {
console.log("表单验证失败");
ElMessage.error("请检查表单填写是否正确");
}
});
};
</script>
<style scoped>
.button-wrap {
display: flex;
justify-content: center;
}
</style>
<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 timelineContainerRef = ref<InstanceType<typeof TimelineContainer> | null>(null) const activeTab = ref("form");
const webBrowser = ref() const formRender = ref();
let timelineService: TimelineService | null = null const timelineContainerRef = ref<InstanceType<typeof TimelineContainer> | 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>
......
...@@ -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)) {
......
/*
* @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 getInitFormJson = () => ({
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: getInitFormJson(),
// 是否显示表单页面
showForm: false,
// 表单提交回调函数
onSubmitCallback: null as ((data: any) => void) | null,
// 过滤掉的隐藏字段
hiddenFields: [] as Array<{ title: string; name: string }>,
}),
actions: {
/**
* 设置表单 JSON 配置
* @param json 表单配置对象
*/
setFormJson(json: any) {
this.formJson = json;
},
/**
* 清空表单 JSON 配置
*/
clearFormJson() {
this.formJson = getInitFormJson();
},
/**
* 设置隐藏字段
* @param fields 隐藏字段数组
*/
setHiddenFields(fields: Array<{ title: string; name: string }>) {
this.hiddenFields = fields;
},
/**
* 清空隐藏字段
*/
clearHiddenFields() {
this.hiddenFields = [];
},
/**
* 设置表单提交回调函数
* @param callback 回调函数
*/
setSubmitCallback(callback: (data: any) => void) {
this.onSubmitCallback = callback;
},
/**
* 触发表单提交
* @param data 表单数据
*/
submitForm(data: any) {
if (this.onSubmitCallback) {
this.onSubmitCallback(data);
}
// 提交后关闭表单
this.closeForm();
},
/**
* 显示表单页面
* @param json 可选的表单配置对象,格式为 { coms: [] }
*/
openForm(json?: any) {
if (json) {
// 验证 json 的有效性
if (!json.coms || !Array.isArray(json.coms)) {
console.error("无效的表单配置:缺少 coms 数组", json);
return;
}
// 获取初始表单配置
const formConfig = getInitFormJson();
// 将接收到的 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();
this.clearHiddenFields();
},
},
});
import { defineConfig } from 'vite' import { defineConfig } from "vite";
import vue from '@vitejs/plugin-vue' import vue from "@vitejs/plugin-vue";
import path from 'path' 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 +15,66 @@ export default defineConfig({ ...@@ -15,40 +15,66 @@ 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: {
target: "esnext",
minify: "terser",
sourcemap: false,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes("node_modules/echarts")) {
return "echarts";
}
if (id.includes("tinymce")) {
return "tinymce";
} }
if (id.includes("@babel/standalone")) {
return "babel-standalone";
} }
if (id.includes("pangea-component")) {
return "pangea-component";
}
/* !id.endsWith(".css") 是为了解决拆包后的样式问题 */
if (id.includes("pangea-ui") && !id.endsWith(".css")) {
return "pangea-ui";
}
return undefined;
// 其他模块的处理逻辑
},
},
}, },
build: {
target: 'esnext',
minify: 'terser',
sourcemap: false
}, },
esbuild: { esbuild: {
jsxFactory: 'h', jsxFactory: "h",
jsxFragment: 'Fragment', jsxFragment: "Fragment",
tsconfigRaw: '{}' tsconfigRaw: "{}",
} },
}) });
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment