vue destroy 可排序元素

Vue Destroy Sortable Element

提问人:Jacques Joubert 提问时间:11/5/2023 更新时间:11/5/2023 访问量:23

问:

总结

创建面板,并将图层添加到面板。尝试删除图层时 将删除面板中的最后一个图层,而不是图层项目 被选中销毁。

完整期刊:

了解 Vue.js 下三分之一图形控制器中的 control.html

control.html 文件是旨在创建和管理层的 Web 应用程序的关键组件。这个接口是使用 Vue.js 构建的,允许用户配置和设计具有高度自定义的层。

control.html 概述

control.html 页面具有用户友好的界面,其中包含多个部分,每个部分专门用于设计过程的特定方面:

“设计编辑器”部分:提供一个选择菜单,用于向面板添加各种面板元素。

正文部分特别具有交互性,因为它支持拖放功能,允许用户对面板元素重新排序。

问题:图层删除不正确

在 control.html 界面中遇到的一个值得注意的问题是删除了特定层。尝试从面板上移除特定图层时,未破坏正确的图层。

故障排除和解决方案

为了解决这个问题,采取了几个步骤来诊断和纠正这个问题:

索引验证:首先确认传递给 destroyLayer 方法的索引准确无误。这涉及添加控制台 .log 语句以输出当前索引并验证它们与预期目标的对齐情况。

唯一键:在 v-for 指令中,通过将 :key 属性绑定到每层的唯一属性(通常是 id,而不是数组索引)来为每一层提供唯一键。这有助于 Vue 清楚地识别每个 DOM 元素,保持重新排序和删除等操作的保真度。

简化的 destroyLayer 函数:destroyLayer 方法经过简化,可直接使用拼接删除图层,无需额外的反应性技巧(如 $set 或 $forceUpdate),这些技巧通常是不必要的,并且可能会掩盖反应性问题的根本原因。

https://jsfiddle.net/4vh80j35/

    <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue.js Control Application</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.10.2/Sortable.min.js"></script>
<style>
  .container {
    border: 2px solid #000;
    padding: 10px;
    margin-top: 10px;
  }
  .panel {
    border: 1px solid blue;
    margin: 5px;
    padding: 5px;
    position: relative;
  }
  .destroy-btn {
    color: white;
    background-color: red;
    padding: 2px 5px;
    cursor: pointer;
    position: absolute;
    right: 5px;
    top: 5px;
  }
  .layer {
    border: 1px solid green;
    margin: 3px;
    padding: 3px;
    position: relative;
    background-color: #efefef;
  }
  .draggable-container {
    min-height: 50px;
  }
</style>
</head>
<body>

<div id="app">
  <div class="editor">
    <button @click="createPanel">Create Panel</button>
    <div v-if="panels.length > 0">
      <label for="panelSelect">Choose a panel to add a layer:</label>
      <select id="panelSelect" v-model="selectedPanel">
        <option v-for="(panel, index) in panels" :value="index">Panel {{ index + 1 }}</option>
      </select>
      <button @click="createLayer">Add Layer to Selected Panel</button>
    </div>
  </div>
  <div class="container">
    <div class="panel" v-for="(panel, pIndex) in panels" :key="`panel-${pIndex}`" :data-index="pIndex">
      <span class="destroy-btn" @click="destroyPanel(pIndex)">[destroy]</span>
      Panel {{ pIndex + 1 }}
      <div class="draggable-container">
        <div class="layer" v-for="(layer, lIndex) in panel.layers" :key="`layer-${lIndex}`">
          Layer {{ lIndex + 1 }} (in Panel {{ pIndex + 1 }})
          <span class="destroy-btn" @click.stop="destroyLayer(pIndex, lIndex)">[destroy]</span>
        </div>
      </div>
    </div>

    
  </div>
</div>
<script>
  new Vue({
    el: '#app',
    data: {
      panels: [],
      selectedPanel: null
    },
    mounted() {
        this.$nextTick(() => {
          this.panels.forEach((_, index) => {
            this.makeSortable(index);
          });
        });
      },
    methods: {
      createPanel() {
        const newPanelIndex = this.panels.push({ layers: [] }) - 1;
        this.$nextTick(() => {
          this.makeSortable(newPanelIndex);
        });
        if (this.selectedPanel === null) {
          this.selectedPanel = 0;
        }
      },
      createLayer() {
        if (this.selectedPanel !== null && this.selectedPanel < this.panels.length) {
          this.panels[this.selectedPanel].layers.push({});
        }
      },
      makeSortable(panelIndex) {
          const container = this.$el.querySelector(`.panel[data-index="${panelIndex}"] .draggable-container`);
          Sortable.create(container, {
            group: 'shared', // set the group to 'shared' for all containers
            onAdd: (evt) => {
              console.log(`New ${evt.newIndex} from  ${evt.oldIndex} in panel`);
              const item = this.panels[evt.oldIndex].layers.splice(evt.oldIndex, 1)[0];
              this.panels[evt.newIndex].layers.splice(evt.newIndex, 0, item);
            },
            onUpdate: (evt) => {
              const panelIndex = evt.to.dataset.index;
              const item = this.panels[panelIndex].layers.splice(evt.oldIndex, 1)[0];
              this.panels[panelIndex].layers.splice(evt.newIndex, 0, item);
            },
          });
        },
      destroyPanel(index) {
        this.panels.splice(index, 1);
        if (index === this.selectedPanel) {
          this.selectedPanel = (this.panels.length > 0) ? 0 : null;
        }
      },
      destroyLayer(panelIndex, layerIndex) {
        console.log(`Attempting to destroy layer ${layerIndex} of  ${this.panels[panelIndex].layers.length} in panel ${panelIndex}`);
        if (panelIndex < this.panels.length) {
          const layers = this.panels[panelIndex].layers;
          if (layerIndex < layers.length) {
            // Removes the layer from the panel
            layers.splice(layerIndex, 1);
          }
          // If the panel becomes empty and it's the selected one, deselect it
          if (!layers.length && this.selectedPanel === panelIndex) {
            this.selectedPanel = null;
          }
        }
      },
    }
  });
