Bladeren bron

update: 新增节点出参入参复杂结构转换功能

liuyang 2 jaren geleden
bovenliggende
commit
2390928e7e

+ 13 - 0
package-lock.json

@ -4539,6 +4539,11 @@
4539 4539
        }
4540 4540
      }
4541 4541
    },
4542
    "sortablejs": {
4543
      "version": "1.14.0",
4544
      "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
4545
      "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
4546
    },
4542 4547
    "source-map": {
4543 4548
      "version": "0.6.1",
4544 4549
      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -5581,6 +5586,14 @@
5581 5586
        "@volar/vue-typescript": "1.2.0"
5582 5587
      }
5583 5588
    },
5589
    "vuedraggable": {
5590
      "version": "4.1.0",
5591
      "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
5592
      "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
5593
      "requires": {
5594
        "sortablejs": "1.14.0"
5595
      }
5596
    },
5584 5597
    "vuex": {
5585 5598
      "version": "4.1.0",
5586 5599
      "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz",

+ 1 - 0
package.json

@ -44,6 +44,7 @@
44 44
    "vite-plugin-svg-icons": "^2.0.1",
45 45
    "vue": "^3.2.44",
46 46
    "vue-router": "^4.1.6",
47
    "vuedraggable": "^4.1.0",
47 48
    "vuex": "^4.1.0",
48 49
    "xlsx": "0.16.0"
49 50
  },

+ 0 - 5
src/components/index.ts

@ -1,5 +0,0 @@
1
import IpuTable from './table/IpuTable.vue';
2
3
export function setupGlobalComponents(app: any) {
4
  app.component('IpuTable', IpuTable);
5
}

+ 0 - 206
src/components/table/IpuTable.vue

@ -1,206 +0,0 @@
1
<template>
2
  <el-table
3
    ref="ipuTable"
4
    class="g-table"
5
    :data="tableData"
6
    :header-cell-style="headerCellStyle"
7
    :cell-style="cellStyle"
8
    v-bind="tableAttrs"
9
    @current-change="handleCurrentRowChange"
10
    @selection-change="handleSelectionChange"
11
  >
12
    <template #empty>
13
      <el-empty description="暂无数据"></el-empty>
14
    </template>
15
    <el-table-column
16
      v-if="isCheck"
17
      type="selection"
18
      width="55"
19
    ></el-table-column>
20
    <template v-for="header in tableHeader" :key="header?.name">
21
      <!-- 自自定义按钮 -->
22
      <el-table-column
23
        v-if="header?.type === 'selection'"
24
        type="selection"
25
        width="55"
26
      ></el-table-column>
27
      <el-table-column
28
        v-else-if="header?.type"
29
        :prop="header?.name"
30
        :label="header?.label"
31
        :width="header?.width"
32
        :min-width="header?.minWidth"
33
        :sortable="header?.sortable || false"
34
        :fixed="header?.fixed || false"
35
        :align="header?.position || 'left'"
36
      >
37
        <template #default="scope">
38
          <!-- <component :is="header.component" :value="scope.row"></component> -->
39
          <!-- 自定义按钮  -->
40
          <table-column
41
            :header="header"
42
            :row="scope.row"
43
            :is-disable="isDisable"
44
            @change-event="changeEvent"
45
            @blur-event="blurEvent"
46
            @svgEvent="svgEvent"
47
          ></table-column>
48
          <!-- 按钮 -->
49
          <slot :name="header.name" :row="scope.row"></slot>
50
        </template>
51
      </el-table-column>
52
      <!-- 普通样式 -->
53
      <el-table-column
54
        v-else
55
        :prop="header?.name"
56
        show-overflow-tooltip
57
        :label="header?.label"
58
        :width="header?.width"
59
        :min-width="header?.minWidth"
60
        :sortable="header?.sortable || false"
61
        :align="header?.position || 'left'"
62
        :fixed="header?.fixed || false"
63
      >
64
        <template #default="scope">
65
          {{
66
            scope.row[header?.name] || scope.row[header?.name] === 0
67
              ? scope.row[header?.name]
68
              : '-'
69
          }}
70
        </template>
71
      </el-table-column>
72
    </template>
73
  </el-table>
74
</template>
75
76
<script setup>
77
import { ref } from 'vue';
78
import TableColumn from './TableColumn.vue';
79
80
defineProps({
81
  tableHeader: {
82
    type: Array,
83
    default: () => []
84
  },
85
  tableData: {
86
    type: Array,
87
    default: () => []
88
  },
89
  tableAttrs: {
90
    type: Object,
91
    default: () => {}
92
  },
93
  isCheck: {
94
    type: Boolean,
95
    default: () => false
96
  },
97
  isDisable: {
98
    type: Boolean,
99
    default: true
100
  }
101
});
102
const emits = defineEmits([
103
  'changeEvent',
104
  'blurEvent',
105
  'handleCurrentRowChange',
106
  'svgEvent',
107
  'handleSelectionChange'
108
]);
109
const cellStyle = ref({
110
  padding: '0 2px',
111
  height: '60px',
112
  'line-height': '60px',
113
  'font-size': '14px'
114
});
115
const headerCellStyle = ref({
116
  padding: '0 2px',
117
  height: '39px',
118
  'font-weight': '600',
119
  'font-size': '14px',
120
  'line-height': '39px',
121
  color: '#000',
122
  'background-color': '#fafafa'
123
});
124
const changeEvent = (form) => {
125
  emits('changeEvent', form);
126
};
127
128
// 触发blur事件
129
const blurEvent = (form) => {
130
  emits('blurEvent', form);
131
};
132
const ipuTable = ref(null);
133
// 选中当前行
134
function handleCurrentRowChange(row) {
135
  // console.log(row)
136
  emits('handleCurrentRowChange', row);
137
}
138
139
function handleSelectionChange(row) {
140
  emits('handleSelectionChange', row);
141
}
142
143
function svgEvent(row) {
144
  emits('svgEvent', row);
145
}
146
147
function setCurrent(rows) {
148
  if (rows) {
149
    rows.forEach((row) => {
150
      ipuTable.value?.toggleRowSelection(row);
151
    });
152
  }
153
}
154
155
function setSingleCurrent(row) {
156
  if (row) {
157
    setTimeout(() => {
158
      ipuTable.value?.setCurrentRow(row);
159
    }, 10);
160
  }
161
}
162
// 反选
163
164
defineExpose({
165
  ipuTable,
166
  setCurrent,
167
  setSingleCurrent
168
});
169
</script>
170
171
<style scoped lang="scss">
172
:deep(.el-table__inner-wrapper) {
173
  font-size: 14px;
174
  .el-table__header-wrapper {
175
    font-size: 14px;
176
    thead tr {
177
      font-size: 14px;
178
    }
179
  }
180
}
181
.el-table {
182
  width: 100%;
183
}
184
// 消除table横向滚动条
185
// :deep(.is-horizontal){
186
//   .el-scrollbar__thumb{
187
//     display: none;
188
//   }
189
// }
190
:deep(.el-tooltip) {
191
  display: flex;
192
}
193
:deep(.current-row) {
194
  position: relative;
195
  background: rgba(25, 130, 255, 0.04);
196
  &:after {
197
    content: '';
198
    position: absolute;
199
    left: 0;
200
    width: 4px;
201
    height: 100%;
202
    background: #2d98ff;
203
    z-index: 1000;
204
  }
205
}
206
</style>

+ 0 - 285
src/components/table/TableColumn.vue

@ -1,285 +0,0 @@
1
<!-- eslint-disable vue/no-mutating-props -->
2
<template>
3
  <!-- input -->
4
  <el-input
5
    v-if="header?.type === 'input'"
6
    v-model="row[header.name]"
7
    v-bind="header?.attrs"
8
    @blur="blurEvent(header)"
9
    @change="changeEvent(header)"
10
  ></el-input>
11
12
  <!-- select -->
13
  <common-select
14
    v-if="header?.type === 'select'"
15
    v-model="row[header.name]"
16
    v-bind="header?.attrs"
