标准流程管理+流程创建
This commit is contained in:
12
rc_autoplc_backend/.idea/misc.xml
generated
Normal file
12
rc_autoplc_backend/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="MavenProjectsManager">
|
||||||
|
<option name="originalFiles">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/pom.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK" />
|
||||||
|
</project>
|
||||||
6
rc_autoplc_backend/.idea/vcs.xml
generated
Normal file
6
rc_autoplc_backend/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
54
rc_autoplc_backend/.idea/workspace.xml
generated
Normal file
54
rc_autoplc_backend/.idea/workspace.xml
generated
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="d47bc58e-d36b-422c-92c6-393c467a25d3" name="Changes" comment="" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
|
||||||
|
</component>
|
||||||
|
<component name="MarkdownSettingsMigration">
|
||||||
|
<option name="stateVersion" value="1" />
|
||||||
|
</component>
|
||||||
|
<component name="MavenImportPreferences">
|
||||||
|
<option name="generalSettings">
|
||||||
|
<MavenGeneralSettings>
|
||||||
|
<option name="useMavenConfig" value="true" />
|
||||||
|
</MavenGeneralSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectId" id="37uy9ngFoDUVUddX8Yg9afHWRsn" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"WebServerToolWindowFactoryState": "false",
|
||||||
|
"vue.rearranger.settings.migration": "true"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="d47bc58e-d36b-422c-92c6-393c467a25d3" name="Changes" comment="" />
|
||||||
|
<created>1767767108230</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1767767108230</updated>
|
||||||
|
<workItem from="1767767109651" duration="29000" />
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Vite App</title>
|
<title>北京融创智能仪器管理系统</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
@@ -8,6 +8,14 @@ export function stepInfoadd(data: any) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stepInfoaddlist(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/stepInfo/batchAdd',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function stepInfodel(id: string | number) {
|
export function stepInfodel(id: string | number) {
|
||||||
return request({
|
return request({
|
||||||
url: `/stepInfo/del/${id}`,
|
url: `/stepInfo/del/${id}`,
|
||||||
@@ -15,6 +23,14 @@ export function stepInfodel(id: string | number) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stepInfodellist(data: any[]) {
|
||||||
|
return request({
|
||||||
|
url: '/stepInfo/batchDel',
|
||||||
|
method: 'delete',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function stepInfoupd(data: any) {
|
export function stepInfoupd(data: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/stepInfo/update',
|
url: '/stepInfo/update',
|
||||||
@@ -23,6 +39,14 @@ export function stepInfoupd(data: any) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stepInfoupdlist(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/stepInfo/batchUpdate',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function stepInfolist(data: any) {
|
export function stepInfolist(data: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/stepInfo/listPage',
|
url: '/stepInfo/listPage',
|
||||||
|
|||||||
@@ -91,6 +91,14 @@
|
|||||||
/>
|
/>
|
||||||
<el-table-column label="操作" width="180" fixed="right">
|
<el-table-column label="操作" width="180" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
:type="scope.row.hasWorkflow ? 'success' : 'warning'"
|
||||||
|
link
|
||||||
|
:loading="scope.row.workflowLoading"
|
||||||
|
@click="handleConfigWorkflow(scope.row)"
|
||||||
|
>
|
||||||
|
{{ scope.row.hasWorkflow ? '编辑流程' : '配置流程' }}
|
||||||
|
</el-button>
|
||||||
<el-button type="primary" link @click="handleEdit(scope.row)">编辑</el-button>
|
<el-button type="primary" link @click="handleEdit(scope.row)">编辑</el-button>
|
||||||
<el-button type="danger" link @click="handleDelete(scope.row)">删除</el-button>
|
<el-button type="danger" link @click="handleDelete(scope.row)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -203,6 +211,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { Plus } from '@element-plus/icons-vue'
|
import { Plus } from '@element-plus/icons-vue'
|
||||||
@@ -213,6 +222,7 @@ import {
|
|||||||
flowInfolist,
|
flowInfolist,
|
||||||
flowInfobyid,
|
flowInfobyid,
|
||||||
} from '@/api/tb/flowinfo'
|
} from '@/api/tb/flowinfo'
|
||||||
|
import { stepInfolist } from '@/api/tb/stepinfo'
|
||||||
|
|
||||||
interface FlowInfoItem {
|
interface FlowInfoItem {
|
||||||
id?: number | string
|
id?: number | string
|
||||||
@@ -226,8 +236,11 @@ interface FlowInfoItem {
|
|||||||
testMethod?: string
|
testMethod?: string
|
||||||
scanNum?: string
|
scanNum?: string
|
||||||
islandIdList?: string
|
islandIdList?: string
|
||||||
|
hasWorkflow?: boolean
|
||||||
|
workflowLoading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
const tableData = ref<FlowInfoItem[]>([])
|
const tableData = ref<FlowInfoItem[]>([])
|
||||||
@@ -334,8 +347,40 @@ const getFlowInfoList = async () => {
|
|||||||
const totalValue =
|
const totalValue =
|
||||||
data.total ?? data.count ?? data.totalCount ?? records.length ?? 0
|
data.total ?? data.count ?? data.totalCount ?? records.length ?? 0
|
||||||
|
|
||||||
tableData.value = records
|
tableData.value = records.map((item: any) => ({
|
||||||
|
...item,
|
||||||
|
hasWorkflow: false,
|
||||||
|
workflowLoading: false,
|
||||||
|
}))
|
||||||
total.value = Number(totalValue) || 0
|
total.value = Number(totalValue) || 0
|
||||||
|
|
||||||
|
// 查询每条标准流程是否已配置流程
|
||||||
|
await Promise.all(
|
||||||
|
tableData.value.map(async (row) => {
|
||||||
|
if (!row.id) return
|
||||||
|
row.workflowLoading = true
|
||||||
|
try {
|
||||||
|
const res: any = await stepInfolist({
|
||||||
|
flowId: row.id,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 1,
|
||||||
|
})
|
||||||
|
const data = res?.data ?? res ?? {}
|
||||||
|
const records =
|
||||||
|
data.records ||
|
||||||
|
data.list ||
|
||||||
|
data.rows ||
|
||||||
|
data.items ||
|
||||||
|
(Array.isArray(data.data) ? data.data : [])
|
||||||
|
row.hasWorkflow = Array.isArray(records) && records.length > 0
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`查询流程配置状态失败(${row.id}):`, error)
|
||||||
|
row.hasWorkflow = false
|
||||||
|
} finally {
|
||||||
|
row.workflowLoading = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取流程信息列表失败:', error)
|
console.error('获取流程信息列表失败:', error)
|
||||||
ElMessage.error('获取流程信息列表失败')
|
ElMessage.error('获取流程信息列表失败')
|
||||||
@@ -432,6 +477,23 @@ const handleEdit = async (row: FlowInfoItem) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 跳转到流程创建/编辑
|
||||||
|
const handleConfigWorkflow = (row: FlowInfoItem) => {
|
||||||
|
if (!row.id) {
|
||||||
|
ElMessage.warning('缺少流程信息ID')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
router.push({
|
||||||
|
name: 'step-info',
|
||||||
|
query: {
|
||||||
|
flowId: row.id,
|
||||||
|
flowIndex: row.flowIndex ?? '',
|
||||||
|
flowName: row.flowName ?? '',
|
||||||
|
mode: row.hasWorkflow ? 'edit' : 'create',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 删除流程信息
|
// 删除流程信息
|
||||||
const handleDelete = async (row: FlowInfoItem) => {
|
const handleDelete = async (row: FlowInfoItem) => {
|
||||||
if (!row.id) {
|
if (!row.id) {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
v-for="(item, index) in islandList"
|
v-for="(item, index) in islandList"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="island-card"
|
class="island-card"
|
||||||
|
:style="{ animationDelay: `${index * 50}ms` }"
|
||||||
>
|
>
|
||||||
<!-- 左侧步骤编号和过滤器图标 -->
|
<!-- 左侧步骤编号和过滤器图标 -->
|
||||||
<div class="card-left">
|
<div class="card-left">
|
||||||
@@ -945,6 +946,8 @@ onMounted(() => {
|
|||||||
min-height: 95px;
|
min-height: 95px;
|
||||||
height: 95px;
|
height: 95px;
|
||||||
border: 1px solid rgba(64, 158, 255, 0.1);
|
border: 1px solid rgba(64, 158, 255, 0.1);
|
||||||
|
animation: slideUpIn 0.4s ease-out forwards;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 4px 16px rgba(64, 158, 255, 0.2);
|
box-shadow: 0 4px 16px rgba(64, 158, 255, 0.2);
|
||||||
@@ -1100,6 +1103,18 @@ onMounted(() => {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 功能岛卡片动画:从下往上滑动
|
||||||
|
@keyframes slideUpIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
v-for="(item, index) in islandList"
|
v-for="(item, index) in islandList"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="component-item"
|
class="component-item"
|
||||||
|
:style="{ animationDelay: `${index * 50}ms` }"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
@dragstart="handleDragStart($event, item)"
|
@dragstart="handleDragStart($event, item)"
|
||||||
>
|
>
|
||||||
@@ -24,7 +25,29 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 中间流程设计画布 -->
|
<!-- 中间流程设计画布 -->
|
||||||
<div class="workflow-canvas" @dragover="handleDragOver" @drop="handleDrop">
|
<div
|
||||||
|
class="workflow-canvas"
|
||||||
|
v-loading="workflowLoading"
|
||||||
|
@dragover="handleDragOver"
|
||||||
|
@drop="handleDrop"
|
||||||
|
>
|
||||||
|
<div class="canvas-header">
|
||||||
|
<div class="canvas-title">
|
||||||
|
<template v-if="flowIndex && flowName">
|
||||||
|
{{ flowIndex }}、{{ flowName }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="flowName">
|
||||||
|
{{ flowName }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
未选择标准流程
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="canvas-status">
|
||||||
|
<el-tag v-if="isEditMode" type="success" effect="plain">编辑流程</el-tag>
|
||||||
|
<el-tag v-else type="warning" effect="plain">配置流程</el-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div v-if="workflowItems.length === 0" class="empty-canvas">
|
<div v-if="workflowItems.length === 0" class="empty-canvas">
|
||||||
拖拽左侧功能岛到此处创建流程步骤
|
拖拽左侧功能岛到此处创建流程步骤
|
||||||
</div>
|
</div>
|
||||||
@@ -35,6 +58,7 @@
|
|||||||
:key="index"
|
:key="index"
|
||||||
class="workflow-item"
|
class="workflow-item"
|
||||||
:class="{ 'is-selected': selectedItemIndex === index }"
|
:class="{ 'is-selected': selectedItemIndex === index }"
|
||||||
|
:style="{ animationDelay: `${index * 100}ms` }"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
@dragstart="handleWorkflowItemDragStart($event, index)"
|
@dragstart="handleWorkflowItemDragStart($event, index)"
|
||||||
@dragover.stop="handleWorkflowItemDragOver($event, index)"
|
@dragover.stop="handleWorkflowItemDragOver($event, index)"
|
||||||
@@ -84,9 +108,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<el-button @click="clearWorkflow">清空</el-button>
|
<el-button type="primary" plain @click="clearWorkflow">清空</el-button>
|
||||||
<el-button type="primary" plain @click="createWorkflow">新建</el-button>
|
<el-button type="primary" @click="saveWorkflow" :loading="savingWorkflow">保存</el-button>
|
||||||
<el-button type="primary" @click="saveWorkflow">保存</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -175,6 +198,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, onMounted, computed } from 'vue'
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import {
|
import {
|
||||||
MoreFilled,
|
MoreFilled,
|
||||||
@@ -198,16 +222,50 @@ import { devselect } from '@/api/tb/devinfo'
|
|||||||
import { devparamselect } from '@/api/tb/devparam'
|
import { devparamselect } from '@/api/tb/devparam'
|
||||||
import { dictypelist } from '@/api/system/dictype'
|
import { dictypelist } from '@/api/system/dictype'
|
||||||
import { dicdatabydicid } from '@/api/system/dicdata'
|
import { dicdatabydicid } from '@/api/system/dicdata'
|
||||||
|
import {
|
||||||
|
stepInfoaddlist,
|
||||||
|
stepInfodel,
|
||||||
|
stepInfodellist,
|
||||||
|
stepInfoupdlist,
|
||||||
|
stepInfolist,
|
||||||
|
} from '@/api/tb/stepinfo'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 路由携带的标准流程信息
|
||||||
|
const flowId = computed(() => {
|
||||||
|
const raw = route.query.flowId
|
||||||
|
const num = Number(raw)
|
||||||
|
return Number.isNaN(num) ? undefined : num
|
||||||
|
})
|
||||||
|
const flowName = computed(() => (route.query.flowName as string) || '')
|
||||||
|
const flowIndex = computed(() => (route.query.flowIndex as string) || '')
|
||||||
|
const hasRemoteWorkflow = ref(false)
|
||||||
|
const isEditMode = computed(() => {
|
||||||
|
const mode = (route.query.mode as string) || ''
|
||||||
|
return mode === 'edit' || hasRemoteWorkflow.value
|
||||||
|
})
|
||||||
|
|
||||||
// 功能岛列表
|
// 功能岛列表
|
||||||
const islandList = ref<any[]>([])
|
const islandList = ref<any[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
const workflowLoading = ref(false)
|
||||||
|
|
||||||
// 当前流程中的组件
|
// 当前流程中的组件
|
||||||
const workflowItems = ref<any[]>([])
|
const workflowItems = ref<any[]>([])
|
||||||
const selectedItemIndex = ref(-1)
|
const selectedItemIndex = ref(-1)
|
||||||
const workflowGridRef = ref<HTMLElement | null>(null)
|
const workflowGridRef = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
const generateInstanceId = (base?: number | string) =>
|
||||||
|
`island-${base ?? 'unknown'}-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
||||||
|
const getInstanceId = (item: any) => {
|
||||||
|
if (!item.instanceId) {
|
||||||
|
item.instanceId = generateInstanceId(item.id)
|
||||||
|
}
|
||||||
|
return item.instanceId
|
||||||
|
}
|
||||||
|
|
||||||
// 计算网格样式
|
// 计算网格样式
|
||||||
const gridStyle = computed(() => {
|
const gridStyle = computed(() => {
|
||||||
const count = workflowItems.value.length
|
const count = workflowItems.value.length
|
||||||
@@ -238,6 +296,7 @@ const paramList = ref<any[]>([])
|
|||||||
const paramFormData = reactive<Record<string, any>>({})
|
const paramFormData = reactive<Record<string, any>>({})
|
||||||
const paramLoading = ref(false)
|
const paramLoading = ref(false)
|
||||||
const savingParams = ref(false)
|
const savingParams = ref(false)
|
||||||
|
const savingWorkflow = ref(false)
|
||||||
|
|
||||||
// 字典类型和字典数据缓存
|
// 字典类型和字典数据缓存
|
||||||
const dicTypeCache = ref<Map<string, any>>(new Map())
|
const dicTypeCache = ref<Map<string, any>>(new Map())
|
||||||
@@ -245,6 +304,24 @@ const dicDataCache = ref<Map<number, any[]>>(new Map())
|
|||||||
|
|
||||||
// 参数选项映射:paramId -> options[]
|
// 参数选项映射:paramId -> options[]
|
||||||
const paramOptionsMap = reactive<Record<string | number, Array<{ label: string; value: string }>>>({})
|
const paramOptionsMap = reactive<Record<string | number, Array<{ label: string; value: string }>>>({})
|
||||||
|
const paramCache = reactive<Record<string, any[]>>({})
|
||||||
|
const clearParamCache = () => {
|
||||||
|
Object.keys(paramCache).forEach((key) => {
|
||||||
|
delete paramCache[key]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeListResponse = (res: any) => {
|
||||||
|
const data = res?.data ?? res ?? {}
|
||||||
|
return (
|
||||||
|
data.records ||
|
||||||
|
data.list ||
|
||||||
|
data.rows ||
|
||||||
|
data.items ||
|
||||||
|
(Array.isArray(data.data) ? data.data : []) ||
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 中文数字
|
// 中文数字
|
||||||
const chineseNumbers = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
const chineseNumbers = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
||||||
@@ -372,7 +449,12 @@ const getIslandList = async () => {
|
|||||||
return idA - idB
|
return idA - idB
|
||||||
})
|
})
|
||||||
|
|
||||||
islandList.value = records
|
islandList.value = records
|
||||||
|
|
||||||
|
// 若带有标准流程信息则尝试加载已保存的流程
|
||||||
|
if (flowId.value) {
|
||||||
|
await loadWorkflowFromServer()
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取功能岛列表失败:', error)
|
console.error('获取功能岛列表失败:', error)
|
||||||
ElMessage.error('获取功能岛列表失败')
|
ElMessage.error('获取功能岛列表失败')
|
||||||
@@ -381,6 +463,88 @@ const getIslandList = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从后端加载已有流程并反填
|
||||||
|
const loadWorkflowFromServer = async () => {
|
||||||
|
if (!flowId.value) return
|
||||||
|
try {
|
||||||
|
workflowLoading.value = true
|
||||||
|
const res: any = await stepInfolist({
|
||||||
|
flowId: flowId.value,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10000,
|
||||||
|
})
|
||||||
|
|
||||||
|
const records = normalizeListResponse(res)
|
||||||
|
if (!Array.isArray(records) || records.length === 0) {
|
||||||
|
hasRemoteWorkflow.value = false
|
||||||
|
workflowItems.value = []
|
||||||
|
clearParamCache()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedRecords = [...records].sort(
|
||||||
|
(a: any, b: any) => (a?.stepOrder ?? 0) - (b?.stepOrder ?? 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
workflowItems.value = []
|
||||||
|
clearParamCache()
|
||||||
|
const islandCountMap: Record<string, number> = {}
|
||||||
|
let lastIslandId: any = null
|
||||||
|
let currentInstanceId = ''
|
||||||
|
|
||||||
|
sortedRecords.forEach((record: any) => {
|
||||||
|
const islandId = record.islandId ?? record.islandID ?? record.islandid
|
||||||
|
const islandNameFromRecord = record.islandName || record.islandname || ''
|
||||||
|
if (lastIslandId !== islandId) {
|
||||||
|
const islandMeta = islandList.value.find((it) => it.id === islandId) || {}
|
||||||
|
const islandName = islandMeta.islandName || islandMeta.name || islandNameFromRecord || '功能岛'
|
||||||
|
const descriptionKey = getIslandDescriptionKey(islandName)
|
||||||
|
const count = islandCountMap[descriptionKey] ?? 0
|
||||||
|
islandCountMap[descriptionKey] = count + 1
|
||||||
|
currentInstanceId = generateInstanceId(islandId)
|
||||||
|
workflowItems.value.push({
|
||||||
|
...islandMeta,
|
||||||
|
id: islandId,
|
||||||
|
islandName,
|
||||||
|
name: islandMeta.name || islandName,
|
||||||
|
description: `第${chineseNumbers[count] || count + 1}次${descriptionKey || islandName}`,
|
||||||
|
sort: count,
|
||||||
|
instanceId: currentInstanceId,
|
||||||
|
})
|
||||||
|
paramCache[currentInstanceId] = []
|
||||||
|
lastIslandId = islandId
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentInstanceId) return
|
||||||
|
const cacheArr = paramCache[currentInstanceId] || []
|
||||||
|
paramCache[currentInstanceId] = cacheArr
|
||||||
|
cacheArr.push({
|
||||||
|
stepId: record.id,
|
||||||
|
flowId: flowId.value,
|
||||||
|
islandId,
|
||||||
|
islandName: islandNameFromRecord,
|
||||||
|
devId: record.devId ?? record.devid,
|
||||||
|
devName: record.devName ?? record.devname,
|
||||||
|
paramId: record.paramId ?? record.paramid,
|
||||||
|
paramName: record.paramName,
|
||||||
|
paramType: record.paramType,
|
||||||
|
paramUnit: record.paramUnit,
|
||||||
|
paramValue: record.paramValue,
|
||||||
|
formType: record.formType,
|
||||||
|
stepOrder: record.stepOrder,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
hasRemoteWorkflow.value = workflowItems.value.length > 0
|
||||||
|
selectedItemIndex.value = -1
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载流程数据失败:', error)
|
||||||
|
ElMessage.error('加载流程数据失败')
|
||||||
|
} finally {
|
||||||
|
workflowLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 拖拽开始
|
// 拖拽开始
|
||||||
const handleDragStart = (event: DragEvent, item: any) => {
|
const handleDragStart = (event: DragEvent, item: any) => {
|
||||||
// 检查是否超过添加次数限制
|
// 检查是否超过添加次数限制
|
||||||
@@ -423,6 +587,7 @@ const handleDrop = (event: DragEvent) => {
|
|||||||
...dragItemData,
|
...dragItemData,
|
||||||
description: `第${chineseNumbers[count]}次${descriptionKey}`,
|
description: `第${chineseNumbers[count]}次${descriptionKey}`,
|
||||||
sort: 0,
|
sort: 0,
|
||||||
|
instanceId: generateInstanceId(dragItemData.id),
|
||||||
}
|
}
|
||||||
|
|
||||||
workflowItems.value.push(newItem)
|
workflowItems.value.push(newItem)
|
||||||
@@ -462,6 +627,7 @@ const handleContainerDrop = (event: DragEvent) => {
|
|||||||
...dragItemData,
|
...dragItemData,
|
||||||
description: `第${chineseNumbers[count]}次${descriptionKey}`,
|
description: `第${chineseNumbers[count]}次${descriptionKey}`,
|
||||||
sort: count,
|
sort: count,
|
||||||
|
instanceId: generateInstanceId(dragItemData.id),
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = document.querySelectorAll('.workflow-item')
|
const items = document.querySelectorAll('.workflow-item')
|
||||||
@@ -607,6 +773,8 @@ const handleEdit = async (item: any) => {
|
|||||||
try {
|
try {
|
||||||
paramLoading.value = true
|
paramLoading.value = true
|
||||||
paramList.value = []
|
paramList.value = []
|
||||||
|
const instanceId = getInstanceId(item)
|
||||||
|
const cachedParams = paramCache[instanceId] || []
|
||||||
|
|
||||||
// 获取功能岛绑定的设备
|
// 获取功能岛绑定的设备
|
||||||
const devRes: any = await devselect({})
|
const devRes: any = await devselect({})
|
||||||
@@ -644,10 +812,20 @@ const handleEdit = async (item: any) => {
|
|||||||
})
|
})
|
||||||
// 给每个参数添加设备信息
|
// 给每个参数添加设备信息
|
||||||
params.forEach((param: any) => {
|
params.forEach((param: any) => {
|
||||||
|
const cached = cachedParams.find(
|
||||||
|
(cacheItem: any) =>
|
||||||
|
cacheItem.paramId === param.id ||
|
||||||
|
cacheItem.paramName === param.paramName
|
||||||
|
)
|
||||||
allParams.push({
|
allParams.push({
|
||||||
...param,
|
...param,
|
||||||
devId: device.id,
|
devId: device.id,
|
||||||
devName: device.devName || `设备${device.id}`,
|
devName: device.devName || `设备${device.id}`,
|
||||||
|
paramValue: cached?.paramValue ?? param.paramValue,
|
||||||
|
paramType: cached?.paramType ?? param.paramType,
|
||||||
|
paramUnit: cached?.paramUnit ?? param.paramUnit,
|
||||||
|
formType: cached?.formType ?? param.formType,
|
||||||
|
stepId: cached?.stepId,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -664,7 +842,7 @@ const handleEdit = async (item: any) => {
|
|||||||
})
|
})
|
||||||
allParams.forEach((param) => {
|
allParams.forEach((param) => {
|
||||||
if (param.id) {
|
if (param.id) {
|
||||||
paramFormData[param.id] = param.paramValue || ''
|
paramFormData[param.id] = param.paramValue ?? ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -763,9 +941,30 @@ const loadParamOptions = async (params: any[]) => {
|
|||||||
const handleSaveParams = async () => {
|
const handleSaveParams = async () => {
|
||||||
try {
|
try {
|
||||||
savingParams.value = true
|
savingParams.value = true
|
||||||
// TODO: 调用API保存参数值
|
if (!editingItem.value) {
|
||||||
// 这里需要根据实际的API接口来保存参数值
|
ElMessage.warning('未选中功能岛')
|
||||||
ElMessage.success('保存成功')
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const instanceId = getInstanceId(editingItem.value)
|
||||||
|
const paramsForCache =
|
||||||
|
paramList.value.map((param) => ({
|
||||||
|
stepId: param.stepId,
|
||||||
|
flowId: flowId.value,
|
||||||
|
islandId: editingItem.value.id,
|
||||||
|
islandName: editingItem.value.islandName || editingItem.value.name,
|
||||||
|
devId: param.devId,
|
||||||
|
devName: param.devName,
|
||||||
|
paramId: param.id,
|
||||||
|
paramName: param.paramName,
|
||||||
|
paramType: param.paramType,
|
||||||
|
paramUnit: param.paramUnit,
|
||||||
|
paramValue: paramFormData[param.id],
|
||||||
|
formType: param.formType,
|
||||||
|
})) || []
|
||||||
|
|
||||||
|
paramCache[instanceId] = paramsForCache
|
||||||
|
ElMessage.success('参数保存成功')
|
||||||
drawerVisible.value = false
|
drawerVisible.value = false
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存参数失败:', error)
|
console.error('保存参数失败:', error)
|
||||||
@@ -782,7 +981,26 @@ const handleDelete = (index: number, item: any) => {
|
|||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(async () => {
|
||||||
|
const cacheKey = getInstanceId(item)
|
||||||
|
const cachedParams = paramCache[cacheKey] || []
|
||||||
|
|
||||||
|
// 删除已保存的步骤数据
|
||||||
|
if (hasRemoteWorkflow.value && cachedParams.some((p) => p.stepId)) {
|
||||||
|
try {
|
||||||
|
await Promise.all(
|
||||||
|
cachedParams
|
||||||
|
.map((p) => p.stepId)
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((id) => stepInfodel(id))
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除功能岛关联步骤失败:', error)
|
||||||
|
ElMessage.error('删除功能岛关联步骤失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete paramCache[cacheKey]
|
||||||
workflowItems.value.splice(index, 1)
|
workflowItems.value.splice(index, 1)
|
||||||
if (selectedItemIndex.value === index) {
|
if (selectedItemIndex.value === index) {
|
||||||
selectedItemIndex.value = -1
|
selectedItemIndex.value = -1
|
||||||
@@ -810,23 +1028,131 @@ const handleDelete = (index: number, item: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 清空
|
// 清空
|
||||||
const clearWorkflow = () => {
|
const clearWorkflow = async () => {
|
||||||
workflowItems.value = []
|
// 如果是编辑模式(从编辑流程按钮跳转过来),需要调用删除接口
|
||||||
selectedItemIndex.value = -1
|
if (isEditMode.value && flowId.value) {
|
||||||
ElMessage.info('已清空当前流程')
|
try {
|
||||||
}
|
await ElMessageBox.confirm(
|
||||||
|
`您确定要清除[${flowName.value || '该'}]的完整流程吗?`,
|
||||||
|
'提示',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// 新建
|
// 先获取所有需要删除的数据 ID
|
||||||
const createWorkflow = () => {
|
try {
|
||||||
workflowItems.value = []
|
const res: any = await stepInfolist({
|
||||||
selectedItemIndex.value = -1
|
flowId: flowId.value,
|
||||||
ElMessage.success('已创建新的流程')
|
pageNum: 1,
|
||||||
|
pageSize: 10000,
|
||||||
|
})
|
||||||
|
|
||||||
|
const records = normalizeListResponse(res)
|
||||||
|
if (Array.isArray(records) && records.length > 0) {
|
||||||
|
const ids = records.map((r: any) => r.id).filter(Boolean)
|
||||||
|
if (ids.length > 0) {
|
||||||
|
await stepInfodellist(ids)
|
||||||
|
ElMessage.success('流程已清空,可重新配置流程')
|
||||||
|
} else {
|
||||||
|
ElMessage.info('没有可删除的数据')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.info('没有可删除的数据')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除流程数据失败:', error)
|
||||||
|
ElMessage.error('删除流程数据失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空画布和缓存
|
||||||
|
workflowItems.value = []
|
||||||
|
selectedItemIndex.value = -1
|
||||||
|
clearParamCache()
|
||||||
|
hasRemoteWorkflow.value = false
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
console.error('清空流程失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 配置模式或从菜单栏直接进入,仅清除画布卡片
|
||||||
|
workflowItems.value = []
|
||||||
|
selectedItemIndex.value = -1
|
||||||
|
clearParamCache()
|
||||||
|
ElMessage.info('已清空当前流程')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存
|
// 保存
|
||||||
const saveWorkflow = () => {
|
const saveWorkflow = async () => {
|
||||||
// 暂时留空,后续实现
|
if (!flowId.value) {
|
||||||
ElMessage.info('保存功能待实现')
|
ElMessage.warning('请先选择标准流程!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!flowName.value) {
|
||||||
|
ElMessage.warning('缺少标准流程名称(stepName),请返回列表补全')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (workflowItems.value.length === 0) {
|
||||||
|
ElMessage.warning('请先在画布上添加功能岛')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let stepOrder = 1
|
||||||
|
const payload: any[] = []
|
||||||
|
|
||||||
|
for (const item of workflowItems.value) {
|
||||||
|
const cacheKey = getInstanceId(item)
|
||||||
|
const params = paramCache[cacheKey] || []
|
||||||
|
if (!params.length) {
|
||||||
|
ElMessage.warning(`请先为${item.islandName || item.name || '功能岛'}配置参数并保存`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params.forEach((param) => {
|
||||||
|
payload.push({
|
||||||
|
id: param.stepId,
|
||||||
|
flowId: flowId.value,
|
||||||
|
islandId: item.id,
|
||||||
|
devId: param.devId,
|
||||||
|
paramName: param.paramName,
|
||||||
|
paramType: param.paramType,
|
||||||
|
paramUnit: param.paramUnit,
|
||||||
|
paramValue: param.paramValue ?? '',
|
||||||
|
formType: param.formType,
|
||||||
|
stepName: flowName.value,
|
||||||
|
stepOrder: stepOrder++,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!payload.length) {
|
||||||
|
ElMessage.warning('暂无可保存的参数数据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
savingWorkflow.value = true
|
||||||
|
const apiAction = hasRemoteWorkflow.value ? stepInfoupdlist : stepInfoaddlist
|
||||||
|
const res: any = await apiAction(payload)
|
||||||
|
if (res?.code === 0 || res?.code === '0') {
|
||||||
|
ElMessage.success(hasRemoteWorkflow.value ? '流程更新成功!' : '流程保存成功')
|
||||||
|
hasRemoteWorkflow.value = true
|
||||||
|
clearParamCache()
|
||||||
|
await loadWorkflowFromServer()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res?.message || res?.msg || '保存失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存流程失败:', error)
|
||||||
|
ElMessage.error('保存流程失败')
|
||||||
|
} finally {
|
||||||
|
savingWorkflow.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭抽屉
|
// 关闭抽屉
|
||||||
@@ -921,6 +1247,8 @@ onMounted(() => {
|
|||||||
cursor: move;
|
cursor: move;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
animation: slideUpIn 0.4s ease-out forwards;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
@@ -961,6 +1289,30 @@ onMounted(() => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.canvas-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #d9ecff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.empty-canvas {
|
.empty-canvas {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1019,6 +1371,8 @@ onMounted(() => {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
animation: fadeInUp 0.5s ease-out forwards;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
&.is-selected {
|
&.is-selected {
|
||||||
border-color: #409eff;
|
border-color: #409eff;
|
||||||
@@ -1273,5 +1627,29 @@ onMounted(() => {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-top: 1px solid #e6e6e6;
|
border-top: 1px solid #e6e6e6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 功能岛卡片动画:从下往上滑动
|
||||||
|
@keyframes slideUpIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 流程卡片动画:淡入+向上移动
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(15px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user