|
@@ -1,127 +1,127 @@
|
|
|
<template>
|
|
|
<div class="command-info">
|
|
|
<div class="header">
|
|
|
- <div class="go-back" @click="router.push(`/project-info/${crtProject!.id}`)">
|
|
|
- <van-button size="small"><van-icon name="arrow-left" />返回</van-button>
|
|
|
+ <div class="go-back">
|
|
|
+ <van-icon size="16" name="arrow-left" @click="router.push(`/project-info/${crtProject!.id}`)" />
|
|
|
+ <div class="command-name">{{ crtCommand?.name }}</div>
|
|
|
+ <van-icon size="16" name="edit" @click.stop="showEditPopup = true" />
|
|
|
</div>
|
|
|
<div class="btn-group">
|
|
|
- <van-button size="small" type="primary" @click="handleAddCommand('condition')">增加条件</van-button>
|
|
|
- <van-button size="small" type="primary" @click="handleAddCommand('delay')">增加延时</van-button>
|
|
|
- <van-button size="small" type="primary" @click="handleAddCommand('exec')">增加执行</van-button>
|
|
|
- <van-button size="small" type="danger" @click="handleAddCommand('del')">删除任务</van-button>
|
|
|
<van-icon name="question-o" size="24" @click="showHelpDoc = true" />
|
|
|
</div>
|
|
|
</div>
|
|
|
-
|
|
|
<div class="container">
|
|
|
<div class="card-group" v-if="crtCommand!.stepList.length">
|
|
|
- <div class="card" v-for="(item, index) in crtCommand!.stepList">
|
|
|
- <div class="card-header">
|
|
|
- <div class="step">
|
|
|
- 第
|
|
|
- <div class="num">{{ index + 1 }}</div>
|
|
|
- 步
|
|
|
+ <VueDraggableNext :list="crtCommand!.stepList" animation="250" :delay="500">
|
|
|
+ <div class="card" v-for="(item, index) in crtCommand!.stepList">
|
|
|
+ <div class="card-header">
|
|
|
+ <div class="step">
|
|
|
+ 第
|
|
|
+ <div class="num">{{ index + 1 }}</div>
|
|
|
+ 步
|
|
|
+ </div>
|
|
|
+ <div class="label">{{ textMap[item.type] }}</div>
|
|
|
</div>
|
|
|
- <div class="label">{{ textMap[item.type] }}</div>
|
|
|
- </div>
|
|
|
- <template v-if="item.type === 'condition' || item.type === 'exec'">
|
|
|
- <div class="item" v-for="(cItem, cIndex) in item.list">
|
|
|
- <div class="tip" v-show="cIndex !== 0 && item.type !== 'exec'">或</div>
|
|
|
- <div class="wrap">
|
|
|
- <div class="content">
|
|
|
- <template v-if="cItem.type !== 'time'">
|
|
|
- <van-field
|
|
|
- v-model="cItem.label"
|
|
|
- is-link
|
|
|
- readonly
|
|
|
- placeholder="请选择"
|
|
|
- style="width: 30%"
|
|
|
- @click="
|
|
|
- showSelect(
|
|
|
- cItem.label.includes('DI') || cItem.label.includes('AI') ? 'inputLabel' : 'outputLabel',
|
|
|
- index,
|
|
|
- cIndex,
|
|
|
- 'label'
|
|
|
- )
|
|
|
- "
|
|
|
- />
|
|
|
- <template v-if="cItem.label.includes('DI') || cItem.label.includes('DO')">
|
|
|
+ <template v-if="item.type === 'condition' || item.type === 'exec'">
|
|
|
+ <div class="item" v-for="(cItem, cIndex) in item.list">
|
|
|
+ <div class="tip" v-show="cIndex !== 0 && item.type !== 'exec'">或</div>
|
|
|
+ <div class="wrap">
|
|
|
+ <div class="content">
|
|
|
+ <template v-if="cItem.type !== 'time'">
|
|
|
<van-field
|
|
|
- v-model="cItem.value"
|
|
|
+ v-model="cItem.label"
|
|
|
is-link
|
|
|
readonly
|
|
|
placeholder="请选择"
|
|
|
- style="width: 70%"
|
|
|
- @click="showSelect('commandValue', index, cIndex, 'value')"
|
|
|
+ style="width: 30%"
|
|
|
+ @click="
|
|
|
+ showSelect(
|
|
|
+ cItem.label.includes('DI') || cItem.label.includes('AI') ? 'inputLabel' : 'outputLabel',
|
|
|
+ index,
|
|
|
+ cIndex,
|
|
|
+ 'label'
|
|
|
+ )
|
|
|
+ "
|
|
|
/>
|
|
|
+ <template v-if="cItem.label.includes('DI') || cItem.label.includes('DO')">
|
|
|
+ <van-field
|
|
|
+ v-model="cItem.value"
|
|
|
+ is-link
|
|
|
+ readonly
|
|
|
+ placeholder="请选择"
|
|
|
+ style="width: 70%"
|
|
|
+ @click="showSelect('commandValue', index, cIndex, 'value')"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ <template v-if="cItem.label.includes('AI') || cItem.label.includes('AO')">
|
|
|
+ <van-field
|
|
|
+ v-if="item.type === 'condition'"
|
|
|
+ v-model="cItem.operation"
|
|
|
+ is-link
|
|
|
+ readonly
|
|
|
+ placeholder="请选择"
|
|
|
+ style="width: 35%"
|
|
|
+ @click="showSelect('operation', index, cIndex, 'operation')"
|
|
|
+ />
|
|
|
+ <div class="operation" v-if="item.type === 'exec'">=</div>
|
|
|
+ <van-field
|
|
|
+ v-model="cItem.value"
|
|
|
+ placeholder="请输入"
|
|
|
+ :number="2.2"
|
|
|
+ type="number"
|
|
|
+ :style="{ width: item.type === 'exec' ? '60%' : '35%' }"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
</template>
|
|
|
- <template v-if="cItem.label.includes('AI') || cItem.label.includes('AO')">
|
|
|
+ <template v-else>
|
|
|
<van-field
|
|
|
- v-if="item.type === 'condition'"
|
|
|
- v-model="cItem.operation"
|
|
|
+ v-model="cItem.label"
|
|
|
is-link
|
|
|
readonly
|
|
|
placeholder="请选择"
|
|
|
- style="width: 35%"
|
|
|
- @click="showSelect('operation', index, cIndex, 'operation')"
|
|
|
+ style="width: 60%"
|
|
|
+ @click="showSelect('timeLabel', index, cIndex, 'label')"
|
|
|
/>
|
|
|
- <div class="operation" v-if="item.type === 'exec'">=</div>
|
|
|
<van-field
|
|
|
v-model="cItem.value"
|
|
|
- placeholder="请输入"
|
|
|
- type="number"
|
|
|
- :style="{ width: item.type === 'exec' ? '60%' : '35%' }"
|
|
|
+ is-link
|
|
|
+ readonly
|
|
|
+ placeholder="请选择"
|
|
|
+ style="width: 40%"
|
|
|
+ @click="showSelect('timeValue', index, cIndex, 'value')"
|
|
|
/>
|
|
|
</template>
|
|
|
- </template>
|
|
|
- <template v-else>
|
|
|
- <van-field
|
|
|
- v-model="cItem.label"
|
|
|
- is-link
|
|
|
- readonly
|
|
|
- placeholder="请选择"
|
|
|
- style="width: 60%"
|
|
|
- @click="showSelect('timeLabel', index, cIndex, 'label')"
|
|
|
- />
|
|
|
- <van-field
|
|
|
- v-model="cItem.value"
|
|
|
- is-link
|
|
|
- readonly
|
|
|
- placeholder="请选择"
|
|
|
- style="width: 40%"
|
|
|
- @click="showSelect('timeValue', index, cIndex, 'value')"
|
|
|
- />
|
|
|
- </template>
|
|
|
- </div>
|
|
|
- <div class="del-icon">
|
|
|
- <van-icon name="cross" @click="handleEditCommand('delChild', index, cIndex)" />
|
|
|
+ </div>
|
|
|
+ <div class="del-icon" v-show="item.list?.length !== 1">
|
|
|
+ <van-icon name="cross" @click="handleEditCommand('delChild', index, cIndex)" />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
+ </template>
|
|
|
|
|
|
- <template v-else-if="item.type === 'delay'">
|
|
|
- <div class="delay-content">延时<van-stepper v-model="item.value" />秒</div>
|
|
|
- </template>
|
|
|
- <div class="btn-group">
|
|
|
- <template v-if="item.type === 'condition'">
|
|
|
- <van-button size="small" type="primary" @click="handleEditCommand('condition', index)"
|
|
|
- >增加条件</van-button
|
|
|
- >
|
|
|
- <van-button size="small" type="primary" @click="handleEditCommand('delay', index)">增加定时</van-button>
|
|
|
+ <template v-else-if="item.type === 'delay'">
|
|
|
+ <div class="delay-content">延时<van-stepper v-model="item.value" />秒</div>
|
|
|
</template>
|
|
|
- <van-button
|
|
|
- v-if="item.type === 'exec'"
|
|
|
- size="small"
|
|
|
- type="primary"
|
|
|
- @click="handleEditCommand('exec', index)"
|
|
|
- >添加执行</van-button
|
|
|
- >
|
|
|
- <van-button size="small" type="danger" @click="handleEditCommand('del', index)"
|
|
|
- > 删除 </van-button
|
|
|
- >
|
|
|
+ <div class="btn-group">
|
|
|
+ <template v-if="item.type === 'condition'">
|
|
|
+ <van-button size="small" type="primary" @click="handleEditCommand('condition', index)"
|
|
|
+ >增加条件</van-button
|
|
|
+ >
|
|
|
+ <van-button size="small" type="primary" @click="handleEditCommand('delay', index)">增加定时</van-button>
|
|
|
+ </template>
|
|
|
+ <van-button
|
|
|
+ v-if="item.type === 'exec'"
|
|
|
+ size="small"
|
|
|
+ type="primary"
|
|
|
+ @click="handleEditCommand('exec', index)"
|
|
|
+ >添加执行</van-button
|
|
|
+ >
|
|
|
+ <van-button size="small" type="danger" @click="handleEditCommand('del', index)"
|
|
|
+ > 删除 </van-button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ </VueDraggableNext>
|
|
|
</div>
|
|
|
<div class="empty" v-else>当前没有可执行任务</div>
|
|
|
<div class="footer" v-if="crtCommand!.stepList.length || isExistCnt">
|
|
@@ -132,6 +132,15 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
+ <div class="handle-group" v-show="showBtnGroup">
|
|
|
+ <van-button size="small" type="primary" @click="handleAddCommand('condition')">增加条件</van-button>
|
|
|
+ <van-button size="small" type="primary" @click="handleAddCommand('delay')">增加延时</van-button>
|
|
|
+ <van-button size="small" type="primary" @click="handleAddCommand('exec')">增加执行</van-button>
|
|
|
+ <van-button size="small" type="danger" @click="handleAddCommand('del')">删除任务</van-button>
|
|
|
+ <div class="pack" @click="showBtnGroup = false"><van-icon name="arrow" /></div>
|
|
|
+ </div>
|
|
|
+ <div class="unfold" @click="showBtnGroup = true" v-show="!showBtnGroup"><van-icon name="plus" /></div>
|
|
|
+
|
|
|
<van-popup v-model:show="showOptionsPicker" destroy-on-close round position="bottom">
|
|
|
<van-picker
|
|
|
v-if="pickerType !== 'timeValue'"
|
|
@@ -159,6 +168,26 @@
|
|
|
注意:条件和延时下面需添加执行才能完成自动化控制。整个运行逻辑为从上往下顺序执行。
|
|
|
</div>
|
|
|
</van-popup>
|
|
|
+
|
|
|
+ <van-popup v-model:show="showEditPopup" round :style="{ width: '80%' }">
|
|
|
+ <div class="label" style="margin: 16px">编辑任务名称</div>
|
|
|
+ <van-form @submit="onSubmit">
|
|
|
+ <van-cell-group inset>
|
|
|
+ <van-field
|
|
|
+ border
|
|
|
+ v-model="newCommandName"
|
|
|
+ name="newCommandName"
|
|
|
+ placeholder="任务名称"
|
|
|
+ maxlength="20"
|
|
|
+ :rules="[{ required: true, message: '请填写任务名称' }]"
|
|
|
+ />
|
|
|
+ </van-cell-group>
|
|
|
+ <div class="van-button-group">
|
|
|
+ <van-button @click="handleCancel" size="small">取消</van-button>
|
|
|
+ <van-button type="primary" native-type="submit" size="small">确定</van-button>
|
|
|
+ </div>
|
|
|
+ </van-form>
|
|
|
+ </van-popup>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
@@ -169,6 +198,7 @@ import { deepClone, deepEqual } from '@/utils/tools'
|
|
|
import { storeToRefs } from 'pinia'
|
|
|
import { v4 as uuid4 } from 'uuid'
|
|
|
import { showConfirmDialog } from 'vant'
|
|
|
+import { VueDraggableNext } from 'vue-draggable-next'
|
|
|
|
|
|
const router = useRouter()
|
|
|
const route = useRoute()
|
|
@@ -188,6 +218,8 @@ watch(
|
|
|
() => route,
|
|
|
() => {
|
|
|
crtProject.value = projectList.value.find((item) => item.id === route.params.pid)
|
|
|
+ console.log(route, projectList.value)
|
|
|
+
|
|
|
if (crtProject.value) {
|
|
|
const command = crtProject.value.commandList!.find((item) => item.id === route.params.id)!
|
|
|
if (command.stepList.length) {
|
|
@@ -204,7 +236,7 @@ watch(
|
|
|
}
|
|
|
)
|
|
|
const showOptionsPicker = ref(false)
|
|
|
-const pickerValue = ref([])
|
|
|
+const pickerValue = ref<string[]>([])
|
|
|
const currentTime = ref(['00', '00'])
|
|
|
const pickerType = ref('')
|
|
|
const columnsMap: Record<string, any> = {
|
|
@@ -283,15 +315,19 @@ const showSelect = (colType: string, stepIndex: number, index: number, key: keyo
|
|
|
|
|
|
const showCalendar = ref(false)
|
|
|
const onConfirm = () => {
|
|
|
- console.log(pickerValue.value)
|
|
|
+ console.log(pickerValue.value, pickerType.value)
|
|
|
if (crtCommand.value) {
|
|
|
if (pickerType.value !== 'timeValue') {
|
|
|
if (pickerValue.value.length && pickerValue.value[0] === '自定义') {
|
|
|
showCalendar.value = true
|
|
|
} else {
|
|
|
crtCommand.value.stepList[changeInfo.stepIndex].list![changeInfo.index][changeInfo.key] = pickerValue.value[0]
|
|
|
- if (pickerType.value.includes('AI') || pickerType.value.includes('DI')) {
|
|
|
- crtCommand.value.stepList[changeInfo.stepIndex].list![changeInfo.index].value = ''
|
|
|
+
|
|
|
+ if (pickerValue.value[0].includes('AI')) {
|
|
|
+ crtCommand.value.stepList[changeInfo.stepIndex].list![changeInfo.index].value = '0'
|
|
|
+ crtCommand.value.stepList[changeInfo.stepIndex].list![changeInfo.index].operation = '='
|
|
|
+ } else if (pickerValue.value[0].includes('DI')) {
|
|
|
+ crtCommand.value.stepList[changeInfo.stepIndex].list![changeInfo.index].value = '闭合'
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
@@ -328,8 +364,8 @@ const handleEditCommand = (type: string, index: number, cIndex?: number) => {
|
|
|
crtCommand.value.stepList[index].list!.push({
|
|
|
id: '' + Date.now(),
|
|
|
type: 'input',
|
|
|
- label: 'DI1',
|
|
|
- value: '',
|
|
|
+ label: 'DI0',
|
|
|
+ value: '闭合',
|
|
|
x,
|
|
|
y
|
|
|
})
|
|
@@ -338,8 +374,8 @@ const handleEditCommand = (type: string, index: number, cIndex?: number) => {
|
|
|
crtCommand.value.stepList[index].list!.push({
|
|
|
id: '' + Date.now(),
|
|
|
type: 'output',
|
|
|
- label: 'DO1',
|
|
|
- value: '',
|
|
|
+ label: 'DO0',
|
|
|
+ value: '打开',
|
|
|
x,
|
|
|
y
|
|
|
})
|
|
@@ -350,7 +386,7 @@ const handleEditCommand = (type: string, index: number, cIndex?: number) => {
|
|
|
type: 'time',
|
|
|
label: '每天',
|
|
|
operation: '=',
|
|
|
- value: '',
|
|
|
+ value: '08:00',
|
|
|
x,
|
|
|
y
|
|
|
})
|
|
@@ -374,6 +410,7 @@ const handleAddCommand = (type: string) => {
|
|
|
})
|
|
|
.then(() => {
|
|
|
// on confirm
|
|
|
+ if (crtProject.value!.commandList.length === 1) return showFailToast('请至少保留一个任务!')
|
|
|
console.log(crtProject.value, crtCommand.value)
|
|
|
crtProject.value!.commandList = crtProject.value!.commandList.filter(
|
|
|
(item) => crtCommand.value?.id !== item.id
|
|
@@ -392,21 +429,31 @@ const handleAddCommand = (type: string) => {
|
|
|
console.log(endCMD, x)
|
|
|
|
|
|
const y = 100 + crtProject.value!.commandList.length * 2000
|
|
|
+ const id = '' + Date.now()
|
|
|
switch (type) {
|
|
|
case 'condition':
|
|
|
crtCommand.value.stepList.push({
|
|
|
- id: '' + Date.now(),
|
|
|
+ id,
|
|
|
type: 'condition',
|
|
|
- list: [],
|
|
|
+ list: [
|
|
|
+ {
|
|
|
+ id,
|
|
|
+ label: 'DI0',
|
|
|
+ value: '闭合',
|
|
|
+ type: 'input',
|
|
|
+ x,
|
|
|
+ y: y + 100
|
|
|
+ }
|
|
|
+ ],
|
|
|
x: isExistTime ? (endCMD.x || 0) + 3000 : x + 400,
|
|
|
y
|
|
|
})
|
|
|
break
|
|
|
case 'delay':
|
|
|
crtCommand.value.stepList.push({
|
|
|
- id: '' + Date.now(),
|
|
|
+ id,
|
|
|
type: 'delay',
|
|
|
- value: '1000',
|
|
|
+ value: '5',
|
|
|
unit: 's',
|
|
|
x: isExistTime ? (endCMD.x || 0) + 3000 : x + 400,
|
|
|
y
|
|
@@ -414,9 +461,18 @@ const handleAddCommand = (type: string) => {
|
|
|
break
|
|
|
case 'exec':
|
|
|
crtCommand.value.stepList.push({
|
|
|
- id: '' + Date.now(),
|
|
|
+ id,
|
|
|
type: 'exec',
|
|
|
- list: [],
|
|
|
+ list: [
|
|
|
+ {
|
|
|
+ id,
|
|
|
+ label: 'DO0',
|
|
|
+ value: '打开',
|
|
|
+ type: 'output',
|
|
|
+ x,
|
|
|
+ y: y + 100
|
|
|
+ }
|
|
|
+ ],
|
|
|
x: isExistTime ? (endCMD.x || 0) + 3000 : x + 400,
|
|
|
y
|
|
|
})
|
|
@@ -468,20 +524,51 @@ const handleSaveCommand = () => {
|
|
|
}
|
|
|
|
|
|
const showHelpDoc = ref(false)
|
|
|
+
|
|
|
+const showEditPopup = ref(false)
|
|
|
+const newCommandName = ref('')
|
|
|
+const onSubmit = (values: Record<string, string>) => {
|
|
|
+ console.log(values)
|
|
|
+ const command = crtProject.value!.commandList!.find((item) => item.id === route.params.id)!
|
|
|
+ console.log(crtProject.value?.commandList)
|
|
|
+ const isExist = crtProject.value?.commandList.some(
|
|
|
+ (item) => item.name === values.newCommandName && item.id !== command.id
|
|
|
+ )
|
|
|
+ if (isExist) {
|
|
|
+ showFailToast('任务名称已存在')
|
|
|
+ return
|
|
|
+ } else {
|
|
|
+ command!.name = values.newCommandName
|
|
|
+ crtCommand.value!.name = values.newCommandName
|
|
|
+ handleCancel()
|
|
|
+ }
|
|
|
+}
|
|
|
+const handleCancel = async () => {
|
|
|
+ showEditPopup.value = false
|
|
|
+ await nextTick()
|
|
|
+ newCommandName.value = ''
|
|
|
+}
|
|
|
+const showBtnGroup = ref(true)
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.command-info {
|
|
|
- padding: 16px 0;
|
|
|
height: 100%;
|
|
|
overflow: hidden;
|
|
|
+ user-select: none;
|
|
|
}
|
|
|
.header {
|
|
|
- padding: 0 16px;
|
|
|
+ padding: 16px;
|
|
|
margin-bottom: 16px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
+ background-color: #fff;
|
|
|
+}
|
|
|
+.go-back {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
}
|
|
|
.btn-group {
|
|
|
display: flex;
|
|
@@ -504,7 +591,7 @@ const showHelpDoc = ref(false)
|
|
|
}
|
|
|
.container {
|
|
|
padding: 0 16px;
|
|
|
- height: calc(100% - 34px);
|
|
|
+ height: calc(100% - 86px);
|
|
|
overflow: auto;
|
|
|
}
|
|
|
.step {
|
|
@@ -589,4 +676,49 @@ const showHelpDoc = ref(false)
|
|
|
padding: 0 24px 24px;
|
|
|
line-height: 2;
|
|
|
}
|
|
|
+.van-button-group {
|
|
|
+ margin: 16px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+.handle-group {
|
|
|
+ position: fixed;
|
|
|
+ bottom: 64px;
|
|
|
+ right: 16px;
|
|
|
+ padding: 16px;
|
|
|
+ background-color: rgba(255, 255, 255, 0.7);
|
|
|
+ border-radius: 4px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ max-width: 90%;
|
|
|
+ transition: all 0.3s;
|
|
|
+}
|
|
|
+.unfold {
|
|
|
+ position: fixed;
|
|
|
+ bottom: 72px;
|
|
|
+ right: 16px;
|
|
|
+ background-color: rgba(255, 255, 255, 0.7);
|
|
|
+ border-radius: 4px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ font-size: 28px;
|
|
|
+ transition: all 0.3s;
|
|
|
+}
|
|
|
+.pack {
|
|
|
+ display: flex;
|
|
|
+ width: 28px;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ border-radius: 2px;
|
|
|
+}
|
|
|
+.pack:active {
|
|
|
+ background: #efefef;
|
|
|
+}
|
|
|
</style>
|