feat(backend): 新增 QuickJS 运行时支持 JavaScript 执行器

refactor(backend): 重构 script_js 执行器实现 JavaScript 文件/内联脚本执行
feat(backend): 变量节点支持表达式/引用快捷语法输入
docs: 添加变量节点使用文档说明快捷语法功能
style(frontend): 调整测试面板样式和布局
fix(frontend): 修复测试面板打开时自动关闭节点编辑侧栏
build(backend): 添加 rquickjs 依赖用于 JavaScript 执行
This commit is contained in:
2025-09-20 17:35:36 +08:00
parent baa787934a
commit 296f0ae9f6
11 changed files with 357 additions and 41 deletions

View File

@ -10,7 +10,8 @@
padding: 8px 8px 8px 4px;
border-radius: 8px;
border: 1px solid #7f92cd40;
width: 348px;
width: 100%;
box-sizing: border-box;
:global(.cm-editor) {
height: 100% !important;

View File

@ -27,8 +27,8 @@
}
.code-editor-container {
min-height: 200px;
max-height: 400px;
min-height: 240px;
max-height: 520px;
background: #fff;
padding: 8px 8px 8px 4px;
border-radius: 4px;
@ -40,13 +40,13 @@
}
:global(.cm-scroller) {
min-height: 200px !important;
max-height: 400px !important;
min-height: 240px !important;
max-height: 520px !important;
}
:global(.cm-content) {
min-height: 200px !important;
max-height: 400px !important;
min-height: 240px !important;
max-height: 520px !important;
}
}
}
@ -67,9 +67,10 @@
.button {
border-radius: 8px;
width: 358px;
width: 100%;
height: 40px;
margin: 16px;
margin: 0;
box-sizing: border-box;
&.running {
background-color: rgba(87, 104, 161, 0.08) !important; // override semi style
@ -93,6 +94,7 @@
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
.testrun-panel-header {
background: var(#fcfcff);
@ -129,11 +131,15 @@
.testrun-panel-footer {
border-top: 1px solid rgba(82, 100, 154, 0.13);
height: 40px;
position: fixed;
position: absolute;
background: #fbfbfb;
height: 72px;
bottom: 16px;
left: 0;
right: 0;
bottom: 0;
border-radius: 0 0 8px 8px;
padding: 8px 16px;
display: flex;
align-items: center;
}
}

View File

@ -102,10 +102,12 @@ export const TestRunSidePanel: FC<TestRunSidePanelProps> = ({ visible, onCancel
onCancel();
};
// sidebar effect
// 当测试运行面板打开时,自动关闭右侧节点编辑侧栏,避免两个 SideSheet 重叠
useEffect(() => {
setNodeId(undefined);
}, []);
if (visible) {
setNodeId(undefined);
}
}, [visible]);
useEffect(() => {
if (sidebarNodeId) {
@ -177,7 +179,7 @@ export const TestRunSidePanel: FC<TestRunSidePanelProps> = ({ visible, onCancel
mask={false}
motion={false}
onCancel={onClose}
width={400}
width={500}
headerStyle={{
display: 'none',
}}

View File

@ -5,6 +5,7 @@
import { FormMeta, FormRenderProps } from '@flowgram.ai/free-layout-editor';
import { AssignRows, createInferAssignPlugin, DisplayOutputs } from '@flowgram.ai/form-materials';
import { Typography } from '@douyinfe/semi-ui';
import { FormHeader, FormContent } from '../../form-components';
import { VariableNodeJSON } from './types';
@ -18,7 +19,17 @@ export const FormRender = ({ form }: FormRenderProps<VariableNodeJSON>) => {
<>
<FormHeader />
<FormContent>
{isSidebar ? <AssignRows name="assign" /> : <DisplayOutputs displayFromScope />}
{isSidebar ? (
<>
<Typography.Text type="tertiary" style={{ display: 'block', marginBottom: 8 }}>
1) ctx["user_n"], ctx.user.profile.name2) 使 {"${path.to.value}"}
</Typography.Text>
<AssignRows name="assign" />
</>
) : (
<DisplayOutputs displayFromScope />
)}
</FormContent>
</>
);

View File

@ -31,6 +31,24 @@ function getFlowIdFromUrl(): string {
return '';
}
// 新增:针对后端的 design_json 兼容性转换
// - 将前端 UI 的 type: 'code' 映射为后端可识别并映射到 script_js 执行器的 'javascript'
// - 其余字段保持不变
function transformDesignJsonForBackend(json: any): any {
try {
const clone = JSON.parse(JSON.stringify(json));
clone.nodes = (clone.nodes || []).map((n: any) => {
if (n && n.type === 'code') {
return { ...n, type: 'javascript' };
}
return n;
});
return clone;
} catch {
return json;
}
}
@injectable()
export class CustomService {
@inject(FreeLayoutPluginContext) ctx!: FreeLayoutPluginContext;
@ -52,7 +70,9 @@ export class CustomService {
}
const json = this.document.toJSON() as any;
const yaml = stringifyFlowDoc(json);
const design_json = JSON.stringify(json);
// 使用转换后的 design_json以便后端将 code 节点识别为 javascript 并选择 script_js 执行器
const designForBackend = transformDesignJsonForBackend(json);
const design_json = JSON.stringify(designForBackend);
const { data } = await api.put<ApiResp<{ saved: boolean }>>(`/flows/${id}`, { yaml, design_json });
if (data?.code === 0) {
if (!silent) Toast.success(I18n.t('Saved'));