middleground_code_v2/src/views/LinkUp/compoment/addDialogChunk.vue

2960 lines
83 KiB
Vue
Raw Normal View History

2025-05-30 10:04:55 +08:00
<template>
<div class="addDialogChunk">
<base-right-dialog
@handleClose="examineHandleClose"
:dialogVisible="examineOperateDialog"
size="100%"
:appendBody="true"
:loading="sceneLoading"
:footerShow="true"
:submitShow="true"
@handleConfirmClick="handleConfirmClick"
:submitTitle="'发布'"
:submitIcon="'el-icon-upload2'"
:title="sceneName"
>
<template #operateTitle>
<img
style="width: 15px; height: 15px; margin-left: 10px; cursor: pointer"
src="../images/edit.svg"
@click="editSceneName"
/>
</template>
2025-05-30 19:59:25 +08:00
<div
class="rightDialogClass_main drawContent"
v-if="drawShowList.length > 0"
style="background: #fff"
>
<!-- 步骤画布 -->
<div class="drawCanvas" v-show="!representation">
2025-05-30 10:04:55 +08:00
<div
class="drawItem"
v-for="(ele, index) in drawShowList"
:key="ele.value"
>
<div class="title">{{ ele.title }}</div>
<div
@click="selectDrawItem(index)"
class="drawBox"
:class="{ active: index == drawSelectIndex }"
>
<template v-if="index == 0">
<div class="img">
<img src="../images/pingtai.svg" v-if="triggerMode == 2" />
<img
src="../images/naozhong.svg"
v-else-if="triggerMode == 1"
/>
<img src="../images/api.svg" v-else-if="triggerMode == 4" />
<img src="../images/point.svg" v-else-if="triggerMode == 3" />
</div>
<div style="width: calc(100% - 60px)">
<div class="actionName">
{{ index + 1 + "." + ele.actionName }}
</div>
<div
class="content DescribeContent"
v-if="!ele.options.taskType"
>
{{ ele.content }}
</div>
<div class="content" v-else>
{{ getTimeDivide(ele.options.taskType) }}
</div>
</div>
</template>
<template v-else>
<div
:class="
ele.options && ele.options.iconUrl ? 'iconStyle' : 'img'
"
>
<img
v-if="ele.options && ele.options.iconUrl"
:src="
ele.options.iconUrl
? ele.options.iconUrl.split('/wwwroot')[1]
: ''
"
/>
<img v-else src="../images/huaO.svg" />
</div>
<div style="width: calc(100% - 60px)">
<div class="actionName" v-if="!ele.options.appName">
{{ index + 1 + "." + ele.actionName }}
</div>
<div
class="actionName"
style="display: flex; align-items: center"
v-else
>
{{ index + 1 + "." + ele.options.appName }}
</div>
<div
class="content DescribeContent"
v-if="ele.options.stepDescribe"
2025-05-30 10:04:55 +08:00
>
{{ ele.options.stepDescribe }}
2025-05-30 10:04:55 +08:00
</div>
<div class="content" v-else>{{ ele.content }}</div>
</div>
</template>
<div
@click.stop="delDrawItem(ele, index)"
v-if="index != 0"
class="del"
>
<img src="../images/close.svg" />
</div>
</div>
<div class="line-wrapper" v-if="index != drawShowList.length - 1">
<div class="line"></div>
<img
src="../images/addBtn.svg"
@click.stop="insertDrawItem(ele, index)"
/>
</div>
</div>
<div @click="addDrwaItem" class="drawItem drawItemBtn">
添加新步骤
</div>
</div>
<!-- 动态内容及其表达式 -->
<div
class="drawCanvas"
style="align-items: flex-start; padding: 0"
v-show="representation"
>
<div class="repContainer">
<el-tabs v-model="representationActiveName">
<el-tab-pane label="动态内容" name="first"></el-tab-pane>
<el-tab-pane label="表达式" name="second"></el-tab-pane>
</el-tabs>
<div class="closeRepContainer" @click="closeRepContainer">
<i class="el-icon-close"></i> 关闭
</div>
<div class="treeNodeBox" v-if="representationActiveName == 'first'">
<tree-node
v-for="(value, key) in treeData"
:key="key"
:node-key="key"
:node-value="value"
:path="key"
:selected-node="selectedNode"
@node-selected="handleNodeSelected"
/>
</div>
<div
class="treeNodeBox"
v-if="representationActiveName == 'second'"
>
<el-tabs tab-position="left" style="height: 100%">
<el-tab-pane
v-for="(item, index) in representationData"
:label="item.label"
>
<template v-for="(row, rowIndex) in item.children">
<div class="repItem" @click="handleTagSelected(row.label)">
<el-tag size="small">
{{ row.label }}
</el-tag>
<span class="repItemValue">{{ row.value }}</span>
</div>
</template>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
2025-05-30 10:04:55 +08:00
<!-- 操作区 -->
2025-05-30 19:59:25 +08:00
<!-- 第一步的时候单独处理 -->
2025-05-30 10:04:55 +08:00
<div class="drawAction" v-if="drawSelectIndex == 0">
<el-tabs v-model="activeTabName">
<el-tab-pane label="1. 选择操作" name="选择操作">
<div
class="currentDrawBox"
style="padding: 10px"
v-if="drawShowList.length > 0"
>
<div class="drawimg">
<img src="../images/pingtai.svg" v-if="triggerMode == 2" />
<img
src="../images/naozhong.svg"
v-else-if="triggerMode == 1"
/>
<img src="../images/api.svg" v-else-if="triggerMode == 4" />
<img src="../images/point.svg" v-else-if="triggerMode == 3" />
</div>
<div style="width: calc(100% - 60px)">
<div class="actionName">
{{ 1 + "." + drawShowList[0].actionName }}
</div>
<div class="content" v-if="!drawShowList[0].options.taskType">
{{ drawShowList[0].content }}
</div>
<div class="content" v-else>
{{ getTimeDivide(drawShowList[0].options.taskType) }}
</div>
</div>
</div>
<div class="timeWrap">
<template v-for="(item, index) in timeDivide">
<div
@click="handleTimeItem(item, index)"
:class="{ activTimeItem: activTimeIndex == item.id }"
class="timeItem"
>
<div class="imgBox">
<img src="../images/shandian.svg" />
</div>
<div>
<div class="title">{{ item.title }}</div>
<div class="content">{{ item.content }}</div>
</div>
</div>
</template>
</div>
</el-tab-pane>
<el-tab-pane
label="2. 配置"
name="配置"
:disabled="!this.activTimeIndex"
>
2025-05-30 10:04:55 +08:00
<div
class="currentDrawBox"
style="padding: 10px"
v-if="drawShowList.length > 0"
>
<div class="drawimg">
<img src="../images/pingtai.svg" v-if="triggerMode == 2" />
<img
src="../images/naozhong.svg"
v-else-if="triggerMode == 1"
/>
<img src="../images/api.svg" v-else-if="triggerMode == 4" />
<img src="../images/point.svg" v-else-if="triggerMode == 3" />
</div>
<div style="width: calc(100% - 60px)">
<div class="actionName">
{{ 1 + "." + drawShowList[0].actionName }}
</div>
<div class="content" v-if="!drawShowList[0].options.taskType">
{{ drawShowList[0].content }}
</div>
<div class="content" v-else>
{{ getTimeDivide(drawShowList[0].options.taskType) }}
</div>
</div>
</div>
<div class="crontabBox">
<div class="crontabBoxTitle">cron表达式</div>
<FishCrontab
ref="fishCrontab"
class="crontab"
@fill="crontabFill"
:expression="expression"
:fiveTimes="false"
></FishCrontab>
</div>
</el-tab-pane>
</el-tabs>
</div>
2025-05-30 19:59:25 +08:00
<!-- 其他步骤 -->
<div class="drawAction" v-else v-loading="drawMask">
<el-tabs
v-model="activeOtherTabName"
@tab-click="handleOtherTabClick"
>
<el-tab-pane label="1. 选择应用" name="选择应用">
<!-- 选中的操作步骤 -->
<div class="drawItemBox">
<div class="currentDrawBox" style="width: 100%">
<div class="img">
<img class="appIcon" src="../images/applicotion.svg" />
</div>
<div style="width: calc(100% - 64px)">
<!-- 应用名称 -->
<div class="actionName" v-if="CurrentAppRow.appName">
{{ CurrentAppRow.appName }}
</div>
<!-- 步骤描述 -->
<div class="content" v-if="CurrentAppRow.description">
{{ CurrentAppRow.description }}
</div>
</div>
</div>
</div>
<div class="search">
<el-input
class="input"
v-model="appCodeOrName"
placeholder="请输入"
clearable
style="width: 210px"
@keydown.enter.native="searchApp"
></el-input>
<el-button
type="primary"
style="margin-right: 5px"
size="small"
@click="searchApp"
>查询</el-button
>
</div>
<div
class="app"
style="max-height: calc(-345px + 100vh); overflow: auto"
>
<div class="applist">
<div
v-for="(app, index) in appList"
class="appItem"
@click="hangleAppItem(app, index)"
:class="{ active: appActivIndex == app.id }"
>
<img class="appIcon" src="../images/applicotion.svg" />
<div class="name">{{ app.name }}</div>
</div>
</div>
</div>
<div class="DescribeDataBox">
<span style="font-size: 14px">步骤描述</span>
<el-input
class="input"
v-model="description"
placeholder="请输入步骤描述"
clearable
@blur="changeDescription"
type="textarea"
></el-input>
</div>
</el-tab-pane>
<el-tab-pane
label="2. 选择操作"
name="选择操作"
:disabled="!this.appActivIndex"
>
2025-05-30 19:59:25 +08:00
<!-- 选中的操作步骤 -->
<div class="drawItemBox">
<div class="currentDrawBox" style="width: 100%">
<div class="img">
<img class="appIcon" src="../images/applicotion.svg" />
</div>
<div style="width: calc(100% - 64px)">
<!-- 应用名称 -->
<div class="actionName" v-if="CurrentAppRow.appName">
{{ CurrentAppRow.appName }}
</div>
<!-- 步骤描述 -->
<div class="content" v-if="CurrentAppRow.description">
{{ CurrentAppRow.description }}
</div>
</div>
</div>
</div>
<div class="search">
<el-input
class="input"
v-model="operateCodeOrName"
placeholder="请输入"
style="width: 210px"
clearable
@keydown.enter.native="searchApiPlugin"
></el-input>
<el-button type="primary" size="small" @click="searchApiPlugin"
>查询</el-button
>
</div>
<el-radio-group
v-model="activeApiPliginTabName"
@change="ApiPliginTabChangeEvent"
>
<el-radio-button label="API接口">API接口</el-radio-button>
<el-radio-button label="插件">插件</el-radio-button>
</el-radio-group>
<div class="ApiPliginBox app">
<div
class="applist"
v-show="activeApiPliginTabName == 'API接口'"
>
<div
v-for="(item, index) in apiList"
class="appItem"
@click="hangleApiClickEvent(item, index)"
:class="{ active: apiIdActiv == item.id }"
2025-05-30 19:59:25 +08:00
>
<div class="imgBox">
<img src="../images/shandian.svg" />
</div>
<div>
<div class="name">{{ item.apiName }}</div>
<div class="name">{{ item.apiPath }}</div>
</div>
</div>
</div>
<div class="applist" v-show="activeApiPliginTabName == '插件'">
<div
v-for="(item, index) in pluginList"
class="appItem"
@click="hanglePluginClickEvent(item, index)"
:class="{ active: pluginActiv == item.id }"
2025-05-30 19:59:25 +08:00
>
<div class="imgBox">
<img src="../images/shandian.svg" />
</div>
<div>
<div class="name">{{ item.plugName }}</div>
<div class="name">{{ item.plugDescribe }}</div>
</div>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane
label="3. 选择账号"
name="选择账号"
:disabled="
!this.appActivIndex || !(this.apiIdActiv || this.pluginActiv)
"
>
2025-05-30 19:59:25 +08:00
<!-- 选中的操作步骤 -->
<div class="drawItemBox">
<div class="currentDrawBox" style="width: 100%">
<div class="img">
<img class="appIcon" src="../images/applicotion.svg" />
</div>
<div style="width: calc(100% - 64px)">
<!-- 应用名称 -->
<div class="actionName" v-if="CurrentAppRow.appName">
{{ CurrentAppRow.appName }}
</div>
<!-- 步骤描述 -->
<div class="content" v-if="CurrentAppRow.description">
{{ CurrentAppRow.description }}
</div>
</div>
</div>
</div>
<div class="userList">
<div @click="addAccount" class="userItem userBtn">
<i
class="el-icon-plus"
style="font-size: 12px; margin-right: 5px"
></i>
添加账户
</div>
<div class="userContentBox">
<div
v-for="(item, index) in userList"
class="userItem"
style="justify-content: flex-start"
2025-05-30 19:59:25 +08:00
@click="handleUserClickEvent(item, index)"
:class="{ active: userActivId == item.id }"
>
<div class="status">
<i
class="el-icon-success"
style="color: #7ec050"
v-if="item.status == 1"
></i>
<i
class="el-icon-error"
style="color: #f56c6c"
v-else-if="item.status == 0"
></i>
<i
class="el-icon-remove"
style="color: #919398"
v-else
></i>
</div>
<div>
<div class="userName">{{ item.name }}</div>
<div class="content" style="margin-top: 5px">
最新编辑时间{{ item.modify_time }}
</div>
</div>
2025-05-30 19:59:25 +08:00
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane
label="4. 配置"
name="配置应用"
:disabled="
!this.appActivIndex ||
!(this.apiIdActiv || this.pluginActiv) ||
!this.userActivId
"
>
<!-- 选中的操作步骤 -->
<div class="drawItemBox">
<div class="currentDrawBox" style="width: 100%">
<div class="img">
<img class="appIcon" src="../images/applicotion.svg" />
</div>
<div style="width: calc(100% - 64px)">
<!-- 应用名称 -->
<div class="actionName" v-if="CurrentAppRow.appName">
{{ CurrentAppRow.appName }}
</div>
<!-- 步骤描述 -->
<div class="content" v-if="CurrentAppRow.description">
{{ CurrentAppRow.description }}
</div>
</div>
</div>
</div>
<div class="outsideContent">
<!-- 选择表 -->
<el-form
ref="outsideForm"
:model="outsideFormData"
:rules="outsideRules"
label-width="80px"
label-position="top"
style="margin-bottom: 10px"
>
<el-form-item label="选择表">
<el-select
v-model="outsideFormData.tableName"
placeholder="请选择表"
@change="outsideSelectChange"
style="width: 100%"
>
<el-option
v-for="(el, index) in outsideOptions"
:key="index"
:label="el.table_name"
:value="el.table_name"
></el-option>
</el-select>
</el-form-item>
</el-form>
<!-- 表字段 -->
<el-collapse
v-model="activeNames"
v-if="outsideColumns.length > 0"
>
<el-collapse-item title="查询条件配置" name="1">
<el-form
ref="ColumnsForm"
:model="ColumnsFormData"
:rules="ColumnsRules"
label-width="80px"
label-position="top"
style="margin: 0 10px"
>
<div ref="editorContainer">
<div
v-for="(row, rowIndex) in outsideColumns"
:key="rowIndex"
>
<el-form-item :label="row.column_name">
<div class="editor-container">
<div
class="content-editor"
:ref="
'contentEditor' + row.column_name + rowIndex
"
contenteditable="true"
@keydown="
handleKeyDown(
$event,
row.column_name,
rowIndex
)
"
@input="
handleInput($event, row.column_name, rowIndex)
"
@click="
handleEditorClick(
$event,
row.column_name,
rowIndex
)
"
@paste="
handlePaste($event, row.column_name, rowIndex)
"
@focus="
handleEditorFocus(
$event,
row.column_name,
rowIndex
)
"
@blur="
handleEditorBlur(
$event,
row.column_name,
rowIndex
)
"
></div>
<div
class="editorIcon"
@click.stop="
handleAddNodeToEditor(
row.column_name,
rowIndex
)
"
>
<i class="el-icon-circle-plus"></i>
</div>
<div
class="clearIcon"
@click.stop="
handleClearNodeToEditor(
row.column_name,
rowIndex
)
"
>
<i class="el-icon-circle-close"></i>
</div>
</div>
</el-form-item>
</div>
</div>
</el-form>
</el-collapse-item>
</el-collapse>
</div>
</el-tab-pane>
2025-05-30 19:59:25 +08:00
</el-tabs>
</div>
2025-05-30 10:04:55 +08:00
</div>
</base-right-dialog>
<editSence ref="editSence" @handleConfirmClick="updateTitle"></editSence>
2025-05-30 19:59:25 +08:00
<addAccount
ref="addAccount"
@handleConfirmClick="addAccountConfirmClick"
></addAccount>
2025-05-30 10:04:55 +08:00
</div>
</template>
<script>
// 导入编辑场景组件
import baseRightDialog from "@/components/base/baseRightDialog/index.vue";
import editSence from "./editSence.vue";
import { authApi } from "@/api/apis/auth";
// 导入步骤添加和使用的常量
import { stepAdd, stepUse, timeDivide, representationData } from "./constant";
2025-05-30 10:04:55 +08:00
// 导入 cron 表达式组件
import FishCrontab from "fish-crontab";
2025-05-30 19:59:25 +08:00
// 导入添加账号组件
import addAccount from "./addAccount.vue";
import baseForm from "@/components/base/baseNewForm";
import treeNode from "./TreeNode";
import IconsDialog from "../../tool/build/IconsDialog.vue";
2025-05-30 10:04:55 +08:00
export default {
components: {
editSence,
baseRightDialog,
FishCrontab,
2025-05-30 19:59:25 +08:00
addAccount,
baseForm,
treeNode,
IconsDialog,
2025-05-30 10:04:55 +08:00
},
data() {
return {
examineOperateDialog: false, //判断是都打开弹窗
sceneLoading: false, //遮照
sceneName: "", //场景名称
sceneID: "", //场景ID
// 画布列表,对应右侧操作区域
drawShowList: [],
drawSelectIndex: 0, // 当前选中的画布索引
2025-05-30 19:59:25 +08:00
//当步骤为1时
timeDivide: timeDivide,
activeTabName: "选择操作", // 显示的操作栏
activTimeIndex: "", // 当前选择操作的类型
expression: "", //cron表达式
// 其他步骤
activeOtherTabName: "",
CurrentAppRow: {}, // 当前选择的应用数据
appCodeOrName: "", //应用搜索条件
drawMask: false,
2025-05-30 10:04:55 +08:00
// 应用列表
appList: [],
appCodeOrName: "", // 应用搜索关键字
appActivIndex: "", // 当前选中的应用 ID
2025-05-30 19:59:25 +08:00
description: "", //应用描述
operateCodeOrName: "", //api 或者 插件的搜索
activeApiPliginTabName: "API接口", // 当前操作是 API 还是插件
// API 插件列表
pluginList: [],
// API 接口列表
apiList: [],
apiIdActiv: "", // 选择的 API ID
pluginActiv: "", // 选择的插件 ID
// 用户列表
userList: [],
userActivId: "", // 选择的用户 ID
outsideFormRow: [
{
elCol: [
{
label: "选择表",
prop: "tableName",
tag: "elSelect",
span: 24,
},
],
},
],
outsideRules: {
tableName: [
{
required: true,
message: "请选择表",
trigger: "change, blur",
},
],
},
outsideFormData: {
tableName: "",
},
outsideOptions: [],
outsideColumns: [], //表字段
activeNames: "1", //展开
ColumnsFormData: {},
ColumnsRules: {},
representation: false,
selectedNode: "",
representationActiveName: "first",
treeData: {
user: {
id: 1001,
profile: {
name: "李四",
email: "lisi@example.com",
settings: {
theme: "dark",
notifications: {
email: true,
sms: false,
push: true,
},
},
},
permissions: ["read", "write", "admin"],
metadata: {
created_at: "2023-01-15T10:30:00Z",
updated_at: "2024-03-20T15:45:30Z",
version: 2.1,
},
},
config: {
database: {
host: "localhost",
port: 5432,
ssl: false,
},
features: {
experimental: ["ai_assist", "real_time_sync"],
stable: ["user_management", "data_export"],
},
},
},
currenrActiveNodeRef: "",
lastSelectedTokenIndex: -1,
tokenCounter: 0,
representationData: representationData,
2025-05-30 10:04:55 +08:00
};
},
methods: {
/**
* 打开弹窗
* @param {string} sceneID 场景 ID
* @param {string} sceneName 场景名称
* @param {string} type 操作类型
* @param {number} triggerMode 触发模式
*/
openDialog(sceneID, sceneName, type, triggerMode) {
// 更新场景 ID
this.sceneID = sceneID;
// 更新场景类型
this.sceneType = type;
// 更新场景名称
this.sceneName = sceneName;
// 更新触发模式
this.triggerMode = triggerMode;
// 显示审核操作弹窗
this.examineOperateDialog = true;
// 显示场景加载状态
this.sceneLoading = true;
// 5 秒后隐藏场景加载状态
setTimeout(() => {
this.sceneLoading = false;
}, 5000);
if (type === "add") {
// 新增时生成第一步
let params = {
flowId: this.sceneID,
step: 1,
stepType: 2,
};
this.SaveSceneStepData(params, "first");
}
2025-05-30 19:59:25 +08:00
if (type === "edit") {
// 编辑时获取场景步骤列表
this.GetSceneStepList();
}
},
/**
* 获取场景步骤列表
*/
async GetSceneStepList() {
// 清空画布列表
this.drawShowList = [];
// 获取场景步骤列表的参数
let params = {
flowId: this.sceneID,
};
let res = await authApi(
"sysFlowStepService",
"",
"queryList",
"",
params
);
// 隐藏场景加载状态
this.sceneLoading = false;
if (res.status == "200") {
if (res.attribute.length > 0) {
res.attribute.forEach((item, index) => {
if (index === 0) {
let obj = {
...stepAdd,
options: {
...item,
stepID: item.id,
taskType: item.apiId,
2025-05-30 19:59:25 +08:00
},
};
if (this.triggerMode === 2) {
obj.actionName = "定时触发";
} else if (this.triggerMode === 1) {
obj.actionName = "应用程序触发";
} else if (this.triggerMode === 4) {
obj.actionName = "Webhook触发";
} else if (this.triggerMode === 3) {
obj.actionName = "手动触发";
}
// 将第一个步骤添加到画布列表
this.drawShowList.push(obj);
} else {
// 将其他步骤添加到画布列表
this.drawShowList.push({
...stepUse,
options: {
...item,
stepID: item.id,
stepDescribe: item.apiName || item.plugName,
2025-05-30 19:59:25 +08:00
},
});
}
});
// 在下一个 DOM 更新周期后执行操作
this.$nextTick(() => {
// 选中第一个步骤
this.drawSelectIndex = 0;
this.selectDrawItem(this.drawSelectIndex);
});
} else {
// 新增时生成第一步
let params = {
flowId: this.sceneID,
step: 1,
stepType: 2,
};
this.SaveSceneStepData(params, "first");
}
}
2025-05-30 10:04:55 +08:00
},
/**
* 新增实时修改场景步骤基本信息
* @param {Object} params 请求参数
* @param {string} type 操作类型
2025-05-30 19:59:25 +08:00
* @param {string} insertIndex 用于插入
2025-05-30 10:04:55 +08:00
*/
async SaveSceneStepData(params, type, insertIndex) {
let res = await authApi(
"sysFlowStepService",
"",
2025-05-30 19:59:25 +08:00
"saveOrUpdateFlowStep",
2025-05-30 10:04:55 +08:00
"",
params
);
// 隐藏场景加载状态
this.sceneLoading = false;
if (res.status == "200") {
2025-05-30 19:59:25 +08:00
// 第一次打开弹窗
2025-05-30 10:04:55 +08:00
if (type === "first") {
let obj = [
{
...stepAdd,
options: {
// 步骤 id
stepID: res.attribute.id,
step: 1,
stepType: 2, // 步骤类型1、触发方式 2、应用 3、 数据集 4、条件
},
},
];
if (this.triggerMode === 2) {
2025-05-30 19:59:25 +08:00
obj[0].actionName = "定时触发";
2025-05-30 10:04:55 +08:00
} else if (this.triggerMode === 1) {
2025-05-30 19:59:25 +08:00
obj[0].actionName = "应用程序触发";
2025-05-30 10:04:55 +08:00
} else if (this.triggerMode === 4) {
2025-05-30 19:59:25 +08:00
obj[0].actionName = "Webhook触发";
2025-05-30 10:04:55 +08:00
} else if (this.triggerMode === 3) {
obj[0].actionName = "手动触发";
}
// 更新画布列表
this.drawShowList = obj;
// 在下一个 DOM 更新周期后执行操作
this.$nextTick(() => {
// 选中第一个步骤
this.drawSelectIndex = 0;
this.selectDrawItem(this.drawSelectIndex);
// 获取应用列表
this.getAppList();
});
}
// 手动新增时
if (type === "add") {
// 将新步骤添加到画布列表
this.drawShowList.push({
...stepUse,
options: {
// 步骤 id
stepID: res.attribute.id,
},
});
// 在下一个 DOM 更新周期后执行操作
this.$nextTick(() => {
// 选中当前新增的步骤
this.drawSelectIndex = this.drawShowList.length - 1;
this.selectDrawItem(this.drawSelectIndex);
});
}
2025-05-30 19:59:25 +08:00
// 插入时
2025-05-30 10:04:55 +08:00
if (type == "insert") {
this.drawShowList.splice(insertIndex + 1, 0, {
...stepUse,
options: {
// 步骤 id
stepID: res.attribute.id,
},
});
// 在下一个 DOM 更新周期后执行操作
this.$nextTick(() => {
// 选中当前新增的步骤
this.drawSelectIndex = insertIndex + 1;
this.selectDrawItem(this.drawSelectIndex);
});
}
2025-05-30 19:59:25 +08:00
if (type === "time") {
this.activeTabName = "配置";
}
// cron表达式保存后
if (type === "expression") {
if (this.drawShowList.length === 1) {
// 自动添加一个步骤
this.addDrwaItem();
} else {
// 在下一个 DOM 更新周期后执行操作
this.$nextTick(() => {
// 选中第二个步骤
this.drawSelectIndex = 1;
this.selectDrawItem(this.drawSelectIndex);
});
}
}
2025-05-30 10:04:55 +08:00
}
},
/**
* 手动新增场景步骤
*/
2025-05-30 19:59:25 +08:00
addDrwaItem() {
2025-05-30 10:04:55 +08:00
// 新增步骤的参数
let params = {
flowId: this.sceneID,
step: this.drawShowList.length + 1,
stepType: 2,
};
// 保存场景步骤数据
this.SaveSceneStepData(params, "add");
},
/**
* 切换步骤时
* @param {number} index 步骤索引
*/
async selectDrawItem(index) {
2025-05-30 10:04:55 +08:00
// 更新当前选中的步骤索引
this.drawSelectIndex = index;
2025-05-30 19:59:25 +08:00
// 先清空
this.pluginActiv = "";
this.apiIDActiv = "";
this.userActivId = "";
this.CurrentAppRow = {};
2025-05-30 19:59:25 +08:00
if (index === 0) {
this.activeTabName = "选择操作";
} else {
this.activeOtherTabName = "选择应用";
// 清空应用搜索关键字
this.appCodeOrName = "";
// 获取应用列表
await this.getAppList();
2025-05-30 19:59:25 +08:00
// 获取场景步骤数据
await this.GetSceneStepData();
2025-05-30 19:59:25 +08:00
}
2025-05-30 10:04:55 +08:00
},
2025-05-30 19:59:25 +08:00
/**
* 获取场景步骤数据
*/
async GetSceneStepData() {
// 获取场景步骤数据的参数
let params = {
id: this.drawShowList[this.drawSelectIndex].options.stepID,
};
let res = await authApi(
"sysFlowStepService",
"",
"getFlowStep",
"",
params
);
if (res.status == "200") {
let obj = {
...this.drawShowList[this.drawSelectIndex].options,
...res.attribute,
};
// 更新当前步骤的选项
obj.stepDescribe = obj.apiName || obj.plugName;
2025-05-30 19:59:25 +08:00
this.drawShowList[this.drawSelectIndex].options = obj;
// 更新当前选择的应用步骤描述
this.CurrentAppRow.description = obj.stepDescribe;
2025-05-30 19:59:25 +08:00
if (this.drawSelectIndex == 0) {
this.expression = obj.taskCorn;
this.activTimeIndex = obj.apiId;
} else {
this.activeApiPliginTabName = obj.actionType || "API接口";
2025-05-30 10:04:55 +08:00
2025-05-30 19:59:25 +08:00
// 清空 API 或插件搜索关键字
this.operateCodeOrName = "";
// 清空 API 列表
this.apiList = [];
// 清空插件列表
this.pluginList = [];
// 清空用户列表
this.userList = [];
if (this.drawShowList[this.drawSelectIndex].options.appId) {
// 更新当前选中的应用 ID
this.appActivIndex =
this.drawShowList[this.drawSelectIndex].options.appId;
// 更新当前选择的应用名称
this.CurrentAppRow.appName =
this.drawShowList[this.drawSelectIndex].options.appName;
// 获取当前步骤的 api、插件、账号
await this.GetApiList(this.appActivIndex);
2025-05-30 19:59:25 +08:00
// this.getPluginList(this.appActivIndex);
await this.GetAccountListAPI(this.appActivIndex);
2025-05-30 19:59:25 +08:00
} else {
// 重置当前选中的应用 ID
this.appActivIndex = "";
// 重置当前选择的应用数据
this.CurrentAppRow = {};
}
this.$nextTick(() => {
if (this.drawShowList[this.drawSelectIndex].options.description) {
// 更新步骤描述
this.description =
this.drawShowList[this.drawSelectIndex].options.description;
} else {
// 重置步骤描述
this.description = "";
}
if (this.drawShowList[this.drawSelectIndex].options.apiId) {
// 更新选择的 API ID
this.apiIdActiv =
this.drawShowList[this.drawSelectIndex].options.apiId;
} else {
// 重置选择的 API ID
this.apiIdActiv = "";
}
// 插件
if (this.drawShowList[this.drawSelectIndex].options.plugId) {
// 更新选择的插件 ID
this.pluginActiv =
this.drawShowList[this.drawSelectIndex].options.plugId;
} else {
// 重置选择的插件 ID
this.pluginActiv = "";
}
// 账号
if (this.drawShowList[this.drawSelectIndex].options.step_acc_id) {
// 更新选择的用户 ID
this.userActivId =
this.drawShowList[this.drawSelectIndex].options.step_acc_id;
// 更新当前选择的应用账号名称
this.CurrentAppRow.step_acc_name =
this.drawShowList[this.drawSelectIndex].options.step_acc_name;
} else {
// 重置选择的用户 ID
this.userActivId = "";
// 重置当前选择的应用账号名称
this.CurrentAppRow.step_acc_name = "";
}
});
2025-05-30 19:59:25 +08:00
}
}
},
2025-05-30 10:04:55 +08:00
/**
* 删除场景步骤
* @param {Object} ele 步骤数据
* @param {number} index 步骤索引
*/
delDrawItem(ele, index) {
// 获取步骤 ID
let stepID = ele.options.stepID || null;
if (!stepID) {
this.$vmNews("删除失败步骤ID丢失请联系管理员处理");
return;
}
// 弹出确认删除对话框
this.$confirm("确认删除吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
if (stepID) {
let res = await authApi(
"sysFlowStepService",
"",
"saveFlowStep",
"",
{
id: stepID,
}
);
if (res.status == "200") {
// 提示删除成功
this.$vmNews("删除成功", "success");
// 从画布列表中删除该步骤
this.drawShowList.splice(index, 1);
if (this.drawSelectIndex === index) {
// 选中第一个步骤
this.drawSelectIndex = 0;
this.selectDrawItem(this.drawSelectIndex);
} else if (this.drawSelectIndex >= this.drawShowList.length) {
// 选中最后一个步骤
this.drawSelectIndex = this.drawShowList.length - 1;
this.selectDrawItem(this.drawSelectIndex);
}
}
} else {
// 提示删除成功
this.$vmNews("删除成功", "success");
// 从画布列表中删除该步骤
this.drawShowList.splice(index, 1);
if (this.drawSelectIndex === index) {
// 选中第一个步骤
this.drawSelectIndex = 0;
this.selectDrawItem(this.drawSelectIndex);
} else if (this.drawSelectIndex >= this.drawShowList.length) {
// 选中最后一个步骤
this.drawSelectIndex = this.drawShowList.length - 1;
this.selectDrawItem(this.drawSelectIndex);
}
}
})
.catch(() => {});
},
/**
* 插入场景步骤
* @param {any} value 标签页值
*/
insertDrawItem(ele, index) {
let params = {
flowId: this.sceneID,
step: index + 1,
stepType: 2,
};
// 保存场景步骤数据
this.SaveSceneStepData(params, "insert", index);
},
2025-05-30 19:59:25 +08:00
// ----------------------------------------------步骤一相关操作
/**
* 处理时间项点击事件
* @param {Object} item 时间项数据
* @param {number} index 时间项索引
*/
handleTimeItem(item, index) {
// 更新当前选择操作的类型
this.activTimeIndex = item.id;
// 更新当前步骤的任务类型
this.drawShowList[this.drawSelectIndex].options.taskType = item.id;
let params = {
flowId: this.sceneID,
id: this.drawShowList[this.drawSelectIndex].options.stepID,
apiId: item.id,
apiName: this.getTimeDivide(item.id),
};
// 保存场景步骤数据
this.SaveSceneStepData(params, "time");
},
2025-05-30 10:04:55 +08:00
/**
* cron 表达式点击确定时时间插件回调
* @param {string} value cron 表达式的值
*/
crontabFill(value) {
// 更新 cron 表达式
this.expression = value;
// 更新当前步骤的任务值
this.drawShowList[this.drawSelectIndex].options.taskValue = value;
2025-05-30 19:59:25 +08:00
let params = {
flowId: this.sceneID,
id: this.drawShowList[this.drawSelectIndex].options.stepID,
taskCorn: value,
};
// 保存场景步骤数据
this.SaveSceneStepData(params, "expression");
},
// ----------------------------------------------其他步骤--相关操作
/**
* 处理 其他步骤的tab点击事件
* @param {any} value 标签页值
*/
handleOtherTabClick(value) {},
/**
* 应用搜索
*/
searchApp() {
// 显示搜索遮罩
this.drawMask = true;
// 5 秒后隐藏搜索遮罩
setTimeout(() => {
this.drawMask = false;
}, 5000);
// 获取应用列表
this.getAppList();
2025-05-30 10:04:55 +08:00
},
/**
* 获取应用列表
*/
async getAppList() {
// 获取应用列表的参数
let params = {
2025-05-30 19:59:25 +08:00
name: this.appCodeOrName,
2025-05-30 10:04:55 +08:00
};
// 调用获取应用列表的 API
let res = await authApi(
"sysApplicationService",
"",
"queryEntity",
"",
params
);
// 隐藏搜索遮罩
this.drawMask = false;
if (res.status == "200") {
// 更新应用列表
this.appList = res.attribute;
}
},
2025-05-30 19:59:25 +08:00
/**
* 步骤描述变化时保存数据
*/
changeDescription() {
// 更新当前步骤的步骤描述
this.drawShowList[this.drawSelectIndex].options.description =
this.description;
let params = {
flowId: this.sceneID,
id: this.drawShowList[this.drawSelectIndex].options.stepID,
description: this.description,
};
// 保存场景步骤数据
this.SaveSceneStepData(params);
},
/**
* 选择应用时触发的方法
* @param {Object} row 应用数据
* @param {number} index 应用索引
*/
async hangleAppItem(row, index) {
if (!this.appActivIndex) {
// 保存应用信息
this.matchAppData(row);
} else if (this.appActivIndex !== row.appId) {
this.$confirm(
"切换应用会清空操作、账号及配置内的所有数据,确定切换吗?",
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
)
.then(async () => {
// 重置当前选择的应用插件名称
this.CurrentAppRow.plugName = "";
// 重置当前选择的应用账号名称
this.CurrentAppRow.step_acc_name = "";
this.CurrentAppRow.description = "";
// 重置当前步骤的资源类型
this.drawShowList[this.drawSelectIndex].options.actionType = "";
// 重置当前步骤的 API ID
this.drawShowList[this.drawSelectIndex].options.apiId = "";
this.drawShowList[this.drawSelectIndex].options.apiName = "";
// 重置当前步骤的插件 ID
this.drawShowList[this.drawSelectIndex].options.plugId = "";
// 重置当前步骤的插件名称
this.drawShowList[this.drawSelectIndex].options.plugName = "";
// 重置当前步骤的账号 ID
this.drawShowList[this.drawSelectIndex].options.step_acc_id = "";
// 重置当前步骤的账号名称
this.drawShowList[this.drawSelectIndex].options.step_acc_name = "";
this.drawShowList[this.drawSelectIndex].options.stepDescribe = "";
this.apiIdActiv = "";
this.apiList = [];
this.pluginActiv = "";
this.pluginList = [];
this.userActivId = "";
this.userList = [];
// 保存应用信息
this.matchAppData(row);
// 清空步骤配置
await this.clearFlowStep();
})
.catch(() => {});
2025-05-30 19:59:25 +08:00
}
},
async matchAppData(row) {
// 更新当前选中的应用 ID
this.appActivIndex = row.id;
// 更新当前选择的应用 ID
this.CurrentAppRow.appId = row.id;
// 更新当前选择的应用名称
this.CurrentAppRow.appName = row.name;
// 更新当前步骤的应用 ID
this.drawShowList[this.drawSelectIndex].options.appId = row.id;
// 更新当前步骤的应用名称
this.drawShowList[this.drawSelectIndex].options.appName = row.name;
// 切换到选择操作标签页
this.activeOtherTabName = "选择操作";
// 切换到 API 接口标签页
this.activeApiPliginTabName = "API接口";
// 清空 API 或插件搜索关键字
this.operateCodeOrName = "";
// 更新当前步骤的资源类型
this.drawShowList[this.drawSelectIndex].options.actionType = "API接口";
// 实时保存配置
let params = {
flowId: this.sceneID,
id: this.drawShowList[this.drawSelectIndex].options.stepID,
appId: row.id,
appName: row.name,
actionType: "",
plugId: "",
plugName: "",
apiId: "",
apiName: "",
step_acc_name: "",
step_acc_id: "",
};
// 保存场景步骤数据
await this.SaveSceneStepData(params);
// 获取 API 列表
await this.GetApiList(row.id);
// 获取插件列表
// await this.getPluginList(row.id);
// // 获取用户列表
await this.GetAccountListAPI(row.id);
},
async clearFlowStep() {
let params = {
id: this.drawShowList[this.drawSelectIndex].options.stepID,
};
let res = await authApi(
"sysFlowStepService",
"",
"clearFlowStep",
"",
params
);
},
2025-05-30 19:59:25 +08:00
/**
* API 或者插件的搜索
*/
searchApiPlugin() {
// 检查是否选择了应用
if (!this.drawShowList[this.drawSelectIndex].options.appId) {
this.$vmNews("请先选择应用");
return;
}
if (this.activeApiPliginTabName === "API接口") {
// 显示搜索遮罩
this.drawMask = true;
// 5 秒后隐藏搜索遮罩
setTimeout(() => {
this.drawMask = false;
}, 5000);
// 获取 API 列表
this.GetApiList(this.drawShowList[this.drawSelectIndex].options.appId);
} else if (this.activeApiPliginTabName === "插件") {
// 显示搜索遮罩
this.drawMask = true;
// 5 秒后隐藏搜索遮罩
setTimeout(() => {
this.drawMask = false;
}, 5000);
// 获取插件列表
this.getPluginList(
this.drawShowList[this.drawSelectIndex].options.appId
);
}
},
/**
* 切换 API 插件标签页
* @param {string} e 标签页值
*/
ApiPliginTabChangeEvent(e) {
// 更新当前操作是 API 还是插件
this.activeApiPliginTabName = e;
},
// 选择API
hangleApiClickEvent(item) {
this.pluginActiv = "";
this.drawShowList[this.drawSelectIndex].options.plugId = "";
this.drawShowList[this.drawSelectIndex].options.plugName = "";
// 更新选择的 API ID
this.apiIdActiv = item.id;
// 更新当前步骤的 API ID
this.drawShowList[this.drawSelectIndex].options.apiId = item.id;
this.drawShowList[this.drawSelectIndex].options.apiName = item.apiName;
// 更新当前选择的应用步骤描述
this.CurrentAppRow.description = item.apiName;
this.drawShowList[this.drawSelectIndex].options.stepDescribe =
item.apiName;
2025-05-30 19:59:25 +08:00
// 更新当前步骤的资源类型
this.drawShowList[this.drawSelectIndex].options.actionType =
this.activeApiPliginTabName;
// 切换到选择账号标签页
this.activeOtherTabName = "选择账号";
let params = {
flowId: this.sceneID,
id: this.drawShowList[this.drawSelectIndex].options.stepID,
actionType: this.activeApiPliginTabName,
plugId: "",
plugName: "",
apiId: item.id,
apiName: item.apiName,
};
// 保存场景步骤数据
this.SaveSceneStepData(params);
},
// 选择插件
hanglePluginClickEvent(item) {
// 更新选择的 API ID
this.apiIdActiv = "";
// 更新当前步骤的 API ID
this.drawShowList[this.drawSelectIndex].options.apiId = "";
this.drawShowList[this.drawSelectIndex].options.apiName = "";
this.pluginActiv = item.id;
this.drawShowList[this.drawSelectIndex].options.plugId = item.id;
this.drawShowList[this.drawSelectIndex].options.plugName = item.plugName;
// 更新当前选择的应用步骤描述
this.CurrentAppRow.description = item.plugName;
this.drawShowList[this.drawSelectIndex].options.stepDescribe =
item.plugName;
2025-05-30 19:59:25 +08:00
// 更新当前步骤的资源类型
this.drawShowList[this.drawSelectIndex].options.actionType =
this.activeApiPliginTabName;
// 切换到选择账号标签页
this.activeOtherTabName = "选择账号";
let params = {
flowId: this.sceneID,
id: this.drawShowList[this.drawSelectIndex].options.stepID,
actionType: this.activeApiPliginTabName,
plugId: item.id,
plugName: item.plugName,
apiId: "",
apiName: "",
};
// 保存场景步骤数据
this.SaveSceneStepData(params);
},
/**
* 选择账号 -- 添加账号
*/
addAccount() {
if (!this.appActivIndex) {
this.$vmNews("请选择应用");
return;
}
// 打开添加账号弹窗
this.$refs.addAccount.openDialog(
this.appActivIndex,
this.sceneID,
this.drawShowList[this.drawSelectIndex].options.stepID
);
2025-05-30 19:59:25 +08:00
},
/**
* 重新加载用户列表
*/
addAccountConfirmClick() {
// 获取用户列表
this.GetAccountListAPI(this.appActivIndex);
},
/**
* 当前选中的用户
* @param {Object} item 用户数据
*/
async handleUserClickEvent(item) {
2025-05-30 19:59:25 +08:00
// 更新选择的用户 ID
this.userActivId = item.id;
// 更新当前选择的应用账号名称
this.CurrentAppRow.step_acc_name = item.name;
// 更新当前步骤的用户 ID
this.drawShowList[this.drawSelectIndex].options.step_acc_id = item.id;
// 更新当前步骤的用户账号名称
this.drawShowList[this.drawSelectIndex].options.step_acc_name = item.name;
this.outsideFormData = {
tableName: "",
};
this.ColumnsFormData = {};
this.outsideOptions = [];
this.outsideColumns = [];
2025-05-30 19:59:25 +08:00
let params = {
flowId: this.sceneID,
id: this.drawShowList[this.drawSelectIndex].options.stepID,
step_acc_name: item.name,
step_acc_id: item.id,
};
// 保存场景步骤数据
await this.SaveSceneStepData(params);
// 切换到配置应用
this.activeOtherTabName = "配置应用";
// 获取表数据
await this.queryTables(item.id);
this.$nextTick(() => {
this.resetForm("outsideForm");
});
},
/**
* 重置表单
* @param {string} formName 表单的ref
*/
resetForm(formName) {
this.$refs[formName].resetFields();
},
/**
* 获取表列表
* @param {string} stepAccountId 账号ID
*/
async queryTables(stepAccountId) {
let params = {
stepAccountId: stepAccountId,
};
let res = await authApi(
"sysFlowDataBaseExtServiceImpl",
"",
"queryTables",
"",
params
);
this.drawMask = false;
if (res.status == "200") {
this.outsideOptions = res.attribute;
}
},
// 切换表时
outsideSelectChange(val) {
if (val) {
this.outsideColumns = [];
this.queryColumns(val);
}
},
// 获取表字段
async queryColumns(tableName) {
let params = {
stepAccountId: this.userActivId, //账户id
tableName: tableName,
};
let res = await authApi(
"sysFlowDataBaseExtServiceImpl",
"",
"queryColumns",
"",
params
);
this.drawMask = false;
if (res.status == "200") {
this.outsideColumns = res.attribute.cloums || [];
this.ColumnsRules = res.attribute.rules || {};
this.$nextTick(() => {
this.ColumnsFormData = {};
this.resetForm("ColumnsForm");
});
}
2025-05-30 19:59:25 +08:00
},
/**
* 获取插件列表
* @param {string} appId 应用 ID
*/
async getPluginList(appId) {
let params = {
appId: appId,
name: this.operateCodeOrName,
};
let res = await authApi(
"sysApplicationApiService",
"",
"queryEntity",
"",
params
);
this.drawMask = false;
if (res.status == "200") {
this.pluginList = res.attribute;
}
},
/**
* 获取 api 列表
* @param {string} appId 应用 ID
*/
async GetApiList(appId) {
let params = {
appId: appId,
name: this.operateCodeOrName,
};
let res = await authApi(
"sysApplicationApiService",
"",
"queryEntity",
"",
params
);
this.drawMask = false;
if (res.status == "200") {
this.apiList = res.attribute;
}
},
/**
* 获取用户列表
* @param {string} appId 应用 ID
*/
async GetAccountListAPI(appId) {
let params = {
appId: appId,
};
let res = await authApi(
"sysApplicationAccountService",
"",
"queryAccountList",
"",
params
);
this.drawMask = false;
if (res.status == "200") {
this.userList = res.attribute;
}
},
2025-05-30 10:04:55 +08:00
/**
* 根据任务类型获取时间划分的标题
* @param {number} taskType 任务类型
* @returns {string} 时间划分的标题
*/
getTimeDivide(taskType) {
if (taskType === 1) {
return "秒级";
} else if (taskType === 2) {
return "分钟级";
} else if (taskType === 3) {
return "小时级";
} else if (taskType === 4) {
return "天级";
}
},
/**
* 编辑场景名称
*/
editSceneName() {
// 编辑场景名称的参数
let row = {
flowId: this.sceneID,
};
// 在下一个 DOM 更新周期后打开编辑场景名称的弹窗
this.$nextTick(() => {
this.$refs.editSence.openDialog(row, "edit");
});
},
/**
* 更新场景名称
* @param {string} sceneID 场景 ID
* @param {Object} res 响应数据
*/
updateTitle(sceneID, res) {
// 更新场景名称
this.sceneName = res.sceneName;
this.title = res.sceneName;
// 更新触发模式
this.triggerMode = res.triggerMode;
if (this.triggerMode === 2) {
2025-05-30 19:59:25 +08:00
this.drawShowList[this.drawSelectIndex].actionName = "定时触发";
2025-05-30 10:04:55 +08:00
} else if (this.triggerMode === 1) {
2025-05-30 19:59:25 +08:00
this.drawShowList[this.drawSelectIndex].actionName = "应用程序触发";
2025-05-30 10:04:55 +08:00
} else if (this.triggerMode === 4) {
2025-05-30 19:59:25 +08:00
this.drawShowList[this.drawSelectIndex].actionName = "Webhook触发";
2025-05-30 10:04:55 +08:00
} else if (this.triggerMode === 3) {
this.drawShowList[this.drawSelectIndex].actionName = "手动触发";
}
},
// 场景步骤---【确定】
handleConfirmClick() {
this.examineHandleClose();
},
// 关闭场景步骤弹窗
examineHandleClose() {
// 触发关闭事件
this.$emit("examineHandleClose");
this.examineOperateDialog = false;
this.sceneName = "";
2025-05-30 19:59:25 +08:00
// 清空画布列表
this.drawShowList = [];
// 重置当前选中的步骤索引
this.drawSelectIndex = "";
// 重置当前选择操作的类型
this.activTimeIndex = "";
// 重置 cron 表达式
this.expression = "";
// 清空应用搜索关键字
this.appCodeOrName = "";
// 重置当前选中的应用 ID
this.appActivIndex = "";
// 清空 API 或插件搜索关键字
this.operateCodeOrName = "";
// 重置当前操作是 API 还是插件
this.activeApiPliginTabName = "API接口";
// 重置选择的 API ID
this.apiIdActiv = "";
// 清空 API 列表
this.apiList = [];
// 重置选择的插件 ID
this.pluginActiv = "";
// 清空插件列表
this.pluginList = [];
// 重置选择的用户 ID
this.userActivId = "";
// 清空用户列表
this.userList = [];
this.representation = false;
},
handleKeyDown(e, columnName, rowIndex) {
const editor = this.$refs["contentEditor" + columnName + rowIndex][0];
if (e.key === "Backspace") {
// 检查是否在光标前有token需要删除
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
// 如果光标在文本开头或者前一个兄弟节点是token
if (range.startOffset === 0) {
const prevSibling = range.startContainer.previousSibling;
if (
prevSibling &&
prevSibling.classList &&
prevSibling.classList.contains("content-token")
) {
e.preventDefault();
prevSibling.remove();
this.$message.success("已删除元素");
return;
}
}
}
}
// 清除token选中状态除了特殊键
if (
![
"Backspace",
"Delete",
"ArrowLeft",
"ArrowRight",
"ArrowUp",
"ArrowDown",
].includes(e.key)
) {
this.clearTokenSelection(columnName, rowIndex);
}
},
// 清除token选中状态
clearTokenSelection(columnName, rowIndex) {
const editor = this.$refs["contentEditor" + columnName + rowIndex][0];
this.lastSelectedTokenIndex = -1;
},
// 处理输入事件
handleInput(e, columnName, rowIndex) {
// 清除token选中状态
this.clearTokenSelection(columnName, rowIndex);
// 强制更新computed属性
this.$forceUpdate();
},
// 处理编辑器点击
handleEditorClick(e, columnName, rowIndex) {
const editor = this.$refs["contentEditor" + columnName + rowIndex][0];
this.currenrActiveNodeRef = "contentEditor" + columnName + rowIndex;
// 如果点击的不是token清除选中状态
if (
!e.target.classList.contains("content-token") &&
!e.target.closest(".content-token")
) {
this.clearTokenSelection(columnName, rowIndex);
}
// 确保编辑器获得焦点
if (e.target === editor || editor.contains(e.target)) {
editor.focus();
}
},
// 处理粘贴事件
handlePaste(e, columnName, rowIndex) {
// 清除token选中状态
this.clearTokenSelection(columnName, rowIndex);
},
// 处理编辑器获得焦点
handleEditorFocus(e, columnName, rowIndex) {
const editor = this.$refs["contentEditor" + columnName + rowIndex][0];
// 如果编辑器为空,设置光标到开始位置
if (this.isEmpty(columnName, rowIndex)) {
setTimeout(() => {
const range = document.createRange();
const selection = window.getSelection();
range.setStart(editor, 0);
range.setEnd(editor, 0);
selection.removeAllRanges();
selection.addRange(range);
}, 0);
}
},
// 判断当前编辑器是否为空
isEmpty(columnName, rowIndex) {
const editor = this.$refs["contentEditor" + columnName + rowIndex][0];
if (!editor) return true;
const content = editor.textContent.trim();
const hasTokens = editor.querySelectorAll(".content-token").length > 0;
return content === "" && !hasTokens;
},
// 处理编辑器失去焦点
handleEditorBlur(e, columnName, rowIndex) {
// 可以在这里处理失去焦点的逻辑
},
handleClearNodeToEditor(columnName, rowIndex) {
const editor = this.$refs["contentEditor" + columnName + rowIndex][0];
if (!editor) return;
let nodeRef = "contentEditor" + columnName + rowIndex;
this.currenrActiveNodeRef = nodeRef;
editor.innerHTML = ""; // 先清空内容
this.$forceUpdate();
this.$nextTick(() => {
// 创建 range 和 selection
const range = document.createRange();
const selection = window.getSelection();
// 将 range 定位到 editor 的最后一个子节点
range.selectNodeContents(editor);
range.collapse(false); // 折叠到末尾
// 清空现有选区并设置新 range
selection.removeAllRanges();
selection.addRange(range);
// 确保编辑器有焦点
editor.focus();
});
},
//添加节点
handleAddNodeToEditor(columnName, rowIndex) {
let nodeRef = "contentEditor" + columnName + rowIndex;
this.currenrActiveNodeRef = nodeRef;
this.representation = true;
this.representationActiveName = "first";
// 这段代码的目的是 让光标停留在最后一个span标签后面
const editor = this.$refs[this.currenrActiveNodeRef][0];
if (!editor) return;
// 创建 range 和 selection
const range = document.createRange();
const selection = window.getSelection();
// 将 range 定位到 editor 的最后一个子节点
range.selectNodeContents(editor);
range.collapse(false); // 折叠到末尾
// 清空现有选区并设置新 range
selection.removeAllRanges();
selection.addRange(range);
// 确保编辑器有焦点
editor.focus();
},
handleNodeSelected(path) {
this.selectedNode = path;
this.insertToken(path, "node-reference");
},
handleTagSelected(label){
},
// 插入token到编辑器
insertToken(text, type) {
if (!this.currenrActiveNodeRef) return;
const editor = this.$refs[this.currenrActiveNodeRef][0];
if (!editor) return;
// 确保编辑器有焦点
editor.focus();
const token = this.createTokenElement(text, type);
// 获取当前光标位置
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
range.deleteContents();
// 插入token
range.insertNode(token);
// 在token后添加一个空格
const space = document.createTextNode(" ");
range.setStartAfter(token);
range.insertNode(space);
// 使用setTimeout确保DOM更新完成后设置光标
setTimeout(() => {
if (!this.setCursorAfter(space)) {
// 如果设置失败,则设置到编辑器末尾
this.setCursorToEnd();
}
}, 10); // 增加延迟时间确保DOM完全更新
} else {
// 如果没有选中区域,直接添加到末尾
editor.appendChild(token);
const space = document.createTextNode(" ");
editor.appendChild(space);
// 使用setTimeout确保DOM更新完成后设置光标
setTimeout(() => {
if (!this.setCursorAfter(space)) {
this.setCursorToEnd();
}
}, 10);
}
// 强制更新
this.$forceUpdate();
},
// 设置光标到末尾
setCursorToEnd() {
if (!this.currenrActiveNodeRef) return;
const editor = this.$refs[this.currenrActiveNodeRef][0];
if (!editor) return;
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(editor);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
},
// 设置光标到指定节点后面
setCursorAfter(node) {
try {
const selection = window.getSelection();
const range = document.createRange();
range.setStartAfter(node);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
return true;
} catch (error) {
return false;
}
},
// 创建token元素
createTokenElement(text, type) {
const token = document.createElement("span");
token.className = `content-token token-function`;
token.contentEditable = false;
token.setAttribute("data-token-id", ++this.tokenCounter);
token.setAttribute("data-token-type", type);
token.setAttribute("data-token-text", text);
if (type === "node-reference") {
token.innerHTML = `<i class="el-icon-connection"></i> ${text}`;
}
return token;
},
// 设置光标到末尾
setCursorToEnd() {
// 清除之前的选中状态
if (!this.currenrActiveNodeRef) return;
const editor = this.$refs[this.currenrActiveNodeRef][0];
if (!editor) return;
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(editor);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
},
closeRepContainer() {
this.representationActiveName = "first";
this.representation = false;
this.selectedNode = "";
this.currenrActiveNodeRef = "";
2025-05-30 10:04:55 +08:00
},
},
};
</script>
<style scoped lang="scss">
2025-05-30 19:59:25 +08:00
::v-deep .el-radio-group {
background: #fff;
padding: 2px;
margin: 0 10px 0 10px;
border-radius: 4px;
}
::v-deep .el-radio-button__inner {
border: 0 !important;
background-color: #fff;
color: #999999;
font-weight: 400;
font-size: 14px;
padding: 7px 15px;
}
::v-deep .el-radio-button__orig-radio:checked + .el-radio-button__inner {
background-color: #f5f5f5;
color: #333333;
box-shadow: none;
border-radius: 4px;
}
2025-05-30 10:04:55 +08:00
.drawContent {
width: 100%;
2025-05-30 19:59:25 +08:00
height: calc(100vh - 100px);
2025-05-30 10:04:55 +08:00
display: flex;
.drawCanvas {
width: 40%;
height: 100%;
background: url("../images/background.png") no-repeat;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-pack: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
overflow-y: auto;
padding: 50px 0;
.drawItem {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.title {
font-weight: 400;
font-size: 14px;
color: #333333;
margin-bottom: 5px;
}
.drawBox {
width: 300px;
height: 58px;
background: #ffffff;
border-radius: 4px;
border: 1px solid #dbdde1;
font-weight: 400;
font-size: 14px;
color: #333333;
display: flex;
// justify-content: center;
align-items: center;
position: relative;
.img {
margin: 0 10px;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #f18709;
img {
width: 60%;
height: 60%;
}
}
.iconStyle {
margin: 0 10px;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
img {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.actionName {
font-weight: 400;
font-size: 14px;
color: #333333;
}
.content {
font-weight: 500;
font-size: 14px;
color: #333333;
margin-top: 5px;
2025-05-30 10:04:55 +08:00
}
}
.drawBox:hover {
background: #f2fcff;
border: 1px solid #1478f5;
}
.del {
position: absolute;
right: -5px;
top: -3px;
cursor: pointer;
width: 16px;
height: 16px;
background: #fff;
img {
width: 100%;
height: 100%;
}
}
.active {
background: #f2fcff;
border: 1px solid #1478f5;
}
.line-wrapper {
width: 100%;
display: flex;
justify-content: center;
position: relative;
img {
position: absolute;
cursor: pointer;
width: 20px;
height: 20px;
top: 33px;
left: 140px;
display: none;
}
}
.line-wrapper:hover img {
display: block;
}
.line {
width: 1px;
height: 60px;
border: 1px solid #dbdde1;
margin: 10px 0;
}
}
}
.drawAction {
width: 60%;
height: 100%;
background-color: #f5f5f5;
position: relative;
.nextButton {
position: absolute;
right: 15px;
top: 8px;
color: #1477f3;
cursor: pointer;
font-size: 14px;
}
::v-deep .el-tabs__header {
margin: 0;
background: #ffffff;
.el-tabs__nav {
margin-left: 10px;
}
}
}
}
// cron表达式样式
.crontabBox {
padding: 15px;
.crontabBoxTitle {
margin-bottom: 15px;
color: #333333;
}
::v-deep .el-tabs--border-card {
border-radius: 8px;
box-shadow: unset;
border: 1px solid #dadce0;
}
::v-deep .popup-main {
border-radius: 8px;
border: 1px solid #dadce0;
font-size: 14px;
}
::v-deep .popup-result {
border: unset;
margin: 0 auto;
padding: 15px 10px 5px 15px;
.title {
width: unset;
margin-left: unset;
background: transparent;
color: #333;
}
}
::v-deep .el-tabs__header {
border-radius: 8px 8px 0 0;
margin: 0;
background: #f5f7fa !important;
.el-tabs__nav {
margin-left: 0 !important;
}
}
::v-deep .el-tabs__header .el-tabs__item:first-child {
border-radius: 8px 0 0 0;
margin-left: -2px;
margin-top: 0px;
}
::v-deep .pop_btn {
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
.el-button:last-child {
display: none;
}
}
}
2025-05-30 19:59:25 +08:00
.drawItemBox {
2025-05-30 10:04:55 +08:00
display: flex;
2025-05-30 19:59:25 +08:00
background: #ffffff;
padding: 10px;
2025-05-30 10:04:55 +08:00
2025-05-30 19:59:25 +08:00
.title {
2025-05-30 10:04:55 +08:00
font-weight: 400;
2025-05-30 19:59:25 +08:00
font-size: 14px;
2025-05-30 10:04:55 +08:00
color: #333333;
}
2025-05-30 19:59:25 +08:00
.drawBox {
background: #ffffff;
font-weight: 400;
font-size: 14px;
color: #333333;
2025-05-30 10:04:55 +08:00
display: flex;
2025-05-30 19:59:25 +08:00
position: relative;
2025-05-30 10:04:55 +08:00
align-items: center;
2025-05-30 19:59:25 +08:00
.img {
margin-right: 20px;
}
.actionName {
font-weight: 400;
font-size: 14px;
color: #333333;
margin-bottom: 5px;
}
.content {
font-weight: 500;
font-size: 14px;
color: #333333;
margin-top: 5px;
2025-05-30 10:04:55 +08:00
}
}
2025-05-30 19:59:25 +08:00
.del {
position: absolute;
right: 5px;
top: 2px;
cursor: pointer;
}
.active {
background: #f2fcff;
border: 1px solid #1478f5;
}
.line {
width: 1px;
height: 60px;
border: 1px solid #dbdde1;
margin: 10px 0;
}
2025-05-30 10:04:55 +08:00
}
2025-05-30 19:59:25 +08:00
2025-05-30 10:04:55 +08:00
// 选择操作
.timeWrap {
padding: 15px;
box-sizing: border-box;
.timeItem {
width: 100%;
height: 58px;
background: #ffffff;
border-radius: 4px;
border: 1px solid #dbdde1;
padding: 10px 14px;
box-sizing: border-box;
margin-bottom: 10px;
display: flex;
.imgBox {
width: 15px;
height: 20px;
margin-right: 3px;
img {
width: 100%;
height: 100%;
}
}
.title {
font-weight: 500;
font-size: 14px;
color: #333333;
}
.content {
font-weight: 400;
font-size: 14px;
color: #333333;
}
}
.timeItem:hover {
background: #f2fcff;
border: 1px solid #1478f5;
}
.activTimeItem {
background: #f2fcff;
border-radius: 4px;
border: 1px solid #1478f6;
}
}
2025-05-30 19:59:25 +08:00
.app {
.applist {
display: flex;
padding: 10px;
flex-wrap: wrap;
.appItem {
width: 202px;
height: 58px;
background: #ffffff;
border-radius: 4px;
padding: 0 10px;
display: flex;
flex-direction: row;
align-items: center;
margin-right: 10px;
border: 1px solid #fff;
margin-bottom: 10px;
cursor: pointer;
.imgBox {
width: 15px;
height: 22px;
margin-right: 3px;
img {
width: 100%;
height: 100%;
}
}
.appIcon {
width: 44px;
height: 44px;
margin-right: 10px;
border-radius: 50%;
}
.name {
font-weight: 400;
font-size: 14px;
color: #333333;
flex: 1;
}
}
.appItem:hover {
background: #f2fcff;
border: 1px solid #1478f5;
}
.active {
background: #f2fcff;
border: 1px solid #1478f5;
}
}
}
.DescribeDataBox {
display: flex;
flex-direction: column;
padding: 10px 15px 10px 10px;
font-size: 15px;
color: #333;
span {
margin-bottom: 10px;
}
}
.search {
display: flex;
margin: 10px;
.input {
margin-right: 10px;
}
}
.ApiPliginBox {
height: calc(100vh - 270px);
overflow: auto;
padding-right: 5px;
.appItem {
height: unset !important;
display: flex;
flex-direction: column;
width: 100% !important;
padding: 10px !important;
align-items: flex-start !important;
margin-right: 0 !important;
color: #333333 !important;
font-size: 14px !important;
.name:first-child {
font-weight: 500;
color: #333333;
}
}
}
.userList {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 10px 15px 10px 10px;
font-size: 14px;
.userItem {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
background: #ffffff;
border-radius: 4px;
border: 1px solid #dbdde1;
margin-bottom: 10px;
padding: 15px;
cursor: pointer;
}
.status {
margin-right: 10px;
font-size: 18px !important;
}
2025-05-30 19:59:25 +08:00
.userAccountIcon {
width: 15px;
height: 15px;
margin-right: 10px;
img {
width: 100%;
height: 100%;
}
}
.userItem:hover {
background: #f2fcff;
border: 1px solid #1478f5;
}
.userBtn {
padding: 15px 10px;
border: 1px dashed #dbdde1;
}
.userBtn:hover {
background: #f2fcff;
border: 1px dashed #1478f6;
}
}
.userContentBox {
width: 100%;
height: calc(100vh - 200px);
overflow: auto;
.active {
background: #f2fcff;
border: 1px solid #1478f5;
}
}
.redBox {
background: #fff1eb;
border-radius: 4px;
border: 1px solid #ffb999;
color: #ff5100;
font-size: 12px;
text-align: center;
padding: 2px 8px;
margin-left: 5px;
}
.successBox {
background: #f6ffed;
border-radius: 4px;
border: 1px solid #b7eb8f;
color: #52c41a;
font-size: 12px;
text-align: center;
padding: 2px 8px;
margin-left: 5px;
}
.settingBox {
padding: 10px;
.title {
margin-bottom: 10px;
color: #333333;
font-weight: 500;
font-size: 14px;
}
}
.settingbtn {
width: 20px;
height: 20px;
font-size: 16px;
color: #1478f6;
text-align: center;
line-height: 20px;
cursor: pointer;
margin: 0 auto;
position: absolute;
top: 90px;
left: 19px;
z-index: 999;
padding: 6px;
}
.settingTabs {
width: 100%;
padding: 10px 15px 10px 10px;
.tabs {
display: flex;
align-items: center;
height: 32px;
background: #fff;
border-radius: 4px;
padding: 2px;
width: fit-content;
.tabsItem {
height: 28px;
line-height: 28px;
background: #fff;
border-radius: 3px;
font-size: 14px;
color: #999;
padding: 0 10px;
cursor: pointer;
}
.active {
background: #f5f5f5;
color: #333;
}
}
::v-deep .el-switch {
height: 23px !important;
line-height: 23px !important;
}
}
.currentDrawBox {
background: #ffffff;
display: flex;
align-items: center;
font-size: 14px;
color: #333;
.img {
width: 44px;
height: 44px;
border-radius: 50%;
margin-right: 10px;
img {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.actionName {
font-weight: 400;
color: #333333;
margin-bottom: 5px;
}
.content {
font-weight: 500;
color: #333333;
margin-bottom: 5px;
}
.drawimg {
margin-right: 10px;
width: 44px;
height: 44px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #f18709;
img {
width: 60%;
height: 60%;
}
}
}
.drawItemBtn {
background: #ffffff;
padding: 10px 50px;
font-size: 14px;
color: #1478f6;
border: 1px dashed #dbdde1;
border-radius: 4px;
cursor: pointer;
margin-top: 50px;
}
.drawItemBtn:hover {
background: #f2fcff;
border: 1px dashed #1478f6;
}
.DescribeContent {
display: -webkit-box;
/*作为弹性伸缩盒子模型显示。*/
-webkit-box-orient: vertical;
/*作为弹性伸缩盒子模型显示。*/
-webkit-line-clamp: 1; //*显示的行*/
overflow: hidden;
/*溢出隐藏*/
/* 对于连字情况或者纯字母,可以解决 */
word-break: break-all;
// max-width: 100px;
}
.InterfaceConfiguration {
background: #fff;
margin: 10px;
padding: 10px;
border-radius: 4px;
.InterCigTitle {
font-size: 14px;
margin-bottom: 10px;
}
}
::v-deep .operatingClass {
display: flex;
align-items: center;
.el-radio-group {
background: unset !important;
}
}
::v-deep .CodeMirror {
height: 100% !important;
}
.toolbar {
background: #fff;
padding: 8px;
font-size: 14px;
border-radius: 4px 4px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
select {
width: 100px;
margin-left: 10px;
padding: 2px;
}
}
.fullScreen {
display: flex;
align-items: center;
font-size: 14px;
margin-left: 10px;
cursor: pointer;
> img {
width: 15px;
height: 15px;
margin-right: 5px;
}
}
.acionImg {
width: 15px;
height: 15px;
margin-left: 5px;
}
::v-deep .el-form-item {
margin-bottom: 0 !important;
}
::v-deep .el-form-item__label {
padding-bottom: 0 !important;
font-weight: bold;
}
.outsideContent {
height: calc(100vh - 220px);
overflow: auto;
padding: 0 10px 10px 10px;
}
::v-deep .el-collapse-item__header.is-active {
padding-left: 10px !important;
}
// ------------------------
.editor-container {
flex: 1;
overflow: hidden;
position: relative;
}
.editorIcon {
position: absolute;
right: 10px;
top: 0;
font-size: 20px;
color: #999999;
}
.clearIcon {
display: none;
position: absolute;
top: 0;
right: 35px;
font-size: 20px;
color: #999999;
}
.editor-container:hover .clearIcon {
display: block;
}
/* hover 时显示 close-icon */
.editor-container:hover .close-icon {
display: inline-flex; /* 或 block根据你的布局 */
}
.content-editor {
border: 1px solid #e5e7eb;
border-radius: 4px;
min-height: 36px;
padding: 0 50px 0 15px;
background: #fff;
position: relative;
font-size: 14px;
// line-height: 1.6;
overflow-y: auto;
outline: none;
word-wrap: break-word;
}
.content-editor:focus {
border-color: #409eff;
}
.content-editor:empty:before {
content: attr(data-placeholder);
color: #9ca3af;
font-style: italic;
pointer-events: none;
}
.content-editor:focus:empty:before {
content: "";
}
/* 确保空编辑器也能显示光标 */
.content-editor:empty {
min-height: 20px;
}
.content-editor:focus {
outline: none;
}
// --------------------------------
::v-deep .content-token {
display: inline-table;
padding: 0 10px;
border-radius: 4px;
margin: 2px;
cursor: pointer;
transition: all 0.2s ease;
vertical-align: middle;
font-size: 13px;
font-weight: 500;
user-select: none;
}
::v-deep .token-function {
background: #e0e7ff;
color: #5b21b6;
border: 1px solid #8b5cf6;
}
::v-deep .repContainer {
width: 100%;
height: 100%;
background: #f5f5f5;
padding: 10px;
position: relative;
}
.treeNodeBox {
height: calc(100% - 50px);
overflow-y: auto;
}
.closeRepContainer {
position: absolute;
top: 20px;
right: 12px;
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
i {
margin-right: 5px;
}
}
.closeRepContainer:hover {
color: #409eff;
}
.repItem {
display: flex;
align-items: center;
margin-bottom: 8px;
cursor: pointer;
}
.repItem:hover .repItemValue{
color: #409eff;
}
.repItemValue {
color: #6b7280;
font-size: 13px;
margin-left: 10px;
}
2025-05-30 19:59:25 +08:00
</style>