17
    @change="changeEvent(header)"
18
  ></common-select>
19
20
  <div
21
    :class="[
22
      header?.attrs?.isShow ? 'color-picker-wrapper' : 'color-picker-noWrapper'
23
    ]"
24
  >
25
    <!-- 取色器 color-picker -->
26
    <el-color-picker
27
      v-if="header?.type === 'color-picker'"
28
      v-bind="header?.attrs"
29
      v-model="row[header.name]"
30
      :predefine="predefineColors"
31
      @change="changeEvent(header)"
32
    ></el-color-picker>
33
    <span v-if="header?.attrs?.isShow" class="color-des">
34
      {{ row[header.name].slice(1) }}
35
    </span>
36
  </div>
37
38
  <!--时间选择器-->
39
  <el-date-picker
40
    v-if="header?.type === 'date-picker'"
41
    v-bind="header?.attrs"
42
    v-model="row[header.name]"
43
    @change="changeEvent(header)"
44
  ></el-date-picker>
45
46
  <!-- 数字 -->
47
  <el-input-num
48
    v-if="header?.type === 'input-num'"
49
    v-bind="header?.attrs"
50
    v-model="row[header.name]"
51
    controls-position="right"
52
    @change="changeEvent(header)"
53
    @blur="blurEvent(header)"
54
  ></el-input-num>
55
56
  <!-- radio -->
57
  <el-radio
58
    v-if="header?.type === 'radio'"
59
    v-bind="header?.attrs"
60
    v-model="row[header.name]"
61
    @change="changeEvent(header)"
62
  ></el-radio>
63
  <!-- switch -->
64
  <el-switch
65
    v-if="header?.type === 'switch'"
66
    v-bind="header?.attrs"
67
    v-model="row[header.name]"
68
    :radios="row[header.name]"
69
    @change="changeEvent(header)"
70
  ></el-switch>
71
72
  <!-- checkox -->
73
  <el-checkbox
74
    v-if="header?.type === 'checkbox'"
75
    v-bind="header?.attrs"
76
    v-model="row[header.name]"
77
    @change="changeEvent(header)"
78
  ></el-checkbox>
79
80
  <!-- 接收传入的行间样式修改颜色 -->
81
  <span
82
    v-if="header?.type === 'color'"
83
    :class="row[header?.name]"
84
    v-bind="header?.attrs"
85
  ></span>
86
87
  <!-- linkPage回调由父组件传入,父组件可以自定义回调的执行 -->
88
  <span v-if="header?.type === 'link'" class="link" @click="linkEvent(row)">
89
    {{ row[header?.name] }}
90
  </span>
91
92
  <!-- 在线状态 在线与离线 -->
93
  <el-tag
94
    v-if="header?.type === 'onlineStatus'"
95
    v-model="row[header?.name]"
96
    :type="
97
      row[header?.name] === '在线' || row[header?.name] === '启用'
98
        ? 'success'
99
        : 'danger'
100
    "
101
    effect="light"
102
    round
103
  >
104
    <common-icon
105
      :name="
106
        row[header?.name] === '在线' || row[header?.name] === '启用'
107
          ? 'success'
108
          : 'danger'
109
      "
110
      :size="header?.attrs?.size"
111
      class="point"
112
    ></common-icon>
113
    <span class="svg-content">
114
      {{ row[header?.name] }}
115
    </span>
116
  </el-tag>
117
118
  <!-- 已发布与未发布 -->
119
  <el-tag
120
    v-if="header?.type === 'publishStatus'"
121
    v-model="row[header?.name]"
122
    :type="row[header?.name] === '已发布' ? 'success' : 'info'"
123
    effect="light"
124
    round
125
  >
126
    <common-icon
127
      :name="row[header?.name] === '已发布' ? 'success' : 'info'"
128
      :size="header?.attrs?.size"
129
      class="point"
130
    ></common-icon>
131
    <span class="svg-content">
132
      {{ row[header?.name] }}
133
    </span>
134
  </el-tag>
135
136
  <!-- 文字展示 -->
137
  <div v-if="header?.type === 'link-text'" class="link-text">
138
    <span class="link-left">{{ row[header.name] }}</span>
139
    <span v-if="!header?.svg" class="link-right" @click="checkDevice(row)">
140
      {{ header?.description }}
141
    </span>
142
    <common-icon
143
      v-if="header?.svg && row['valueTypeDisplay'] === '数字'"
144
      class="link-right my-svg"
145
      :name="header?.svg"
146
      :class="isDisable ? 'disabled' : ''"
147
      @click="svgEvent(row)"
148
    ></common-icon>
149
  </div>
150
</template>
151
152
<script setup>
153
import { ref } from 'vue';
154
155
const predefineColors = ref(['#D9D9D9', '#5AC285', '#ED9248', '#E53E3E']);
156
defineProps({
157
  header: {
158
    type: Object,
159
    default: () => {}
160
  },
161
  row: {
162
    type: Object,
163
    default: () => {}
164
  },
165
  isDisable: {
166
    type: Boolean,
167
    default: true
168
  }
169
});
170
const emits = defineEmits([
171
  'changeEvent',
172
  'blurEvent',
173
  'linkEvent',
174
  'svgEvent',
175
  'checkDevice'
176
]);
177
178
const changeEvent = (form) => {
179
  emits('changeEvent', form);
180
};
181
182
// 触发blur事件
183
const blurEvent = (form) => {
184
  emits('blurEvent', form);
185
};
186
187
// 触发link事件
188
const linkEvent = (row) => {
189
  emits('linkEvent', row);
190
};
191
192
// 触发svg事件
193
const svgEvent = (row) => {
194
  emits('svgEvent', row);
195
};
196
197
// 查看设备
198
function checkDevice(row) {
199
  emits('checkDevice', row);
200
}
201
</script>
202
203
<style scoped lang="scss">
204
.red {
205
  width: 44px;
206
  height: 10px;
207
  display: inline-block;
208
  background: #f34871;
209
}
210
.blue {
211
  width: 44px;
212
  height: 10px;
213
  display: inline-block;
214
  background: #2d98ff;
215
}
216
.point {
217
  margin: 0 4px 2.5px 0;
218
}
219
.link-text {
220
  width: 85px;
221
  display: flex;
222
  justify-content: space-between;
223
  .link-left,
224
  .link-right {
225
    font-size: 14px;
226
  }
227
  .link-right {
228
    cursor: pointer;
229
    color: #2d98ff;
230
  }
231
}
232
.color-picker-wrapper {
233
  width: 101px;
234
  border: 1px solid rgba(0, 0, 0, 0.15);
235
  border-radius: 6px;
236
  display: flex;
237
  justify-content: space-between;
238
  align-items: center;
239
  box-sizing: border-box;
240
  padding-right: 5px;
241
}
242
.color-picker-noWrapper {
243
  display: inline-block;
244
}
245
.color-picker-wrapper {
246
  :deep(.el-color-picker__trigger) {
247
    padding: 1px;
248
    border: none;
249
    .el-color-picker__color {
250
      border: none;
251
    }
252
    .el-color-picker__color-inner {
253
      border-radius: 5px;
254
      .el-icon {
255
        font-size: 0;
256
      }
257
    }
258
  }
259
  .color-des {
260
    font-size: 14px;
261
  }
262
}
263
.ipu-svg {
264
  width: 6px;
265
  height: 6px;
266
  margin-top: 5px;
267
}
268
.svg-content {
269
  margin-left: 4px;
270
}
271
.link {
272
  color: #2d98ff;
273
  cursor: pointer;
274
}
275
.my-svg {
276
  width: 20px;
277
  height: 20px;
278
  position: relative;
279
  bottom: 4px;
280
}
281
.disabled {
282
  color: #cccccc !important;
283
  cursor: not-allowed !important;
284
}
285
</style>

+ 14 - 0
src/router.ts

@ -85,6 +85,20 @@ const routes: Array<RouteRecordRaw> = [
85 85
        component: () => import('./views/sub-flow/SubFlow.vue')
86 86
      }
