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
frontend/.env.test.local
frontend/.env.production.local
# Kiro IDE files
.kiro/
# OS generated files
Thumbs.db
.DS_Store
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -17,8 +17,10 @@
"dompurify": "^3.3.1",
"element-plus": "^2.4.0",
"highlight.js": "^11.9.0",
"lodash-es": "^4.17.21",
"marked": "^17.0.1",
"pako": "^2.1.0",
"pangea-ui": "1.0.1-beta.2",
"pinia": "^2.1.7",
"snabbdom": "^3.6.3",
"vue": "^3.4.0",
......@@ -26,11 +28,13 @@
"vue-router": "^4.3.0"
},
"devDependencies": {
"@arco-design/web-vue": "^2.55.3",
"@types/dompurify": "^3.0.5",
"@types/node": "^20.10.0",
"@vitejs/plugin-vue": "^5.0.0",
"eslint": "^8.55.0",
"eslint-plugin-vue": "^9.19.0",
"less": "^4.2.0",
"terser": "^5.44.1",
"typescript": "^5.9.3",
"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>
<div class="work-area">
<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">
<timeline-container ref="timelineContainerRef" />
</el-tab-pane>
......@@ -12,108 +15,121 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import TimelineContainer from './TimelineContainer.vue'
import WebpageBrowser from './WebpageBrowser.vue'
import { TimelineService } from '../services/TimelineService'
const activeTab = ref('timeline')
const timelineContainerRef = ref<InstanceType<typeof TimelineContainer> | null>(null)
const webBrowser = ref()
let timelineService: TimelineService | null = null
import { ref, onMounted, onUnmounted } from "vue";
import FormRender from "./FormRender.vue";
import TimelineContainer from "./TimelineContainer.vue";
import WebpageBrowser from "./WebpageBrowser.vue";
import { TimelineService } from "../services/TimelineService";
const activeTab = ref("form");
const formRender = ref();
const timelineContainerRef = ref<InstanceType<typeof TimelineContainer> | null>(
null
);
const webBrowser = ref();
let timelineService: TimelineService | null = null;
// 添加事件到时间轴
const addEvent = (event: any): void => {
timelineContainerRef.value?.addEvent(event)
}
timelineContainerRef.value?.addEvent(event);
};
// 初始化Timeline服务
const initTimelineService = () => {
if (timelineContainerRef.value) {
timelineService = new TimelineService((event: any) => {
addEvent(event)
})
timelineService.connectSSE()
addEvent(event);
});
timelineService.connectSSE();
}
}
};
// 清除时间轴
const clearTimeline = (): void => {
timelineContainerRef.value?.clearTimeline()
}
timelineContainerRef.value?.clearTimeline();
};
// 定义embed事件的详细信息类型
interface EmbedEventDetail {
url: string
type: string
title: string
htmlContent?: string
url: string;
type: string;
title: string;
htmlContent?: string;
}
// 监听embed事件
const handleEmbedEvent = (e: Event) => {
const customEvent = e as CustomEvent<EmbedEventDetail>
const { url, type, title, htmlContent } = customEvent.detail
const customEvent = e as CustomEvent<EmbedEventDetail>;
const { url, type, title, htmlContent } = customEvent.detail;
// 验证URL有效性
if (!url || typeof url !== 'string' || url.trim() === '') {
console.error('[WorkArea] embed事件URL验证失败:', {
if (!url || typeof url !== "string" || url.trim() === "") {
console.error("[WorkArea] embed事件URL验证失败:", {
url: url,
type: typeof url,
isEmpty: url?.trim() === '',
detail: customEvent.detail
})
return
isEmpty: url?.trim() === "",
detail: customEvent.detail,
});
return;
}
// 自动切换到浏览器标签页
activeTab.value = 'browser'
activeTab.value = "browser";
// 调用WebpageBrowser的导航方法,传递完整信息
if (webBrowser.value && typeof webBrowser.value.navigateToUrl === 'function') {
if (
webBrowser.value &&
typeof webBrowser.value.navigateToUrl === "function"
) {
webBrowser.value.navigateToUrl(url, {
htmlContent: htmlContent,
embedType: type,
embedTitle: title
})
embedTitle: title,
});
} else {
console.error('[WorkArea] webBrowser引用无效或navigateToUrl方法不存在', {
console.error("[WorkArea] webBrowser引用无效或navigateToUrl方法不存在", {
hasWebBrowser: !!webBrowser.value,
hasFn: webBrowser.value ? typeof webBrowser.value.navigateToUrl : 'undefined'
})
hasFn: webBrowser.value
? typeof webBrowser.value.navigateToUrl
: "undefined",
});
}
}
};
onMounted(() => {
// 监听embed事件
window.addEventListener('embed-event', handleEmbedEvent as EventListener)
window.addEventListener("embed-event", handleEmbedEvent as EventListener);
// 初始化Timeline服务
initTimelineService()
})
initTimelineService();
});
onUnmounted(() => {
// 移除事件监听
window.removeEventListener('embed-event', handleEmbedEvent as EventListener)
window.removeEventListener("embed-event", handleEmbedEvent as EventListener);
// 清理Timeline服务
if (timelineService) {
timelineService.cleanup()
timelineService.cleanup();
}
})// 暴露方法供父组件调用
}); // 暴露方法供父组件调用
defineExpose({
formRender,
timelineContainerRef,
webBrowser,
activeTab,
// 提供切换tab的方法
switchToForm: () => {
activeTab.value = "form";
},
switchToTimeline: () => {
activeTab.value = 'timeline'
activeTab.value = "timeline";
},
switchToBrowser: () => {
activeTab.value = 'browser'
activeTab.value = "browser";
},
// 提供时间轴操作方法
addEvent,
clearTimeline
})
clearTimeline,
});
</script>
<style scoped>
......
......@@ -4,6 +4,9 @@ import router from './router'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
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 './styles/variables.css'
import './styles/global.css'
......@@ -17,6 +20,8 @@ const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(ElementPlus)
app.use(ArcoVue)
app.use(ArcoVueIcon)
// 全局注册所有Element Plus图标
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 vue from '@vitejs/plugin-vue'
import path from 'path'
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
"@": path.resolve(__dirname, "./src"),
},
},
server: {
// port: 8081, // 注释掉固定端口,让Vite自动选择可用端口
......@@ -15,40 +15,66 @@ export default defineConfig({
fs: {
// 允许访问文件系统,解决Windows平台上的动态导入问题
allow: [
'..',
"..",
// 明确允许项目根目录
path.resolve(__dirname),
// 明确允许工作区根目录
path.resolve(__dirname, '..')
path.resolve(__dirname, ".."),
],
// 禁用严格的文件系统限制
strict: false
strict: false,
},
// 添加headers配置以允许iframe加载
headers: {
'X-Frame-Options': 'SAMEORIGIN'
"X-Frame-Options": "SAMEORIGIN",
},
proxy: {
'/api': {
target: 'http://localhost:8080',
"/api": {
// target: "http://localhost:8080",
target: "http://agent-backend.clouddev.hisense.com",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '/api')
rewrite: (path) => path.replace(/^\/api/, "/api"),
},
'/ws': {
target: 'http://localhost:8080',
"/ws": {
// target: "http://localhost:8080",
target: "http://agent-backend.clouddev.hisense.com",
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: {
jsxFactory: 'h',
jsxFragment: 'Fragment',
tsconfigRaw: '{}'
}
})
\ No newline at end of file
jsxFactory: "h",
jsxFragment: "Fragment",
tsconfigRaw: "{}",
},
});
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