<template>
  <DraggableComp>

    <vue2-org-tree :data="treeData" :props="defaultProps" collapsable :render-content="renderNode"
      @on-expand="onExpand" />

    
  </DraggableComp>

</template>



<script>
import DraggableComp from './DraggableComp.vue';
let id = 1000
export default {
  components: { DraggableComp },
  data() {
    return {
      defaultProps: { label: 'label', children: 'children', expand: 'expand' },
      treeData: {                       // 初始数据
        id: 0,
        label: '广东功道云科技有限公司',
        expand: true,
        children: [
          { id: 1, label: '研发部', expand: true, positions: ['前端工程师', '后端工程师', 'DevOps'], children: [{ id: 4, label: '前端组' }, { id: 5, label: '后端组' }] },
          { id: 2, label: '销售部' },
          { id: 3, label: '人事部' }
        ]
      },
      // 缩放相关数据
      zoom: 1, // 缩放比例，1表示100%
      minZoom: 0.3, // 最小缩放比例
      maxZoom: 3, // 最大缩放比例
      zoomStep: 0.1, // 每次滚轮滚动的缩放步长
      zoomOrigin: { x: 50, y: 50 }, // 缩放中心点，默认为中心

      // 拖拽相关数据
      isDragging: false,
      dragStart: { x: 0, y: 0 },
      position: { x: 0, y: 0 }, // 当前位置
      lastPosition: { x: 0, y: 0 }, // 上次位置（用于连续拖拽）
      boundary: {
        minX: -Infinity,
        maxX: Infinity,
        minY: -Infinity,
        maxY: Infinity
      } // 拖拽边界
    }
  },

  computed: {
    // 计算容器样式（同时包含缩放和位置）
    containerStyle() {
      return {
        transform: `translate(${this.position.x}px, ${this.position.y}px) scale(${this.zoom})`,
        transformOrigin: `${this.zoomOrigin.x}% ${this.zoomOrigin.y}%`,
        transition: this.isDragging ? 'none' : 'transform 0.15s ease-out',
        cursor: this.isDragging ? 'grabbing' : 'grab'
      };
    }
  },


  mounted() {
    // 组件挂载后设置初始缩放中心和计算边界
    this.updateZoomOrigin();
    this.calculateBoundary();

    // 添加全局鼠标事件监听
    window.addEventListener('mousemove', this.handleDrag);
    window.addEventListener('mouseup', this.stopDrag);

    // 窗口大小改变时重新计算边界
    window.addEventListener('resize', this.calculateBoundary);
  },
  beforeDestroy() {
    // 移除事件监听
    window.removeEventListener('mousemove', this.handleDrag);
    window.removeEventListener('mouseup', this.stopDrag);
    window.removeEventListener('resize', this.calculateBoundary);
  },

  created() {
    // 初始化职位显示状态
    this.initPositionVisibility(this.treeData);
  },

  methods: {

    /* --------------- 拖拽相关方法 --------------- */
    startDrag(event) {
      // 防止触发其他事件（如文本选择）
      event.preventDefault();

      // 记录拖拽开始位置
      this.isDragging = true;
      this.dragStart = {
        x: event.clientX - this.lastPosition.x,
        y: event.clientY - this.lastPosition.y
      };
    },

    handleDrag(event) {
      if (!this.isDragging) return;

      // 计算新的位置
      let newX = event.clientX - this.dragStart.x;
      let newY = event.clientY - this.dragStart.y;

      // 限制在边界内
      newX = Math.max(this.boundary.minX, Math.min(this.boundary.maxX, newX));
      newY = Math.max(this.boundary.minY, Math.min(this.boundary.maxY, newY));

      // 更新位置
      this.position.x = newX;
      this.position.y = newY;

      // 保存最后位置
      this.lastPosition.x = newX;
      this.lastPosition.y = newY;
    },

    stopDrag() {
      this.isDragging = false;
    },

    // 计算拖拽边界
    calculateBoundary() {
      const container = this.$refs.dragContainer;
      const area = this.$refs.componentArea;

      if (!container || !area) return;

      const containerRect = container.getBoundingClientRect();
      const areaRect = area.getBoundingClientRect();

      // 计算边界（确保容器不会完全拖出可视区域）
      const padding = 20; // 留出一些边距

      this.boundary = {
        minX: areaRect.left - containerRect.left + padding - (containerRect.width * (this.zoom - 1)) / 2,
        maxX: areaRect.right - containerRect.right - padding + (containerRect.width * (this.zoom - 1)) / 2,
        minY: areaRect.top - containerRect.top + padding - (containerRect.height * (this.zoom - 1)) / 2,
        maxY: areaRect.bottom - containerRect.bottom - padding + (containerRect.height * (this.zoom - 1)) / 2
      };
    },

    // 重置位置
    resetPosition() {
      this.position = { x: 0, y: 0 };
      this.lastPosition = { x: 0, y: 0 };
    },

    /* --------------- 缩放相关方法 --------------- */
    handleWheel(event) {
      event.preventDefault();

      // 计算鼠标在容器内的相对位置（作为缩放中心）
      const container = this.$refs.dragContainer;
      const rect = container.getBoundingClientRect();

      const mouseXPercent = ((event.clientX - rect.left) / rect.width) * 100;
      const mouseYPercent = ((event.clientY - rect.top) / rect.height) * 100;

      // 更新缩放中心
      this.zoomOrigin = {
        x: Math.max(0, Math.min(100, mouseXPercent)),
        y: Math.max(0, Math.min(100, mouseYPercent))
      };

      // 判断缩放方向
      const delta = event.deltaY > 0 ? -1 : 1;

      // 计算新缩放比例
      let newZoom = this.zoom + (delta * this.zoomStep);

      // 限制缩放范围
      newZoom = Math.max(this.minZoom, Math.min(this.maxZoom, newZoom));

      // 保存旧缩放值
      const oldZoom = this.zoom;

      // 应用缩放（保留一位小数）
      this.zoom = Math.round(newZoom * 10) / 10;

      // 缩放后重新计算边界
      this.calculateBoundary();

      // 调整位置，使鼠标所在点保持相对位置
      if (oldZoom !== this.zoom) {
        const zoomRatio = this.zoom / oldZoom;
        const mouseX = event.clientX - rect.left;
        const mouseY = event.clientY - rect.top;

        this.position.x = mouseX - (mouseX - this.position.x) * zoomRatio;
        this.position.y = mouseY - (mouseY - this.position.y) * zoomRatio;

        // 限制在边界内
        this.position.x = Math.max(this.boundary.minX, Math.min(this.boundary.maxX, this.position.x));
        this.position.y = Math.max(this.boundary.minY, Math.min(this.boundary.maxY, this.position.y));

        this.lastPosition.x = this.position.x;
        this.lastPosition.y = this.position.y;
      }
    },

    // 重置缩放
    resetZoom() {
      const oldZoom = this.zoom;
      this.zoom = 1;
      this.zoomOrigin = { x: 50, y: 50 };

      // 缩放后重新计算边界
      this.calculateBoundary();

      // 调整位置，使组件居中
      this.position.x = this.position.x / oldZoom;
      this.position.y = this.position.y / oldZoom;

      this.lastPosition.x = this.position.x;
      this.lastPosition.y = this.position.y;
    },

    // 更新缩放中心为容器中心
    updateZoomOrigin() {
      this.zoomOrigin = { x: 50, y: 50 };
    },


    initPositionVisibility(data) {
      if (data.positions && data.positions.length > 0) {
        this.$set(data, '_showPos', true);
      }
      if (data.children) {
        data.children.forEach(child => this.initPositionVisibility(child));
      }
    },
    /* --------------- 节点渲染 --------------- */
    renderNode(h, data) {
      // if (!data._showPos) this.$set(data, '_showPos', true)   // 折叠状态

      return (
        <div class="node-wrapper">
          {/* 1. 折叠按钮 —— 在 popover 之外 */}


          {/* 2. 原 popover 包裹的节点主体 */}
          <el-popover placement="bottom" width="260" trigger="click">
            <div style="text-align:center;">
              <el-button size="mini" type="primary" onClick={() => this.addChild(data)}>添加子部门</el-button>
              <el-button size="mini" onClick={() => this.editNode(data)}>编辑</el-button>
              <el-button size="mini" type="danger" onClick={() => this.delNode(data)}>删除</el-button>
            </div>
            <div slot="reference" class="node-body">
              <div class="node-label">
                {data.label}
                {data.positions && data.positions.length > 0 && (
                  <span
                    class={['fold-btn', { 'collapsed': !data._showPos }]}
                    onClick={e => { e.stopPropagation(); data._showPos = !data._showPos }}>
                    {data._showPos ? '▲' : '▼'}
                  </span>
                )}
              </div>
              {/* 职位列表 */}
              {data._showPos && data.positions && data.positions.length > 0 && (
                <ul class="pos-list">
                  {data.positions.map(p => <li key={p}>{p}</li>)}
                </ul>
              )}
            </div>
          </el-popover>
        </div>
      )
    },

    /* --------------- 业务函数 --------------- */
    addChild(parent) {                      // 添加子节点
      this.$prompt('请输入新部门名称', '添加子部门', {
        inputValidator: v => v && v.trim().length > 0
      }).then(({ value }) => {
        if (!parent.children) this.$set(parent, 'children', [])
        parent.children.push({ id: ++id, label: value.trim(), expand: false })
        this.$message.success('添加成功')
      })
    },
    editNode(node) {                        // 编辑节点
      this.$prompt('请输入新的部门名称', '编辑部门', {
        inputValue: node.label,
        inputValidator: v => v && v.trim().length > 0
      }).then(({ value }) => {
        this.$set(node, 'label', value.trim())
        this.$message.success('修改成功')
      })
    },
    delNode(node) {                         // 删除节点
      this.$confirm(`确定删除 “${node.label}” 及其所有子部门吗？`, '提示', { type: 'warning' })
        .then(() => {
          const parent = this.findParent(this.treeData, node)
          if (!parent) return                      // 根节点不让删
          const idx = parent.children.indexOf(node)
          parent.children.splice(idx, 1)
          this.$message.success('删除成功')
        })
    },

    /* --------------- 辅助函数 --------------- */
    findParent(root, target, parent = null) {      // 递归找父节点
      if (root === target) return parent
      if (root.children) {
        for (const child of root.children) {
          const res = this.findParent(child, target, root)
          if (res) return res
        }
      }
      return null
    },
    // 展开/收起全部
    expandAll(val) {
      // 同时切换所有节点的职位列表显示状态
      this.togglePositionVisibility(this.treeData, val);
    },

    toggleExpand(data, val) {
      this.$set(data, 'expand', val)
      if (data.children) data.children.forEach(c => this.toggleExpand(c, val))
    },

    togglePositionVisibility(data, val) {
      // 只有有职位的节点才设置 _showPos
      if (data.positions && data.positions.length > 0) {
        this.$set(data, '_showPos', val);
      }
      if (data.children) {
        data.children.forEach(child => this.togglePositionVisibility(child, val));
      }
    },

    collapse(list) {
      var that = this;
      list.forEach(function (child) {
        if (child.expand) {
          child.expand = false;
        }
        child.children && that.collapse(child.children);
      });
    },

    // 折叠回调
    onExpand(e, data) {
      console.log('expand change:', data.label, data.expand)
      if ("expand" in data) {
        data.expand = !data.expand;
        if (!data.expand && data.children) {
          this.collapse(data.children);
        }
      } else {
        this.$set(data, "expand", true);
      }
    }
  }
}
</script>