87 87
    ]
88
  },
89
  {
90
    path: '',
91
    component: () => import('./views/sub-flow/Container.vue'),
92
    children: [
93
      {
94
        path: '/manager',
95
        name: 'Manager',
96
        component: () => import('./views/manager/Index.vue'),
97
        meta: {
98
          breadcrumb: [{ name: '上线管理' }]
99
        }
100
      }
101
    ]
88 102
  }
89 103
];
90 104
const router = createRouter({

+ 1 - 1
src/views/_components/_flow/nodes.js

@ -60,7 +60,7 @@ export default {
60 60
          id: 'subprocess',
61 61
          label: '子流程',
62 62
          type: 'logic',
63
          icon: 'flow-subprocess',
63
          icon: 'tool-subprocess',
64 64
          isStartNode: false,
65 65
          attrsData: {}
66 66
        }

+ 204 - 0
src/views/_components/drag/DragComments copy.vue

@ -0,0 +1,204 @@
1
<template>
2
  <div class="container">
3
    <!-- 左侧 el-tree 组件 -->
4
    <!-- <el-tree
5
      ref="tree"
6
      class="left"
7
      :data="treeData"
8
      :props="treeProps"
9
      :draggable="true"
10
      @node-drag-start="handleNodeDragStart"
11
      @node-drag-end="handleNodeDragEnd"
12
      @node-drop="handleNodeDrop"
13
    ></el-tree> -->
14
    <!-- <div class="left">
15
      <draggable
16
        :list="targetData"
17
        item-key="dataIndex"
18
        :group="{ name: 'source', pull: 'clone' }"
19
        :sort="false"
20
      >
21
        <template #item="{ element }">
22
          <div class="list-group-item">
23
            <span>{{ element.label }}</span>
24
          </div>
25
        </template>
26
      </draggable>
27
    </div> -->
28
    <el-tree
29
      ref="tree"
30
      class="left"
31
      :data="treeData"
32
      :props="treeProps"
33
      :draggable="true"
34
      @node-drag-start="handleNodeDragStart"
35
      @node-drag-end="handleNodeDragEnd"
36
      @node-drop="handleNodeDrop"
37
    >
38
      <template #default="{ node, data }">
39
        <draggable :list="treeData" :group="{ name: 'source' }">
40
          <template #item="{ element }">
41
            <template v-if="data.children?.length">
42
              <draggable :list="element.children" :group="{ name: 'source' }">
43
                <template #item="scope">
44
                  {{ scope.element.label }}
45
                </template>
46
              </draggable>
47
            </template>
48
            <template v-else>
49
              <span>{{ node.label }}</span>
50
            </template>
51
          </template>
52
        </draggable>
53
      </template>
54
    </el-tree>
55
56
    <!-- 右侧 tag 列表 -->
57
    <div class="right">
58
      <draggable
59
        :list="sourceData"
60
        item-key="dataIndex"
61
        :group="{ name: 'source', pull: 'clone' }"
62
        :sort="false"
63
      >
64
        <template #item="{ element }">
65
          <div class="list-group-item">
66
            <span>{{ element.label }}</span>
67
          </div>
68
        </template>
69
      </draggable>
70
    </div>
71
  </div>
72
</template>
73
74
<script>
75
import { ref } from 'vue';
76
import draggable from 'vuedraggable';
77
78
export default {
79
  components: {
80
    draggable
81
  },
82
  setup() {
83
    const sourceData = ref([
84
      { id: 1, label: 'Tag 1' },
85
      { id: 2, label: 'Tag 2' },
86
      { id: 3, label: 'Tag 3' },
87
      { id: 4, label: 'Tag 4' },
88
      { id: 5, label: 'Tag 5' }
89
    ]);
90
91
    const targetData = ref([{ id: 10, label: 'Tag 11' }]);
92
93
    const treeData = ref([
94
      {
95
        id: 1,
96
        label: 'Node 1',
97
        children: [
98
          {
99
            id: 2,
100
            label: 'Node 1-1',
101
            children: [
102
              {
103
                id: 3,
104
                label: 'Node 1-1-1',
105
                children: []
106
              }
107
            ]
108
          },
109
          {
110
            id: 4,
111
            label: 'Node 1-2',
112
            children: []
113
          }
114
        ]
115
      }
116
    ]);
117
118
    const treeProps = ref({
119
      label: 'label',
120
      children: 'children'
121
    });
122
123
    const handleNodeDragStart = (node, component) => {
124
      // 获取当前拖拽节点在原节点的位置索引
125
      const oldIndex =
126
        component.$parent.$parent.$el.getAttribute('data-oldindex');
127
      // 将位置索引存储在拖拽节点的 dataTransfer 中
128
      component.$parent.$parent.$el.setAttribute(
129
        'data-oldindex',
130
        node.parent.children.indexOf(node)
131
      );
132
      component.$parent.$parent.$el.setAttribute('data-index', oldIndex);
133
    };
134
135
    const handleNodeDragEnd = (node, component) => {
136
      // 重置拖拽节点的 dataTransfer
137
      component.$parent.$parent.$el.removeAttribute('data-oldindex');
138
      component.$parent.$parent.$el.removeAttribute('data-index');
139
    };
140
141
    const handleNodeDrop = (draggingNode, dropNode, dropType) => {
142
      if (dropType === 'inner') {
143
        // 如果拖拽的节点放置在目标节点的内部,则将拖拽节点移动到目标节点的子节点中
144
        const index = draggingNode.parent.children.indexOf(draggingNode);
145
        draggingNode.parent.children.splice(index, 1);
146
        dropNode.children.push(draggingNode);
147
      } else {
148
        // 如果拖拽的节点放置在目标节点的前面或后面,则将拖拽节点移动到目标节点的前面或后面
149
        const oldIndex = Number(
150
          draggingNode.component.$parent.$parent.$el.getAttribute('data-index')
151
        );
152
        const newIndex = dropNode.parent.children.indexOf(dropNode);
153
        if (newIndex > oldIndex) {
154
          dropNode.parent.children.splice(newIndex + 1, 0, draggingNode);
155
          draggingNode.parent.children.splice(oldIndex, 1);
156
        } else {
157
          dropNode.parent.children.splice(newIndex, 0, draggingNode);
158
          draggingNode.parent.children.splice(oldIndex + 1, 1);
159
        }
160
      }
161
    };
162
    return {
163
      sourceData,
164
      targetData,
165
      treeData,
166
      treeProps,
167
      handleNodeDragStart,
168
      handleNodeDragEnd,
169
      handleNodeDrop
170
    };
171
  }
172
};
173
</script>
174
175
<style>
176
.container {
177
  display: flex;
178
}
179
180
.left {
181
  width: 50%;
182
  height: 400px;
183
  margin-right: 10px;
184
}
185
186
.right {
187
  width: 50%;
188
  height: 400px;
189
  background-color: #f5f5f5;
190
}
191
192
.tag-item {
193
  display: inline-block;
194
  width: 100px;
195
  height: 50px;
196
  margin: 10px;
197
  background-color: #fff;
198
  border: 1px solid #ccc;
199
  border-radius: 4px;
200
  text-align: center;
201
  line-height: 50px;
202
  cursor: move;
203
}
204
</style>

+ 484 - 0
src/views/_components/drag/DragComments.vue

@ -0,0 +1,484 @@
1
<template>
2
  <div class="__data-conversion">
3
    <!--左树-->
4
    <div class="__left">
5
      <div class="__up">
6
        <h5 class="title">属性树形结构</h5>
7
        <div class="tree-content">
8
          <el-tree
9
            :allow-drop="allowDrop"
10
            :allow-drag="allowDrag"
11
            :data="treeData"
12
            :highlight-current="true"
13
            draggable
14
            default-expand-all
15
            node-key="'id'"
16
            @node-click="getCurrentNode"
17
            @node-drag-start="handleDragStart"
18
            @node-drag-enter="handleDragEnter"
19
            @node-drag-leave="handleDragLeave"
20
            @node-drag-over="handleDragOver"
21
            @node-drag-end="handleDragEnd"
22
            @node-drop="handleDrop"
23
          >
24
            <template #default="{ node, data }">
25
              <div class="tree-item">
26
                <span class="label">{{ data.label }}</span>
27
                <span class="type" :class="`${data.type}-color`">
28
                  {{ data.type }}
29
                </span>
30
                <span class="type" :class="`${data.type}-color`">
31
                  {{ data.name }}
32
                </span>
33
                <el-input
34
                  v-model="data.alias"
35
                  class="tree-int"
36
                  size="small"
37
                ></el-input>
38
                <common-icon
39
                  name="common-shanchu"
40
                  @click.stop="removeItem(node, data)"
41
                ></common-icon>
42
              </div>
43
            </template>
44
          </el-tree>
45
        </div>
46
      </div>
47
48
      <div class="__down">
49
        <h5 class="title">属性内容</h5>
50
        <div class="attr-content">
51
          <JsonEditor
52
            v-model="jsonData"
53
            class="editor"
54
            current-mode="code"
55
            :mode-list="['code']"
56
            @blur="validate"
57
          ></JsonEditor>
58
        </div>
59
      </div>
60
    </div>
61
    <!--右列表-->
62
    <div class="__right">
63
      <h5 class="title">属性</h5>
64
      <ul class="tag-list">
65
        <li v-for="(item, index) in tagList" :key="index">
66
          <h5 class="tag-title">{{ item.label }}</h5>
67
          <div class="tag-content">
68
            <template v-for="(tag, tagIndex) in item.tags" :key="tagIndex">
69
              <span
70
                class="tag"
71
                :class="`${tag?.['type']}-color`"
72
                @click.stop="handleClickTag(tag)"
73
              >
74
                {{ tag.label }}
75
              </span>
76
            </template>
77
          </div>
78
        </li>
79
      </ul>
80
    </div>
81
  </div>
82
</template>
83
84
<script setup lang="ts">
85
import { ref, watch, getCurrentInstance } from 'vue';
86
import JsonEditor from 'json-editor-vue3';
87
import { v4 as uuidv4 } from 'uuid';
88
89
const { proxy } = getCurrentInstance();
90
91
const tagList = ref<any>([
92
  {
93
    label: '字符串',
94
    type: 'string',
95
    id: 1,
96
    tags: [
97
      { label: '姓名', name: 'name', type: 'string', id: 2 },
98
      { label: '电话号码', name: 'tel', type: 'string', id: 3 },
99
      { label: '邮箱', name: 'email', type: 'string', id: 4 }
100
    ]
101
  },
102
  {
103
    label: '整数',
104
    type: 'int',
105
    id: 5,
106
    tags: [
107
      { label: '年龄', name: 'age', type: 'int', id: 6 },
108
      { label: '性别', name: 'sex', type: 'int', id: 7 },
109
      { label: '班级', name: 'class', type: 'int', id: 8 }
110
    ]
111
  },
112
113
  {
114
    label: '布尔类型',
115
    type: 'boolean',
116
    id: 9,
117
    tags: [
118
      { label: '婚姻状况', name: 'marry', type: 'boolean', id: 10 },
119
      { label: '生育状况', name: 'sex', type: 'boolean', id: 11 }
120
    ]
121
  },
122
123
  {
124
    label: '对象',
125
    type: 'object',
126
    id: 12,
127
    tags: [
128
      { label: '结果', name: 'result', type: 'object', id: 13 },
129
      { label: '学生', name: 'student', type: 'object', id: 14 },
130
      { label: '员工', name: 'staff', type: 'object', id: 15 }
131
    ]
132
  }
133
]);
134
const treeData = ref<any>([]);
135
const jsonData = ref<any>({});
136
137
const currentNode = ref<any>({});
138
139
/**
140
 * el-tree拖拽start
141
 */
142
143
// 是否可被插入
144
const allowDrop = (draggingNode: any, dropNode: any, type: any) => {
145
  console.log(draggingNode);
146
  if (type === 'inner') {
147
    if (dropNode.data.type === 'object' || dropNode.data.type === 'array') {
148
      return true;
149
    }
150
    return false;
151
  }
152
  // 被进入的节点如果是object或者array时,允许被传入,否则禁止拖进
153
154
  return true;
155
};
156
157
// 是否可被拖拽
158
const allowDrag = (draggingNode: any) => {
159
  console.log(draggingNode);
160
  return true;
161
};
162
163
function handleDragStart() {}
164
function handleDragEnter() {}
165
function handleDragLeave() {}
166
function handleDragOver() {}
167
function handleDragEnd() {}
168
function handleDrop() {}
169
170
// 获取当前节点
171
function getCurrentNode(node: any) {
172
  console.log(node);
173
  currentNode.value = node;
174
}
175
176
/**
177
 * el-tree拖拽end
178
 */
179
180
// 标签点击,添加至左边树
181
function handleClickTag(tag: any) {
182
  console.log(tag);
183
  const newChild = {
184
    label: tag.label,
185
    name: tag.name,
186
    alias: '',
187
    id: uuidv4(),
188
    type: tag.type,
189
    children: []
190
  };
191
  if (currentNode.value?.type) {
192
    if (
193
      currentNode.value?.type === 'object' ||
194
      currentNode.value?.type === 'array'
195
    ) {
196
      currentNode.value.children.push(newChild);
197
      treeData.value = [...treeData.value];
198
    } else {
199
      proxy.$message.error(`${currentNode.value?.type}类型不能有子类!`);
200
    }
201
  } else {
202
    treeData.value.push(newChild);
203
  }
204
}
205
206
// 删除树结构
207
function removeItem(node: any, data: any) {
208
  if (data.children?.length) {
209
    proxy.$message.error('请先删除子类!');
210
    return;
211
  }
212
  const { parent } = node;
213
  const child = parent.data.children || parent.data;
214
215
  const index = child.findIndex((d: any) => d.id === data.id);
216
  child.splice(index, 1);
217
  treeData.value = [...treeData.value];
218
}
219
function assignment(type: any) {
220
  let r;
221
  switch (type) {
222
    case 'object':
223
      r = {};
224
      break;
225
    case 'string':
226
      r = '';
227
      break;
228
    case 'int':
229
      r = 0;
230
      break;
231
    case 'boolean':
232
      r = false;
233
      break;
234
    case 'array':
235
      r = [];
236
      break;
237
    case 'long':
238
      r = 0;
239
      break;
240
    case 'date':
241
      r = '';
242
      break;
243
    case 'datetime':
244
      r = '';
245
      break;
246
    case 'decimal':
247
      r = 0.0;
248
      break;
249
    default:
250
      r = undefined;
251
      break;
252
  }
253
  return r;
254
}
255
256
// 数据处理
257
function tree2json(data: any) {
258
  const obj = {};
259
  data.forEach((item: any) => {
260
    if (item.children?.length) {
261
      obj[item.name] = tree2json(item.children);
262
    } else {
263
      obj[item.name] = assignment(item.type);
264
    }
265
  });
266
  return obj;
267
}
268
function validate() {}
269
watch(
270
  treeData.value,
271
  () => {
272
    if (treeData.value?.length) {
273
      jsonData.value = tree2json(treeData.value);
274
    } else {
275
      currentNode.value = {};
276
    }
277
  },
278
  { deep: true }
279
);
280
</script>
281
282
<style scoped lang="scss">
283
.__data-conversion {
284
  display: flex;
285
  justify-content: space-between;
286
  height: calc(100% - 60px);
287
  padding: 20px;
288
  box-sizing: border-box;
289
  .__left {
290
    width: calc(60% - 7px);
291
    .__up {
292
      height: calc(50% - 20px);
293
294
      .tree-content {
295
        background: #ffffff;
296
        border: 1px solid rgba(0, 0, 0, 0.15);
297
        border-radius: 4px;
298
        height: calc(100% - 20px);
299
300
        padding: 15px;
301
        box-sizing: border-box;
302
        overflow-y: auto;
303
      }
304
    }
305
    .__down {
306
      height: calc(50% - 20px);
307
      margin-top: 20px;
308
      .attr-content {
309
        border: 1px solid rgba(0, 0, 0, 0.15);
310
        background: #ffffff;
311
        border-radius: 4px;
312
        height: calc(100%);
313
        .editor {
314
          height: calc(100%);
315
        }
316
      }
317
    }
318
  }
319
  .__right {
320
    width: calc(40% - 8px);
321
    height: 100%;
322
323
    .tag-list {
324
      background-color: #ffffff;
325
      border: 1px solid rgba(0, 0, 0, 0.15);
326
      border-radius: 4px;
327
      height: calc(100% - 20px);
328
      padding: 15px;
329
      box-sizing: border-box;
330
      overflow-y: auto;
331
      .tag-title {
332
        line-height: 32px;
333
      }
334
      .tag-content {
335
        display: flex;
336
        flex-direction: row;
337
        justify-content: flex-start;
338
        flex-wrap: wrap;
339
340
        .tag {
341
          height: 32px;
342
          margin-right: 12px;
343
          line-height: 32px;
344
          padding: 0 12px;
345
          border-radius: 4px;
346
          position: relative;
347
          padding-left: 26px;
348
          cursor: pointer;
349
          &:after {
350
            content: '';
351
            position: absolute;
352
            width: 6px;
353
            height: 6px;
354
            left: 12px;
355
            top: 13px;
356
            border-radius: 50%;
357
          }
358
        }
359
        .int-color {
360
          color: #169bfa;
361
          background: rgba(22, 155, 250, 0.1);
362
          &:after {
363
            background: #169bfa;
364
          }
365
        }
366
        .string-color {
367
          color: #31cf9a;
368
          background: rgba(49, 207, 154, 0.1);
369
          &:after {
370
            background: #31cf9a;
371
          }
372
        }
373
        .boolean-color {
374
          color: #31cf9a;
375
          background: rgba(49, 207, 154, 0.1);
376
          &:after {
377
            background: #31cf9a;
378
          }
379
        }
380
        .object-color {
381
          color: #ff956f;
382
          background: rgba(255, 149, 111, 0.1);
383
          &:after {
384
            background: #ff956f;
385
          }
386
        }
387
        .array-color {
388
          color: #ffb95b;
389
          background: rgba(255, 185, 91, 0.1);
390
          &:after {
391
            background: #ffb95b;
392
          }
393
        }
394
        .decimal-color {
395
          color: #21c5db;
396
          background: rgba(33, 197, 219, 0.1);
397
          &:after {
398
            background: #21c5db;
399
          }
400
        }
401
        .date-color {
402
          color: #c084fc;
403
          background: rgba(192, 132, 252, 0.1);
404
          &:after {
405
            background: #c084fc;
406
          }
407
        }
408
        .long-color {
409
          color: #ff5f5f;
410
          background: rgba(255, 95, 95, 0.1);
411
          &:after {
412
            background: #ff5f5f;
413
          }
414
        }
415
        .datetime-color {
416
          color: #f471b7;
417
          background: rgba(244, 113, 183, 0.1);
418
          &:after {
419
            background: #f471b7;
420
          }
421
        }
422
      }
423
    }
424
  }
425
}
426
427
.title {
428
  line-height: 32px;
429
  color: #606266;
430
  font-weight: 400;
431
}
432
.tree-item {
433
  display: flex;
434
  justify-content: space-between;
435
  align-items: center;
436
  width: 100%;
437
  .int-color {
438
    color: #169bfa;
439
  }
440
  .string-color {
441
    color: #31cf9a;
442
  }
443
  .boolean-color {
444
    color: #31cf9a;
445
  }
446
  .object-color {
447
    color: #ff956f;
448
  }
449
  .array-color {
450
    color: #ffb95b;
451
  }
452
  .decimal-color {
453
    color: #21c5db;
454
  }
455
  .date-color {
456
    color: #c084fc;
457
  }
458
  .long-color {
459
    color: #ff5f5f;
460
  }
461
  .datetime-color {
462
    color: #f471b7;
463
  }
464
  .tree-int {
465
    width: 100px;
466
  }
467
  .label {
468
    width: 30%;
469
  }
470
  .type {
471
    width: 100px;
472
  }
473
}
474
</style>
475
476
<style>
477
.jsoneditor-menu {
478
  background-color: #bbb;
479
  border-color: rgba(0, 0, 0, 0.15);
480
}
481
.jsoneditor {
482
  border-color: rgba(0, 0, 0, 0.15);
483
}
484
</style>

+ 64 - 0
src/views/_components/drag/MyList.vue

@ -0,0 +1,64 @@
1
<template>
2
  <div class="my-list">
3
    <div
4
      v-for="item in listData"
5
      :key="item.id"
6
      class="item"
7
      :draggable="true"
8
      :data-node="JSON.stringify(item)"
9
      @dragstart="handleDragStart"
10
      @dragend="handleDragEnd"
11
      @dragenter="handleDragEnter($event, $event.target)"
12
      @dragleave="handleDragLeave"
13
    >
14
      {{ item.label }}
15
    </div>
16
  </div>
17
</template>
18
19
<script>
20
export default {
21
  name: 'MyList',
22
  props: {
23
    listData: {
24
      type: Array,
25
      required: true
26
    }
27
  },
28
  methods: {
29
    handleDragStart(e) {
30
      e.dataTransfer.effectAllowed = 'move';
31
      e.dataTransfer.setData('text/plain', JSON.stringify(this.item));
32
    },
33
    handleDragEnd() {
34
      this.$emit('drag-end');
35
    },
36
    handleDragEnter(e, el) {
37
      this.$emit('drag-enter', e, el);
38
    },
39
    handleDragLeave() {
40
      this.$emit('drag-leave');
41
    }
42
  }
43
};
44
</script>
45
46
<style scoped>
47
.my-list {
48
  display: flex;
49
  flex-wrap: wrap;
50
  margin: -5px;
51
}
52
53
.my-list .item {
54
  width: 100px;
55
  height: 50px;
56
  background-color: #eee;
57
  border: 1px solid #ccc;
58
  margin: 5px;
59
  display: flex;
60
  justify-content: center;
61
  align-items: center;
62
  cursor: move;
63
}
64
</style>

+ 232 - 0
src/views/_components/drag/TreeItem copy 2.vue

@ -0,0 +1,232 @@
1
<template>
2
  <div class="container">
3
    <el-tree
4
      class="tree"
5
      :data="data"
6
      :props="props"
7
      :default-expanded-keys="defaultExpandedKeys"
8
      :expand-on-click-node="false"
9
      :draggable="true"
10
      @node-drag-start="handleNodeDragStart"
11
      @node-drag-over="handleNodeDragOver"
12
      @node-drag-end="handleNodeDragEnd"
13
      @node-drop="handleNodeDrop($event)"
14
    ></el-tree>
15
    <div class="list">
16
      <div
17
        v-for="(item, index) in list"
18
        :key="item.id"
19
        class="item"
20
        :draggable="true"
21
        @dragstart="handleItemDragStart(index)"
22
        @dragend="handleItemDragEnd"
23
      >
24
        {{ item.label }}
25
      </div>
26
    </div>
27
  </div>
28
</template>
29
30
<script>
31
export default {
32
  data() {
33
    return {
34
      data: [
35
        {
36
          id: 1,
37
          label: '一级 1',
38
          children: [
39
            {
40
              id: 4,
41
              label: '二级 1-1',
42
              children: [
43
                {
44
                  id: 9,
45
                  label: '三级 1-1-1'
46
                },
47
                {
48
                  id: 10,
49
                  label: '三级 1-1-2'
50
                }
51
              ]
52
            }
53
          ]
54
        },
55
        {
56
          id: 2,
57
          label: '一级 2',
58
          children: [
59
            {
60
              id: 5,
61
              label: '二级 2-1'
62
            },
63
            {
64
              id: 6,
65
              label: '二级 2-2'
66
            }
67
          ]
68
        },
69
        {
70
          id: 3,
71
          label: '一级 3',
72
          children: [
73
            {
74
              id: 7,
75
              label: '二级 3-1'
76
            },
77
            {
78
              id: 8,
79
              label: '二级 3-2'
80
            }
81
          ]
82
        }
83
      ],
84
      props: {
85
        children: 'children',
86
        label: 'label'
87
      },
88
      defaultExpandedKeys: [1, 2, 3],
89
      list: [
90
        {
91
          id: 11,
92
          label: '新增节点1'
93
        },
94
        {
95
          id: 12,
96
          label: '新增节点2'
97
        },
98
        {
99
          id: 13,
100
          label: '新增节点3'
101
        }
102
      ],
103
      draggingItemIndex: -1,
104
      draggingNode: null,
105
      dropMousePos: null
106
    };
107
  },
108
  methods: {
109
    handleItemDragStart(index) {
110
      this.draggingItemIndex = index;
111
    },
112
    handleItemDragEnd() {
113
      this.draggingItemIndex = -1;
114
    },
115
    handleNodeDragStart(data, node) {
116
      this.draggingNode = node;
117
    },
118
    handleNodeDragEnd() {
119
      this.draggingNode = null;
120
    },
121
    handleNodeDragOver(data, node, event) {
122
      const dropMousePos = this.getDropMousePos(event, node.$el);
123
      if (this.dropMousePos !== dropMousePos) {
124
        this.dropMousePos = dropMousePos;
125
      }
126
    },
127
    handleNodeDrop(event, data, node) {
128
      if (this.draggingItemIndex !== -1) {
129
        const item = this.list[this.draggingItemIndex];
130
        const nodeData = {
131
          id: item.id,
132
          label: item.label,
133
          children: item.children
134
        };
135
        // const tree = this.$;
136
        const dropPos = this.getDropPosition(event, node, this.dropMousePos);
137
138
        const { parent } = node;
139
        const index = parent.data.children.indexOf(node.data);
140
        switch (dropPos) {
141
          case 0:
142
            nodeData.children = nodeData.children || [];
143
            nodeData.children.unshift(data);
144
            break;
145
          case 1:
146
            data.children = data.children || [];
147
            data.children.unshift(nodeData);
148
            break;
149
          case 2:
150
            if (index !== -1) {
151
              nodeData.children = nodeData.children || [];
152
              parent.data.children.splice(index + 1, 0, nodeData);
153
            }
154
            break;
155
          default:
156
            break;
157
        }
158
159
        this.list.splice(this.draggingItemIndex, 1);
160
        this.dropMousePos = null;
161
        this.$refs.tree.setCurrentKey(data.id);
162
      }
163
    },
164
    getDropMousePos(event, dropNodeEl) {
165
      const rect = dropNodeEl.getBoundingClientRect();
166
      const offset = event.clientY - rect.top;
167
      const threshold = rect.height / 2;
168
      if (offset < threshold) {
169
        return 0;
170
      }
171
      if (offset > rect.height - threshold) {
172
        return 2;
173
      }
174
      return 1;
175
    },
176
    getDropPosition(event, node, dropMousePos) {
177
      const dropNode = node.$el;
178
      const rect = dropNode.getBoundingClientRect();
179
      const threshold = rect.height / 2;
180
      if (dropMousePos === 0) {
181
        return 0;
182
      }
183
      if (dropMousePos === 2) {
184
        return 1;
185
      }
186
      if (dropMousePos === 1 && rect.width >= threshold) {
187
        const leftThreshold = rect.left + threshold;
188
        const rightThreshold = rect.right - threshold;
189
        if (this.dropMousePos !== null) {
190
          const { clientX } = this.dropMousePos;
191
          if (clientX < leftThreshold) {
192
            return 0;
193
          }
194
          if (clientX > rightThreshold) {
195
            return 1;
196
          }
197
        }
198
        const distance =
199
          Math.abs(event.clientX - leftThreshold) -
200
          Math.abs(event.clientX - rightThreshold);
201
        return distance < 0 ? 0 : 1;
202
      }
203
    }
204
  }
205
};
206
</script>
207
<style>
208
.container {
209
  display: flex;
210
  height: 100%;
211
}
212
213
.tree {
214
  flex: 1;
215
}
216
217
.list {
218
  flex: 0 0 200px;
219
  padding: 20px;
220
  border: 1px solid #ccc;
221
  border-left: none;
222
  box-sizing: border-box;
223
}
224
225
.item {
226
  margin-bottom: 10px;
227
  padding: 10px;
228
  background-color: #f0f0f0;
229
  border: 1px solid #ccc;
230
  cursor: move;
231
}
232
</style>

+ 158 - 0
src/views/_components/drag/TreeItem copy.vue

@ -0,0 +1,158 @@
1
<template>
2
  <div class="container">
3
    <div class="left">
4
      <el-tree
5
        ref="tree"
6
        :data="treeData"
7
        :allow-drop="true"
8
        @node-drop="handleNodeDrop"
9
      ></el-tree>
10
    </div>
11
    <div class="right">
12
      <my-list
13
        :list-data="listData"
14
        @item-dropped="handleItemDropped"
15
        @drag-enter="handleDragEnter"
16
        @drag-leave="handleDragLeave"
17
      ></my-list>
18
    </div>
19
  </div>
20
</template>
21
22
<script setup>
23
import { ref } from 'vue';
24
import MyList from './MyList.vue';
25
26
const treeData = ref([
27
  {
28
    id: 1,
29
    label: 'Parent 1',
30
    children: [
31
      {
32
        id: 2,
33
        label: 'Child 1-1'
34
      },
35
      {
36
        id: 3,
37
        label: 'Child 1-2'
38
      }
39
    ]
40
  },
41
  {
42
    id: 4,
43
    label: 'Parent 2',
44
    children: [
45
      {
46
        id: 5,
47
        label: 'Child 2-1'
48
      }
49
    ]
50
  }
51
]);
52
const listData = ref([
53
  {
54
    id: 6,
55
    label: 'Item 1'
56
  },
57
  {
58
    id: 7,
59
    label: 'Item 2'
60
  },
61
  {
62
    id: 8,
63
    label: 'Item 3'
64
  }
65
]);
66
const mousePos = ref({ x: 0, y: 0 });
67
const insertNode = ref(null);
68
69
function handleItemDropped(item) {
70
  debugger;
71
  // 将拖拽的元素插入到树中
72
  const node = {
73
    id: item.id,
74
    label: item.label
75
  };
76
  if (insertNode.value) {
77
    const { parent } = insertNode.value;
78
    const { index } = insertNode.value;
79
    if (parent.children) {
80
      parent.children.splice(index, 0, node);
81
    } else {
82
      parent.children = [node];
83
    }
84
  } else {
85
    treeData.value.push(node);
86
  }
87
  // 清除插入节点的状态
88
  insertNode.value = null;
89
}
90
91
function handleDragEnter(e, el) {
92
  // 记录鼠标位置和插入的节点
93
  mousePos.value = { x: e.clientX, y: e.clientY };
94
  const { node } = el.dataset;
95
  insertNode.value = JSON.parse(node);
96
}
97
98
function handleDragLeave() {
99
  // 清除插入节点的状态
100
  insertNode.value = null;
101
}
102
103
function handleNodeDrop(data, dropEl, dropType, ev) {
104
  // 获取鼠标在元素内的相对位置
105
  const { top, height } = dropEl.getBoundingClientRect();
106
  const y = ev.clientY - top;
107
  const index = y < height / 2 ? data.index : data.index + 1;
108
  // 将拖拽的元素插入到树中
109
  const item = JSON.parse(ev.dataTransfer.getData('text/plain'));
110
  const node = {
111
    id: item.id,
112
    label: item.label
113
  };
114
  if (data.children) {
115
    data.children.splice(index, 0, node);
116
  } else {
117
    data.children = [node];
118
  }
119
}
120
</script>
121
122
<style scoped>
123
.container {
124
  display: flex;
125
}
126
127
.left {
128
  width: 50%;
129
  height: 500px;
130
  border: 1px solid #ccc;
131
  padding: 20px;
132
}
133
134
.right {
135
  width: 50%;
136
  height: 500px;
137
  border: 1px solid #ccc;
138
  padding: 20px;
139
}
140
141
.my-list {
142
  display: flex;
143
  flex-wrap: wrap;
144
  margin: -5px;
145
}
146
147
.my-list .item {
148
  width: 100px;
149
  height: 50px;
150
  background-color: #eee;
151
  border: 1px solid #ccc;
152
  margin: 5px;
153
  display: flex;
154
  justify-content: center;
155
  align-items: center;
156
  cursor: move;
157
}
158
</style>

+ 103 - 0
src/views/_components/drag/TreeItem.vue

@ -0,0 +1,103 @@
1
<template>
2
  <div class="tree-drag">
3
    <el-tree
4
      ref="tree1"
5
      :data="treeData1"
6
      class="tree"
7
      node-key="id"
8
      draggable
9
      default-expand-all
10
      :allow-drop="returnFalse"
11
      @node-drag-start="handleDragstart"
12
      @node-drag-end="handleDragend"
13
    ></el-tree>
14
15
    <el-tree
16
      ref="tree2"
17
      :data="treeData2"
18
      class="tree"
19
      node-key="id"
20
      draggable
21
      default-expand-all
22
      :allow-drop="returnTrue"
23
    ></el-tree>
24
  </div>
25
</template>
26
27
<script>
28
export default {
29
  name: 'HelloWorld',
30
  data() {
31
    return {
32
      treeData1: [
33
        {
34
          id: 1,
35
          label: '一级 1',
36
          children: [
37
            {
38
              id: 4,
39
              label: '二级 1-1',
40
              children: [{ id: 9, label: '三级 1-1-1' }]
41
            }
42
          ]
43
        }
44
      ],
45
      treeData2: [
46
        {
47
          id: 3,
48
          label: '级 1',
49
          children: [
50
            {
51
              id: 5,
52
              label: '二级 1-1',
53
              children: [{ id: 8, label: '三级 1-1-1' }]
54
            }
55
          ]
56
        }
57
      ]
58
    };
59
  },
60
  methods: {
61
    handleDragstart(node, event) {
62
      this.$refs.tree2.$emit('tree-node-drag-start', event, { node });
63
    },
64
    handleDragend(draggingNode, endNode, position, event) {
65
      // 插入一个空节点用于占位
66
      const emptyData = { id: +new Date(), children: [] };
67
      this.$refs.tree1.insertBefore(emptyData, draggingNode);
68
69
      this.$refs.tree2.$emit('tree-node-drag-end', event);
70
      this.$nextTick(() => {
71
        // 如果是移动到了当前树上,需要清掉空节点
72
        if (this.$refs.tree1.getNode(draggingNode.data)) {
73
          this.$refs.tree1.remove(emptyData);
74
        } else {
75
          // 如果移动到了别的树上,需要恢复该节点,并清掉空节点
76
          const data = JSON.parse(JSON.stringify(draggingNode.data));
77
          this.$refs.tree1.insertAfter(
78
            data,
79
            this.$refs.tree1.getNode(emptyData)
80
          );
81
          this.$refs.tree1.remove(emptyData);
82
        }
83
      });
84
    },
85
    returnTrue() {
86
      return true;
87
    },
88
    returnFalse() {
89
      return false;
90
    }
91
  }
92
};
93
</script>
94
95
<style scoped>
96
.tree {
97
  display: inline-block;
98
  vertical-align: top;
99
  width: 30%;
100
  height: 400px;
101
  border: 1px solid #999;
102
}
103
</style>

+ 294 - 0
src/views/_components/drag/demo.vue

@ -0,0 +1,294 @@
1
<template>
2
  <el-button @click="onSubmit" />
3
  <div class="api-req-wrapper">
4
    <div class="left">
5
      <p class="title">请求参数</p>
6
      <el-table
7
        :data="configTableData"
8
        style="width: 100%; margin-bottom: 20px"
9
        row-key="id"
10
        default-expand-all
11
        header-row-class-name="custom-table-header"
12
        row-class-name="custom-table-row"
13
        class="custom-table"
14
      >
15
        <el-table-column label="服务入参字段(key/name)" min-width="200px">
16
          <template #default="scope">
17
            <div class="custom-table-cell">
18
              <p>{{ scope.row.code }}</p>
19
              <p>{{ scope.row.name }}</p>
20
            </div>
21
          </template>
22
        </el-table-column>
23
        <el-table-column prop="type" label="入参类型" />
24
        <el-table-column prop="dataIndex" label="表单字段" min-width="160px">
25
          <template #default="scope">
26
            <draggable
27
              v-model="scope.row.formData"
28
              item-key="dataIndex"
29
              :group="{
30
                name: 'items',
31
                pull: false,
32
                put: !scope.row.formData.length
33
              }"
34
              class="list-group tag-box"
35
            >
36
              <template #item="{ element }">
37
                <div class="list-group-item">
38
                  <svg class="icon type-icon" aria-hidden="true">
39
                    <use :xlink:href="`#icon-${element.type}`"></use>
40
                  </svg>
41
                  <span>{{ element.label }}</span>
42
                </div>
43
              </template>
44
            </draggable>
45
            <el-icon class="delete" :size="16" @click="scope.row.formData = []">
46
              <Delete />
47
            </el-icon>
48
          </template>
49
        </el-table-column>
50
        <el-table-column label="高级设置">
51
          <template #default="scope">
52
            <el-icon class="btn" :size="16" @click="onSetting(scope.row)">
53
              <Operation />
54
            </el-icon>
55
          </template>
56
        </el-table-column>
57
      </el-table>
58
    </div>
59
    <div class="right">
60
      <p class="title">相应数据</p>
61
      <div class="box">
62
        <div class="content">
63
          <draggable
64
            v-model="inputData"
65
            class="list-group"
66
            item-key="dataIndex"
67
            :group="{ name: 'items', pull: 'clone' }"
68
            :sort="false"
69
          >
70
            <template #item="{ element }">
71
              <div class="list-group-item">
72
                <svg class="icon type-icon" aria-hidden="true">
73
                  <use :xlink:href="`#icon-${element.type}`"></use>
74
                </svg>
75
                <span>{{ element.label }}</span>
76
              </div>
77
            </template>
78
          </draggable>
79
        </div>
80
      </div>
81
    </div>
82
  </div>
83
</template>
84
85
<script setup lang="ts">
86
import draggable from 'vuedraggable';
87
import { ref } from 'vue';
88
89
interface InputData {
90
  type?:
91
    | 'text'
92
    | 'textarea'
93
    | 'select'
94
    | 'radio'
95
    | 'checkbox'
96
    | 'date'
97
    | 'password';
98
  dataIndex: string;
99
  label: string;
100
}
101
102
interface ConfigTableData {
103
  [key: string]: any;
104
  formData: any;
105
}
106
107
defineProps({
108
  tableData: {
109
    type: Array as () => ConfigTableData[],
110
    default: () => []
111
  }
112
});
113
114
const configTableData = ref<ConfigTableData[]>([]);
115
// watch();
116
// () => props.tableData,
117
// (newVal: ConfigTableData[]) => {
118
//   const copyTable = [...newVal];
119
//   configTableData.value = copyTable.map((item) => {
120
//     addExtraProp(item as any, 'formData', []);
121
//     return item;
122
//   });
123
// },
124
// {
125
//   deep: true,
126
//   immediate: true
127
// }
128
129
const inputData = ref<InputData[]>([
130
  {
131
    type: 'text',
132
    dataIndex: 'code',
133
    label: '编码'
134
  },
135
  {
136
    type: 'textarea',
137
    dataIndex: 'name',
138
    label: '名称'
139
  },
140
  {
141
    type: 'select',
142
    dataIndex: 'province',
143
    label: '省份'
144
  },
145
  {
146
    type: 'radio',
147
    dataIndex: 'work',
148
    label: '是否启用'
149
  },
150
  {
151
    type: 'checkbox',
152
    dataIndex: 'way',
153
    label: '方式'
154
  },
155
  {
156
    type: 'date',
157
    dataIndex: 'startDate',
158
    label: '开始日期'
159
  },
160
  {
161
    type: 'password',
162
    dataIndex: 'password',
163
    label: '密码'
164
  }
165
]);
166
167
const onSetting = (data: object) => {
168
  console.log(data);
169
};
170
171
const onSubmit = () => {
172
  console.log('config>>>>', configTableData);
173
};
174
</script>
175
<style scoped lang="scss">
176
.api-req-wrapper {
177
  display: flex;
178
179
  .box {
180
    background: #ffffff;
181
    border-radius: 4px;
182
  }
183
184
  .title {
185
    font-weight: 500;
186
    font-size: 12px;
187
    line-height: 20px;
188
    color: #455a74;
189
    padding: 12px 0 8px;
190
  }
191
192
  .left {
193
    flex: 1;
194
    margin-right: 12px;
195
  }
196
197
  .right {
198
    width: 746px;
199
200
    .box {
201
      padding: 6px 12px;
202
      height: 100%;
203
    }
204
205
    .content {
206
      height: 100%;
207
      background: #f9f9f9;
208
      padding: 12px;
209
      color: #455a74;
210
      font-weight: 400;
211
      font-size: 12px;
212
      line-height: 20px;
213
    }
214
  }
215
}
216
217
.btn {
218
  cursor: pointer;
219
}
220
221
.custom-table {
222
  :deep(.el-table__expand-icon) {
223
    vertical-align: middle;
224
  }
225
226
  .custom-cell {
227
    color: #455a74;
228
    font-size: 12px;
229
    line-height: 20px;
230
    height: 58px;
231
  }
232
233
  :deep(.custom-table-header) {
234
    @extend .custom-cell;
235
236
    .cell {
237
      font-weight: 500;
238
    }
239
  }
240
241
  :deep(.custom-table-row) {
242
    @extend .custom-cell;
243
  }
244
}
245
246
.custom-table-cell {
247
  display: inline-block;
248
  vertical-align: middle;
249
250
  p {
251
    height: 20px;
252
    line-height: 20px;
253
  }
254
}
255
256
.list-group {
257
  display: grid;
258
  grid-template-columns: repeat(auto-fit, 98px);
259
  grid-gap: 12px;
260
261
  &-item {
262
    height: 32px;
263
    line-height: 32px;
264
    padding: 0 12px;
265
    background: #fff;
266
    border: 1px solid #ebebeb;
267
    border-radius: 4px;
268
    cursor: pointer;
269
270
    &:active {
271
      background: #e4ebff;
272
    }
273
  }
274
}
275
276
.type-icon {
277
  font-size: 16px;
278
  margin-right: 8px;
279
}
280
281
.tag-box {
282
  height: 42px;
283
  background: #f9f9f9;
284
  border-radius: 4px;
285
  padding: 5px 10px;
286
}
287
288
.delete {
289
  position: absolute;
290
  right: 22px;
291
  top: 13px;
292
  cursor: pointer;
293
}
294
</style>

+ 17 - 11
src/views/_components/panel/PanelSubFlow.vue

@ -37,10 +37,16 @@
37 37
</template>
38 38
39 39
<script setup>
40
import { ref, watchEffect } from 'vue';
40
import { ref, watchEffect, onMounted } from 'vue';
41 41
import CommonPanel from './common/CommonPanel.vue';
42 42
import InputOutputTab from './sql-components/InputOutputTab.vue';
43
import { getSubFlowList } from '../../../api/subflow';
43 44
45
const subflowList = ref([]);
46
const propsMap = ref({
47
  label: 'name',
48
  value: 'id'
49
});
44 50
const props = defineProps({
45 51
  targetNode: {
46 52
    type: Object,
@ -124,22 +130,22 @@ const panelData = ref(
124 130
  }
125 131
);
126 132
127
// function setNodeAttrData () {
128
//   props.targetNode.cell.setData({
129
//     attrsData: JSON.parse(JSON.stringify(panelData.value))
130
//   })
131
//   console.log(props.targetNode.cell.getData())
132
// }
133
134
// watchEffect(panelData.value, () => {
135
//   setNodeAttrData()
136
// }, { deep: true })
133
// 获取子流程列表
134
function getFlowList() {
135
  getSubFlowList().then((res) => {
136
    subflowList.value = res.result;
137
  });
138
}
137 139
138 140
watchEffect(() => {
139 141
  props.targetNode.cell.setData({
140 142
    attrsData: JSON.parse(JSON.stringify(panelData.value))
141 143
  });
142 144
});
145
146
onMounted(() => {
147
  getFlowList();
148
});
143 149
</script>
144 150
145 151
<script>

+ 17 - 3
src/views/flow/SystemDetail.vue

@ -29,6 +29,7 @@
29 29
      <template #left>
30 30
        <div class="system-tree">
31 31
          <common-tree
32
            ref="logicTree"
32 33
            :tree-data="moduleTreeData"
33 34
            :actions="actions"
34 35
            :attrs="defaultProps"
@ -99,6 +100,8 @@ const apiDetail = ref(null);
99 100
const versionState = ref('');
100 101
const store = useStore();
101 102
103
const logicTree = ref(null);
104
102 105
const systemId = computed(() => route.query.id);
103 106
const curVersionId = ref('');
104 107
@ -259,9 +262,13 @@ const defaultProps = ref({
259 262
  props: {
260 263
    children: 'logicServices',
261 264
    label: 'name',
262
    id: 'id',
263
    'default-expand-all': true
264
  }
265
    id: 'id'
266
  },
267
  'default-expand-all': true,
268
  'node-key': 'id',
269
  'default-expanded-keys': [],
270
  indent: 10,
271
  accordion: true
265 272
});
266 273
// 获取模型树列表
267 274
function getModuleData() {
@ -277,6 +284,13 @@ function getModuleData() {
277 284
      if (!curLogic.value) {
278 285
        curLogic.value = moduleTreeData.value?.[0]?.logicServices?.[0].id || '';
279 286
      }
287
      console.log(moduleTreeData.value);
288
      console.log(curLogic.value);
289
      setTimeout(() => {
290
        logicTree.value.treeRef.setCurrentKey(curLogic.value, true);
291
      }, 100);
292
      defaultProps.value['default-expanded-keys'] =
293
        moduleTreeData.value.datas?.[0]?.id;
280 294
    } else {
281 295
      proxy.$message.error('');
282 296
    }

+ 8 - 2
src/views/flow/_components/ApiDetailPage.vue

@ -326,9 +326,10 @@ onMounted(() => {});
326 326
327 327
<style scoped lang="scss">
328 328
.api-detail {
329
  padding: 24px;
329
  padding: 0;
330 330
  width: auto;
331
  height: calc(100vh - 250px);
331
  height: calc(100vh - 210px);
332
  box-sizing: border-box;
332 333
  overflow: auto;
333 334
  .card {
334 335
    width: 100%;
@ -390,6 +391,11 @@ onMounted(() => {});
390 391
  font-weight: 500;
391 392
  margin-bottom: 10px;
392 393
}
394
.api-text {
395
  padding-bottom: 20px;
396
  background-color: #fff;
397
  box-sizing: border-box;
398
}
393 399
.editor {
394 400
  height: 300px;
395 401
}

+ 0 - 0
src/views/flow/_hooks/flowHooks.js


+ 10 - 0
src/views/manager/Index.vue

@ -0,0 +1,10 @@
1
<template>
2
  <DragComments></DragComments>
3
  <!-- <TreeItem></TreeItem> -->
4
</template>
5
6
<script setup lang="ts">
7
import DragComments from '../_components/drag/DragComments.vue';
8
// import TreeItem from '../_components/drag/TreeItem.vue';
9
</script>
10
<style scoped lang="scss"></style>