</script>
</body>
</html>
vue.js 可拖拽 jquery-ui-sortable 销毁 拼接

评论


答:

1赞 Nikola Pavicevic 11/5/2023 #1

在以下示例中,id 被添加到每个图层中,因此您可以观察到销毁:

new Vue({
    el: '#app',
    data: {
      panels: [],
      selectedPanel: null
    },
    mounted() {
        this.$nextTick(() => {
          this.panels.forEach((_, index) => {
            this.makeSortable(index);
          });
        });
      },
    methods: {
      createPanel() {
        const newPanelIndex = this.panels.push({ layers: [] }) - 1;
        this.$nextTick(() => {
          this.makeSortable(newPanelIndex);
        });
        if (this.selectedPanel === null) {
          this.selectedPanel = 0;
        }
      },
      createLayer() {
        if (this.selectedPanel !== null && this.selectedPanel < this.panels.length) {
          const id = this.panels[this.selectedPanel].layers.length ? Math.max(...this.panels[this.selectedPanel].layers.map(o => o.id)) + 1 : 0
          this.panels[this.selectedPanel].layers.push({id});
        }
      },
      makeSortable(panelIndex) {
          const container = this.$el.querySelector(`.panel[data-index="${panelIndex}"] .draggable-container`);
          Sortable.create(container, {
            group: 'shared', // set the group to 'shared' for all containers
            onAdd: (evt) => {
              console.log(`New ${evt.newIndex} from  ${evt.oldIndex} in panel`);
              const item = this.panels[evt.oldIndex].layers.splice(evt.oldIndex, 1)[0];
              this.panels[evt.newIndex].layers.splice(evt.newIndex, 0, item);
            },
            onUpdate: (evt) => {
              const panelIndex = evt.to.dataset.index;
              const item = this.panels[panelIndex].layers.splice(evt.oldIndex, 1)[0];
              this.panels[panelIndex].layers.splice(evt.newIndex, 0, item);
            },
          });
        },
      destroyPanel(index) {
        this.panels.splice(index, 1);
        if (index === this.selectedPanel) {
          this.selectedPanel = (this.panels.length > 0) ? 0 : null;
        }
      },
      destroyLayer(panelIndex, layerIndex) {
        console.log(`Attempting to destroy layer ${layerIndex} of  ${this.panels[panelIndex].layers.length} in panel ${panelIndex}`);
        if (panelIndex < this.panels.length) {
          const layers = this.panels[panelIndex].layers;
          if (layerIndex < layers.length) {
            // Removes the layer from the panel
            layers.splice(layerIndex, 1);
          }
          // If the panel becomes empty and it's the selected one, deselect it
          if (!layers.length && this.selectedPanel === panelIndex) {
            this.selectedPanel = null;
          }
        }
      },
    }
  });
  .container {
    border: 2px solid #000;
    padding: 10px;
    margin-top: 10px;
  }
  .panel {
    border: 1px solid blue;
    margin: 5px;
    padding: 5px;
    position: relative;
  }
  .destroy-btn {
    color: white;
    background-color: red;
    padding: 2px 5px;
    cursor: pointer;
    position: absolute;
    right: 5px;
    top: 5px;
  }
  .layer {
    border: 1px solid green;
    margin: 3px;
    padding: 3px;
    position: relative;
    background-color: #efefef;
  }
  .draggable-container {
    min-height: 50px;
  }
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.10.2/Sortable.min.js"></script>
<div id="app">
  <div class="editor">
    <button @click="createPanel">Create Panel</button>
    <div v-if="panels.length > 0">
      <label for="panelSelect">Choose a panel to add a layer:</label>
      <select id="panelSelect" v-model="selectedPanel">
        <option v-for="(panel, index) in panels" :value="index">Panel {{ index + 1 }}</option>
      </select>
      <button @click="createLayer">Add Layer to Selected Panel</button>
    </div>
  </div>
  <div class="container">
    <div class="panel" v-for="(panel, pIndex) in panels" :key="`panel-${pIndex}`" :data-index="pIndex">
      <span class="destroy-btn" @click="destroyPanel(pIndex)">[destroy]</span>
      Panel {{ pIndex + 1 }}
      <div class="draggable-container">
        <div class="layer" v-for="(layer, lIndex) in panel.layers" :key="`layer-${lIndex}`">
          Layer {{ lIndex + 1 }} (in Panel {{ pIndex + 1 }}) id: {{layer.id}}
          <span class="destroy-btn" @click.stop="destroyLayer(pIndex, lIndex)">[destroy]</span>
        </div>
      </div>
    </div>
  </div>
</div>

评论

0赞 Jacques Joubert 11/6/2023
您的解决方案解决了 destroyLayer 问题,但在面板之间移动图层时会破坏图层可排序功能。
0赞 Jacques Joubert 11/6/2023
您的答案是正确的,因为从技术上讲,可排序问题在您的解决方案之前就已经存在。