<style lang="scss" scoped>
.page {
  padding: 20px;
  height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* 防止页面滚动 */
}

.toolbar {
  margin-bottom: 20px;
  display: flex;
  gap: 15px;
  align-items: center;
  flex-shrink: 0;
  z-index: 10;
  background: white;
  padding: 10px;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.zoom-hint {
  margin-left: auto;
  font-size: 12px;
  color: #666;
  font-weight: 500;
}

/* 组件区域 */
.component-area {
  flex: 1;
  position: relative;
  overflow: hidden;
  border: 1px dashed #d9d9d9;
  border-radius: 8px;
  background: linear-gradient(45deg, #fafafa 25%, transparent 25%, transparent 75%, #fafafa 75%, #fafafa),
    linear-gradient(45deg, #fafafa 25%, transparent 25%, transparent 75%, #fafafa 75%, #fafafa);
  background-size: 20px 20px;
  background-position: 0 0, 10px 10px;
  min-height: 500px;
}

/* 拖拽和缩放容器 */
.drag-zoom-container {
  position: absolute;
  top: 50%;
  left: 50%;
  transform-origin: center;
  will-change: transform;
  /* 优化性能 */
  user-select: none;
  /* 防止文本选择 */
}

/* 组织架构树包装器 */
.org-tree-wrapper {
  display: inline-block;
  background: white;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  padding: 30px;
  position: relative;
}

/* 拖拽手柄 */
.drag-handle {
  position: absolute;
  bottom: 10px;
  right: 10px;
  width: 32px;
  height: 32px;
  background: rgba(64, 158, 255, 0.9);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  color: white;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
  z-index: 5;
  transition: all 0.2s;
}

.drag-handle:hover {
  background: rgba(64, 158, 255, 1);
  transform: scale(1.1);
}

.drag-handle:active {
  cursor: grabbing;
  transform: scale(0.95);
}

/* 节点相关样式保持不变 */
.node-label {
  padding: 4px 8px;
  cursor: pointer;
  border-radius: 3px;
}

.node-label:hover {
  background: #f5f7fa;
}

.node-body {
  display: inline-block;
  text-align: center;
  padding: 6px 10px;
  background: #fff;
  border: 1px solid #d9d9d9;
  border-radius: 4px;
}

.node-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.node-label {
  font-weight: 500;
}

.pos-list {
  list-style: none;
  margin: 4px 0 0 0;
  padding: 0;
  font-size: 12px;
  color: #666;
  text-align: left;
}

.pos-list li {
  padding: 2px 0;
}

.pos-list li::before {
  content: "· ";
  font-weight: bold;
  color: #409EFF;
}

.node-wrapper {
  display: inline-flex;
  align-items: flex-start;
  gap: 4px;
}

.fold-btn {
  cursor: pointer;
  font-size: 12px;
  color: #409EFF;
  user-select: none;
  padding: 2px 4px;
  box-sizing: border-box;
  transition: color 0.2s;
}

.fold-btn.collapsed {
  color: #909399;
}

/* 缩放比例指示器（浮动） */
.zoom-indicator {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 8px 12px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 10;
  pointer-events: none;
}

/* 边界提示（调试用） */
.boundary-debug {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  border: 2px dashed red;
  pointer-events: none;
  z-index: 100;
}
</style>