var app = new Vue({ el: '#app', data: { stations: [], devices: [], relations: [], // {stationId, deviceNo, deviceType} lines: [], // {x1, y1, x2, y2, stationId, deviceNo} draggingItem: null, // Currently dragged item draggingLine: null, // Temporary line for visual feedback saving: false, containerOffset: { top: 0, left: 0 } }, mounted: function() { this.loadData(); // Listen to window resize to redraw lines window.addEventListener('resize', this.updateLines); // Listen to scroll events on lists to redraw lines this.$refs.stationList.addEventListener('scroll', this.updateLines); this.$refs.deviceList.addEventListener('scroll', this.updateLines); }, beforeDestroy: function() { window.removeEventListener('resize', this.updateLines); }, methods: { loadData: function() { var that = this; var loading = this.$loading({ lock: true, text: '加载数据中...', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)' }); $.ajax({ url: baseUrl + '/basStationDevice/data/auth', headers: { 'token': localStorage.getItem('token') }, method: 'GET', success: function(res) { loading.close(); if (res.code === 200) { that.stations = res.data.stations || []; that.devices = res.data.devices || []; that.relations = res.data.relations || []; // Delay to wait for DOM rendering that.$nextTick(function() { that.updateLines(); }); } else if (res.code === 403) { top.location.href = baseUrl + "/"; } else { that.$message.error('加载数据失败: ' + res.msg); } }, error: function() { loading.close(); that.$message.error('加载请求异常'); } }); }, saveData: function() { var that = this; this.saving = true; $.ajax({ url: baseUrl + '/basStationDevice/save/auth', type: 'POST', headers: { 'token': localStorage.getItem('token') }, contentType: 'application/json', data: JSON.stringify(this.relations), success: function (res) { that.saving = false; if (res.code === 200) { that.$message.success('保存成功'); that.loadData(); } else { that.$message.error('保存失败: ' + res.msg); } }, error: function () { that.saving = false; that.$message.error('保存请求异常'); } }); }, isStationConnected: function(stationId) { return this.relations.some(function(r) { return r.stationId == stationId; }); }, isDeviceConnected: function(deviceNo, type) { return this.relations.some(function(r) { return r.deviceNo == deviceNo && r.deviceType == type; }); }, // --- Drag and Drop Logic --- onDragStart: function(event, item, type) { this.draggingItem = { item: item, type: type }; event.dataTransfer.effectAllowed = 'link'; // Optional: set custom drag image }, onDragEnd: function() { this.draggingItem = null; }, onDrop: function(event, targetDevice) { if (!this.draggingItem || this.draggingItem.type !== 'station') { return; } var station = this.draggingItem.item; // Check if exists var exists = this.relations.some(function(r) { return r.stationId == station.stationId && r.deviceNo == targetDevice.deviceNo && r.deviceType == targetDevice.type; }); if (exists) { this.$message.warning('该关联已存在'); return; } // Add relation this.relations.push({ stationId: station.stationId, deviceNo: targetDevice.deviceNo, deviceType: targetDevice.type }); this.$message.success('已建立关联: 站点 ' + station.stationId + ' -> ' + targetDevice.name); this.$nextTick(this.updateLines); }, confirmDelete: function(index) { var that = this; var link = this.relations[index]; // Note: lines index must match relations index? No. // We need to map line click to actual relation. // The lines array is rebuilt from relations, so we can store the index or relation object in the line object. // But let's check updateLines logic first. // In updateLines, we iterate relations. So index matches if filtered correctly? // Wait, updateLines maps relations 1-to-1 to lines IF DOM exists. // If DOM doesn't exist (scrolled out?), line might not exist. // But we should draw all lines? // Actually, if scrolled out, we might want to hide lines or point to edge. // For simplicity, we only draw lines for visible nodes or calculate positions for all. // Since lists are scrollable, calculating positions for invisible nodes is tricky (top/bottom are relative to viewport). // Better approach: Store the relation object in the line object. var rel = this.lines[index].relation; this.$confirm('确定删除该关联吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(function() { // Find index in relations array var idx = that.relations.indexOf(rel); if (idx > -1) { that.relations.splice(idx, 1); that.$nextTick(that.updateLines); that.$message.success('已删除'); } }).catch(function() {}); }, updateLines: function() { var that = this; var newLines = []; // Get SVG offset var svgElement = this.$el.querySelector('svg'); if (!svgElement) return; var svgRect = svgElement.getBoundingClientRect(); // We need positions relative to the SVG this.relations.forEach(function(rel) { var sDom = document.getElementById('station-' + rel.stationId); var dDom = document.getElementById('device-' + rel.deviceType + '-' + rel.deviceNo); if (sDom && dDom) { var sRect = sDom.getBoundingClientRect(); var dRect = dDom.getBoundingClientRect(); // Coordinates relative to SVG var x1 = sRect.right - svgRect.left; var y1 = sRect.top + sRect.height / 2 - svgRect.top; var x2 = dRect.left - svgRect.left; var y2 = dRect.top + dRect.height / 2 - svgRect.top; newLines.push({ x1: x1, y1: y1, x2: x2, y2: y2, relation: rel, stationId: rel.stationId, deviceNo: rel.deviceNo }); } }); this.lines = newLines; } } });