<!DOCTYPE html>
|
<html lang="zh-CN">
|
|
<head>
|
<meta charset="utf-8">
|
<title>站点与电视机绑定配置</title>
|
<meta name="renderer" content="webkit">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
<link rel="stylesheet" href="../../static/vue/element/element.css">
|
<link rel="stylesheet" href="../../static/css/cool.css">
|
<style>
|
html,
|
body {
|
height: 100%;
|
margin: 0;
|
padding: 0;
|
background-color: #f0f2f5;
|
}
|
|
#app {
|
height: 100%;
|
padding: 20px;
|
box-sizing: border-box;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.main-card {
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
overflow: hidden;
|
}
|
|
.flow-container {
|
flex: 1;
|
display: flex;
|
position: relative;
|
background: #fff;
|
border: 1px solid #ebeef5;
|
overflow: hidden;
|
}
|
|
.column {
|
width: 300px;
|
display: flex;
|
flex-direction: column;
|
border-right: 1px solid #ebeef5;
|
background-color: #fcfcfc;
|
z-index: 10;
|
}
|
|
.column.right {
|
border-right: none;
|
border-left: 1px solid #ebeef5;
|
position: absolute;
|
right: 0;
|
top: 0;
|
bottom: 0;
|
}
|
|
.column-header {
|
padding: 15px;
|
text-align: center;
|
font-weight: bold;
|
background-color: #f5f7fa;
|
border-bottom: 1px solid #ebeef5;
|
color: #606266;
|
}
|
|
.list-container {
|
flex: 1;
|
overflow-y: auto;
|
padding: 10px;
|
}
|
|
.node-item {
|
padding: 12px;
|
margin-bottom: 10px;
|
background-color: #fff;
|
border: 1px solid #dcdfe6;
|
border-radius: 4px;
|
cursor: grab;
|
transition: all 0.3s;
|
position: relative;
|
color: #606266;
|
font-size: 14px;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.node-item:hover {
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
border-color: #409eff;
|
}
|
|
.node-item.active {
|
background-color: #ecf5ff;
|
border-color: #409eff;
|
color: #409eff;
|
}
|
|
.station-node {
|
border-left: 4px solid #67c23a;
|
}
|
|
.tv-node {
|
border-left: 4px solid #e6a23c;
|
}
|
|
.canvas-area {
|
flex: 1;
|
position: relative;
|
margin-right: 300px;
|
background-color: #fafafa;
|
overflow: hidden;
|
}
|
|
svg {
|
position: absolute;
|
top: 0;
|
left: 0;
|
width: 100%;
|
height: 100%;
|
pointer-events: none;
|
}
|
|
svg line {
|
stroke: #909399;
|
stroke-width: 2;
|
cursor: pointer;
|
pointer-events: stroke;
|
transition: stroke-width 0.2s, stroke 0.2s;
|
}
|
|
svg line:hover {
|
stroke: #f56c6c;
|
stroke-width: 4;
|
}
|
|
.toolbar {
|
padding: 10px 0;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
</style>
|
</head>
|
|
<body>
|
<div id="app" v-cloak>
|
<el-card class="main-card"
|
:body-style="{ padding: '0px', display: 'flex', flexDirection: 'column', height: '100%' }">
|
<div slot="header" class="clearfix">
|
<span style="font-weight: bold; font-size: 16px;">站点与电视机绑定配置</span>
|
<div style="float: right;">
|
<el-button type="primary" icon="el-icon-check" size="small" @click="saveData"
|
:loading="saving">保存配置</el-button>
|
<el-button icon="el-icon-refresh" size="small" @click="loadData">刷新</el-button>
|
</div>
|
</div>
|
<div class="toolbar" style="padding: 10px 20px; border-bottom: 1px solid #ebeef5;">
|
<el-alert title="操作说明:拖拽左侧【站点】到右侧【电视机】上建立绑定。点击连线可删除绑定。" type="info" show-icon
|
:closable="false"></el-alert>
|
</div>
|
<div class="flow-container" ref="flowContainer">
|
<div class="column">
|
<div class="column-header">站点 ({{ stations.length }})</div>
|
<div class="list-container" ref="stationList">
|
<div v-for="item in stations" :key="'s-' + item.stationId" :id="'station-' + item.stationId"
|
class="node-item station-node" :class="{ active: isStationBound(item.stationId) }"
|
draggable="true" @dragstart="onDragStart($event, item, 'station')" @dragend="onDragEnd">
|
<span>{{ item.stationId }} - {{ item.memo || '无备注' }}</span>
|
</div>
|
</div>
|
</div>
|
<div class="canvas-area">
|
<svg width="100%" height="100%">
|
<line v-for="(link, index) in lines" :key="index" :x1="link.x1" :y1="link.y1" :x2="link.x2"
|
:y2="link.y2" @click="confirmDelete(index)">
|
<title>点击删除绑定: 站点 {{ link.stationId }} -> 电视 {{ link.tvName }}</title>
|
</line>
|
<line v-if="draggingLine" :x1="draggingLine.x1" :y1="draggingLine.y1" :x2="draggingLine.x2"
|
:y2="draggingLine.y2" stroke="#409eff" stroke-dasharray="5,5" stroke-width="2" />
|
</svg>
|
</div>
|
<div class="column right">
|
<div class="column-header">电视机 ({{ tvs.length }})</div>
|
<div class="list-container" ref="tvList">
|
<div v-for="item in tvs" :key="'t-' + item.id" :id="'tv-' + item.id" class="node-item tv-node"
|
:class="{ active: isTvBound(item.id) }" @dragover.prevent @drop="onDrop($event, item)">
|
<span>{{ item.name }} ({{ item.ip }})</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
</el-card>
|
</div>
|
<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
|
<script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
|
<script type="text/javascript" src="../../static/vue/element/element.js"></script>
|
<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
|
<script>
|
var app = new Vue({
|
el: '#app',
|
data: {
|
stations: [],
|
tvs: [],
|
relations: [],
|
lines: [],
|
draggingItem: null,
|
draggingLine: null,
|
saving: false
|
},
|
mounted: function () {
|
this.loadData();
|
window.addEventListener('resize', this.updateLines);
|
this.$nextTick(function () {
|
if (this.$refs.stationList) this.$refs.stationList.addEventListener('scroll', this.updateLines);
|
if (this.$refs.tvList) this.$refs.tvList.addEventListener('scroll', this.updateLines);
|
}.bind(this));
|
},
|
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 + '/basStationTv/data/auth',
|
headers: { 'token': localStorage.getItem('token') },
|
method: 'GET',
|
success: function (res) {
|
loading.close();
|
if (res.code === 200) {
|
that.stations = res.data.stations || [];
|
that.tvs = res.data.tvs || [];
|
that.relations = res.data.relations || [];
|
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;
|
var payload = {
|
relations: this.relations
|
};
|
$.ajax({
|
url: baseUrl + '/basStationTv/save/auth',
|
type: 'POST',
|
headers: { 'token': localStorage.getItem('token') },
|
contentType: 'application/json',
|
data: JSON.stringify(payload),
|
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('保存请求异常');
|
}
|
});
|
},
|
isStationBound: function (stationId) {
|
return this.relations.some(function (r) { return r.stationId == stationId; });
|
},
|
isTvBound: function (tvId) {
|
return this.relations.some(function (r) { return r.tvId == tvId; });
|
},
|
onDragStart: function (event, item, type) {
|
this.draggingItem = { item: item, type: type };
|
event.dataTransfer.effectAllowed = 'link';
|
},
|
onDragEnd: function () {
|
this.draggingItem = null;
|
this.draggingLine = null;
|
},
|
onDrop: function (event, targetTv) {
|
if (!this.draggingItem || this.draggingItem.type !== 'station') return;
|
var station = this.draggingItem.item;
|
var exists = this.relations.some(function (r) {
|
return r.stationId == station.stationId && r.tvId == targetTv.id;
|
});
|
if (exists) {
|
this.$message.warning('该绑定已存在');
|
return;
|
}
|
this.relations.push({
|
stationId: station.stationId,
|
tvId: targetTv.id
|
});
|
this.$message.success('已建立绑定: 站点 ' + station.stationId + ' -> 电视 ' + targetTv.name);
|
this.$nextTick(this.updateLines);
|
},
|
confirmDelete: function (index) {
|
var rel = this.lines[index] && this.lines[index].relation;
|
if (!rel) return;
|
|
this.$confirm('确定要删除该绑定吗?', '提示', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
}).then(function () {
|
this.relations = this.relations.filter(function (r) {
|
return !(r.stationId == rel.stationId && r.tvId == rel.tvId);
|
});
|
this.$nextTick(this.updateLines);
|
}.bind(this)).catch(function () { });
|
},
|
updateLines: function () {
|
var svg = document.querySelector('svg');
|
if (!svg) return;
|
var svgRect = svg.getBoundingClientRect();
|
var newLines = [];
|
var that = this;
|
this.relations.forEach(function (rel) {
|
var sDom = document.getElementById('station-' + rel.stationId);
|
var tDom = document.getElementById('tv-' + rel.tvId);
|
if (sDom && tDom) {
|
var sRect = sDom.getBoundingClientRect();
|
var tRect = tDom.getBoundingClientRect();
|
var x1 = sRect.right - svgRect.left;
|
var y1 = sRect.top + sRect.height / 2 - svgRect.top;
|
var x2 = tRect.left - svgRect.left;
|
var y2 = tRect.top + tRect.height / 2 - svgRect.top;
|
|
var tvName = 'Unknown';
|
var tv = that.tvs.find(function (t) { return t.id == rel.tvId; });
|
if (tv) { tvName = tv.name; }
|
|
newLines.push({
|
x1: x1, y1: y1, x2: x2, y2: y2,
|
relation: rel,
|
stationId: rel.stationId,
|
tvName: tvName
|
});
|
}
|
});
|
this.lines = newLines;
|
}
|
}
|
});
|
</script>
|
</body>
|
|
</html>
|