优化 大屏

qianlishi 3 years ago
parent 5d3993444e
commit 4cb06437d5

@ -4,6 +4,6 @@ const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
BASE_API: '"http://127.0.0.1:9095"'
// BASE_API: '"http://10.108.26.197:9095"'
// BASE_API: '"http://127.0.0.1:9095"'
BASE_API: '"http://10.108.26.197:9095"'
})

@ -3,27 +3,7 @@
height: 100%;
background: #242a30;
color: #fff;
.layout-bar {
height: 40px;
line-height: 40px;
font-size: 12px;
padding: 0 10px;
display: flex;
flex-direction: row;
overflow: hidden;
.bar-item {
margin-right: 20px;
cursor: pointer;
.iconfont {
font-size: 12px;
margin-right: 4px;
}
.el-dropdown-link {
color: #fff;
cursor: pointer;
}
}
}
.layout-container {
width: 100%;
height: calc(100vh - 40px);
@ -31,70 +11,6 @@
flex-direction: row;
justify-content: space-between;
overflow: hidden;
.layout-left {
width: 200px;
background: #242a30;
overflow-x: hidden;
overflow-y: auto;
.chart-type {
display: flex;
flex-direction: row;
overflow: hidden;
.type-left {
width: 100%;
height: calc(100vh - 80px);
text-align: center;
/deep/.el-tabs__header {
width: 30%;
margin-right: 0;
.el-tabs__nav-wrap {
&::after {
background: transparent;
}
.el-tabs__item {
text-align: center;
width: 100%;
color: #fff;
padding: 0;
}
}
}
/deep/.el-tabs__content {
width: 70%;
}
}
}
//
.tools-item {
display: flex;
position: relative;
width: 100%;
height: 48px;
align-items: center;
-webkit-box-align: center;
padding: 0 6px;
cursor: pointer;
font-size: 12px;
margin-bottom: 1px;
.tools-item-icon {
color: #409eff;
margin-right: 10px;
width: 53px;
height: 30px;
line-height: 30px;
text-align: center;
display: block;
border: 1px solid #3a4659;
background: #282a30;
}
.tools-item-text {
}
}
/deep/.el-tabs__content {
padding: 0;
}
}
.layout-middle {
// display: flex;
position: relative;
@ -107,50 +23,7 @@
align-items: center;
vertical-align: middle;
text-align: center;
.workbench-container {
position: relative;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
.vueRuler {
width: 100%;
padding: 18px 0px 0px 18px;
}
.workbench {
background-color: #1e1e1e;
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
margin: 0;
padding: 0;
}
.bg-grid {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: 30px 30px, 30px 30px;
background-image: linear-gradient(
hsla(0, 0%, 100%, 0.1) 1px,
transparent 0
),
linear-gradient(90deg, hsla(0, 0%, 100%, 0.1) 1px, transparent 0);
// z-index: 2;
}
}
}
.layout-right {
width: 300px;
}
@ -232,6 +105,4 @@
}
}
}
/deep/.el-dropdown-menu__item {
max-width: none;
}

@ -180,7 +180,8 @@ export default {
},
openDesign(val) {
let routeUrl = this.$router.resolve({
path: "/bigscreen/designer",
path: "/screenDesigner",
// path: "/bigscreen/designer",
query: {
reportCode: val.reportCode
}

@ -24,7 +24,9 @@
export default {
props: {
styleObj: Object,
visible: Boolean
visible: Boolean,
widgets: Array,
rightClickIndex: Number
},
data() {
return {};
@ -49,7 +51,8 @@ export default {
type: "warning"
})
.then(() => {
this.$emit("deletelayer");
console.log(this.rightClickIndex);
this.widgets.splice(this.rightClickIndex, 1);
this.$message({
type: "success",
message: "删除成功!"
@ -63,19 +66,41 @@ export default {
});
},
copyLayer() {
this.$emit("copylayer");
const obj = this.deepClone(this.widgets[this.rightClickIndex]);
this.widgets.splice(this.widgets.length, 0, obj);
},
istopLayer() {
this.$emit("istopLayer");
if (this.rightClickIndex + 1 < this.widgets.length) {
const temp = this.widgets.splice(this.rightClickIndex, 1)[0];
this.widgets.push(temp);
}
},
setlowLayer() {
this.$emit("setlowLayer");
if (this.rightClickIndex != 0) {
this.widgets.unshift(this.widgets.splice(this.rightClickIndex, 1)[0]);
}
},
moveupLayer() {
this.$emit("moveupLayer");
if (this.rightClickIndex != 0) {
this.widgets[this.rightClickIndex] = this.widgets.splice(
this.rightClickIndex - 1,
1,
this.widgets[this.rightClickIndex]
)[0];
} else {
this.widgets.push(this.widgets.shift());
}
},
movedownLayer() {
this.$emit("movedownLayer");
if (this.rightClickIndex != this.widgets.length - 1) {
this.widgets[this.rightClickIndex] = this.widgets.splice(
this.rightClickIndex + 1,
1,
this.widgets[this.rightClickIndex]
)[0];
} else {
this.widgets.unshift(this.widgets.splice(this.rightClickIndex, 1)[0]);
}
}
}
};

@ -1,218 +1,48 @@
<template>
<div class="layout">
<!-- 操作栏 -->
<div class="layout-bar">
<div class="bar-item" @click="saveData">
<i class="iconfont iconsave"></i>保存
</div>
<div class="bar-item" @click="viewScreen">
<i class="iconfont iconyulan"></i>预览
</div>
<div class="bar-item" @click="handleUndo">
<i class="iconfont iconundo"></i>撤销
</div>
<div class="bar-item" @click="handleRedo">
<i class="iconfont iconhuifubeifen"></i>恢复
</div>
<div class="bar-item">
<el-upload
class="el-upload"
ref="upload"
:action="uploadUrl"
:headers="headers"
accept=".zip"
:on-success="handleUpload"
:on-error="handleError"
:show-file-list="false"
:limit="1"
>
<i class="iconfont icondaoru"></i>
</el-upload>
导入
</div>
<div class="bar-item">
<i class="iconfont icondaochu"></i>
<el-dropdown @command="exportDashboard">
<span class="el-dropdown-link">
导出<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="1">导出(包含数据集)</el-dropdown-item>
<el-dropdown-item command="0">导出(不包含数据集)</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
<div class="layout-container">
<!-- 图表 -->
<el-tabs class="layout-left" type="border-card">
<el-tab-pane>
<span slot="label"><i class="el-icon-date icon"></i>工具栏</span>
<div class="chart-type">
<el-tabs class="type-left" tab-position="left">
<el-tab-pane
v-for="(item, index) in widgetTools"
:key="index"
:label="item.name"
>
<draggable
v-for="(it, idx) in item.list"
:key="idx"
@end="evt => widgetOnDragged(evt, it.code)"
>
<div class="tools-item">
<span class="tools-item-icon">
<i class="iconfont" :class="it.icon"></i>
</span>
<span class="tools-item-text">{{ it.label }}</span>
</div>
</draggable>
</el-tab-pane>
</el-tabs>
</div>
</el-tab-pane>
<el-tab-pane>
<span slot="label" class="icon"
><i class="el-icon-date icon"></i>图层</span
>
<div
v-for="(item, index) in layerWidget"
:key="'item' + index"
class="tools-item"
:class="widgetIndex == index ? 'is-active' : ''"
@click="layerClick(index)"
>
<span class="tools-item-icon">
<i class="iconfont" :class="item.icon"></i>
</span>
<span class="tools-item-text">{{ item.label }}</span>
</div>
</el-tab-pane>
</el-tabs>
<!-- 设计器 -->
<!-- 左侧菜单 -->
<left-menu
:layerWidget="layerWidget"
:widgetTools="widgetTools"
:widgetIndex="widgetIndex"
@widgetOnDragged="widgetOnDragged"
@layerClick="layerClick"
/>
<div
class="layout-middle"
:style="{ width: middleWidth + 'px', height: middleHeight + 'px' }"
:style="{
width: middleWidth + 'px',
height: middleHeight + 'px'
}"
>
<div
class="workbench-container"
:style="{
width: bigscreenWidthInWorkbench + 'px',
height: bigscreenHeightInWorkbench + 'px'
}"
@mousedown="handleMouseDown"
>
<vue-ruler-tool
v-model="dashboard.presetLine"
class="vueRuler"
:step-length="50"
:parent="true"
:position="'relative'"
:is-scale-revise="true"
:visible.sync="dashboard.presetLineVisible"
>
<div
id="workbench"
class="workbench"
:style="{
transform: workbenchTransform,
width: bigscreenWidth + 'px',
height: bigscreenHeight + 'px',
'background-color': dashboard.backgroundColor,
'background-image': 'url(' + dashboard.backgroundImage + ')',
'background-position': '0% 0%',
'background-size': '100% 100%',
'background-repeat': 'initial',
'background-attachment': 'initial',
'background-origin': 'initial',
'background-clip': 'initial'
}"
@click.self="setOptionsOnClickScreen"
>
<div v-if="grade" class="bg-grid"></div>
<widget
ref="widgets"
v-for="(widget, index) in widgets"
:key="index"
v-model="widget.value"
:index="index"
:step="step"
:type="widget.type"
:bigscreen="{ bigscreenWidth, bigscreenHeight }"
@onActivated="setOptionsOnClickWidget"
@contextmenu.prevent.native="rightClick($event, index)"
@mousedown.prevent.native="widgetsClick(index)"
@mouseup.prevent.native="widgetsMouseup"
/>
</div>
</vue-ruler-tool>
</div>
</div>
<!-- 顶部按钮 -->
<top-bar
:dashboard="dashboard"
:widgets="widgets"
@refresh="initEchartData"
/>
<!-- 设置 -->
<div class="layout-right">
<el-tabs v-model="activeName" type="border-card" :stretch="true">
<el-tab-pane
v-if="
isNotNull(widgetOptions.setup) ||
isNotNull(widgetOptions.collapse)
"
name="first"
label="配置"
>
<dynamicForm
ref="formData"
:options="widgetOptions.setup"
@onChanged="val => widgetValueChanged('setup', val)"
/>
</el-tab-pane>
<el-tab-pane
v-if="isNotNull(widgetOptions.data)"
name="second"
label="数据"
>
<dynamicForm
ref="formData"
:options="widgetOptions.data"
@onChanged="val => widgetValueChanged('data', val)"
/>
</el-tab-pane>
<el-tab-pane
v-if="isNotNull(widgetOptions.position)"
name="third"
label="坐标"
>
<dynamicForm
ref="formData"
:options="widgetOptions.position"
@onChanged="val => widgetValueChanged('position', val)"
/>
</el-tab-pane>
</el-tabs>
<!-- 设计器 -->
<middleScreen
ref="middleScreen"
:middleWidth="middleWidth"
:middleHeight="middleHeight"
:dashboard="dashboard"
:widgets="widgets"
@change="changeOptions"
/>
</div>
<!-- 右侧配置 -->
<right-config
:widgetOptions="widgetOptions"
@widgetValueChanged="widgetValueChanged"
/>
</div>
<content-menu
:visible.sync="visibleContentMenu"
:style-obj="styleObj"
@deletelayer="deletelayer"
@copylayer="copylayer"
@istopLayer="istopLayer"
@setlowLayer="setlowLayer"
@moveupLayer="moveupLayer"
@movedownLayer="movedownLayer"
/>
</div>
</template>
<script>
import {
insertDashboard,
detailDashboard,
importDashboard,
exportDashboard
} from "@/api/bigscreen";
import { detailDashboard, importDashboard } from "@/api/bigscreen";
import {
swapArr,
setDefaultValue,
@ -222,42 +52,31 @@ import {
handleBigScreen,
handlerLayerWidget
} from "./util/screen";
import { screenConfig } from "./config/texts/screenConfig.js";
import { widgetTools, getToolByCode } from "./config/index.js";
import VueRulerTool from "vue-ruler-tool"; //
import widget from "./widget/index.vue";
import dynamicForm from "./components/dynamicForm.vue";
import draggable from "vuedraggable";
import contentMenu from "./components/contentMenu";
import { getToken } from "@/utils/auth";
import { Revoke } from "@/utils/revoke"; // 2022-02-22
import leftMenu from "./layout/leftMenu.vue";
import topBar from "./layout/topBar.vue";
import middleScreen from "./layout/middleScreen.vue";
import rightConfig from "./layout/rightConfig.vue";
export default {
components: {
VueRulerTool,
widget,
dynamicForm,
draggable,
contentMenu
leftMenu,
topBar,
middleScreen,
rightConfig
},
data() {
return {
uploadUrl:
process.env.BASE_API +
"/reportDashboard/import/" +
this.$route.query.reportCode,
grade: false,
//
layerWidget: [],
widgetTools: widgetTools, // js
widthLeftForTools: 200, //
widthLeftForToolsHideButton: 15, //
widthLeftForOptions: 300, //
widthPaddingTools: 18,
toolIsShow: true, //
widgetTools: widgetTools,
widgetIndex: 0,
bigscreenWidth: 1920, //
bigscreenHeight: 1080,
revoke: null, // 2022-02-22
//
//
grade: false,
// gaea_report_dashboard
dashboard: {
id: null,
@ -266,10 +85,13 @@ export default {
height: 1080, //
backgroundColor: "", //
backgroundImage: "", //
refreshSeconds: null, //
presetLine: [], // 线
presetLineVisible: true // 线
refreshSeconds: null //
},
//
widthLeftForTools: 200, //
widthRightForOptions: 300, //
//
screenCode: "",
//
@ -293,105 +115,42 @@ export default {
}
], //
//
widgetIndex: 0,
//
widgetOptions: {
setup: [], //
data: [], //
position: [] //
},
flagWidgetClickStopPropagation: false, // click
styleObj: {
left: 0,
top: 0
},
visibleContentMenu: false,
rightClickIndex: -1,
activeName: "first"
};
},
computed: {
step() {
return Number(100 / (this.bigscreenScaleInWorkbench * 100));
},
headers() {
return {
Authorization: getToken() // token
};
},
//
middleWidth() {
let widthLeftAndRight = 0;
if (this.toolIsShow) {
widthLeftAndRight += this.widthLeftForTools; //
setup: [],
data: [],
position: []
}
widthLeftAndRight += this.widthLeftForToolsHideButton; //
widthLeftAndRight += this.widthLeftForOptions; //
let middleWidth = this.bodyWidth - widthLeftAndRight;
return middleWidth;
},
middleHeight() {
return this.bodyHeight;
},
//
bigscreenScaleInWorkbench() {
let widthScale =
(this.middleWidth - this.widthPaddingTools) / this.bigscreenWidth;
let heightScale =
(this.middleHeight - this.widthPaddingTools) / this.bigscreenHeight;
return Math.min(widthScale, heightScale);
},
workbenchTransform() {
return `scale(${this.bigscreenScaleInWorkbench}, ${
this.bigscreenScaleInWorkbench
})`;
},
//
bigscreenWidthInWorkbench() {
return (
getPXUnderScale(this.bigscreenScaleInWorkbench, this.bigscreenWidth) +
this.widthPaddingTools
);
},
bigscreenHeightInWorkbench() {
return (
getPXUnderScale(this.bigscreenScaleInWorkbench, this.bigscreenHeight) +
this.widthPaddingTools
);
}
};
},
watch: {
widgets: {
handler(val) {
this.layerWidget = handlerLayerWidget(val, getToolByCode);
//
this.$nextTick(() => {
this.revoke.push(this.widgets);
});
},
deep: true
}
},
created() {
/* 以下是记录历史的 */
this.revoke = new Revoke();
computed: {
//
middleWidth() {
return (
this.bodyWidth - this.widthLeftForTools - this.widthRightForOptions
);
},
//
middleHeight() {
return this.bodyHeight;
}
},
mounted() {
this.initScreen();
//
// this.initEchartData();
this.widgets = [];
window.addEventListener("mouseup", () => {
this.grade = false;
});
this.initEchartData();
},
methods: {
//
initScreen() {
this.widgetOptions = screenConfig["options"];
},
// echrats
async initEchartData() {
const reportCode = this.$route.query.reportCode;
@ -445,71 +204,17 @@ export default {
//
const widgetJsonValue = handleDefaultValue(widgetJson);
//20220222
widgetJsonValue.value.position.left =
x - widgetJsonValue.value.position.width / 2;
widgetJsonValue.value.position.top =
y - widgetJsonValue.value.position.height / 2;
//
this.widgets.push(this.deepClone(widgetJsonValue));
//
this.setOptionsOnClickWidget(this.widgets.length - 1);
},
//
setOptionsOnClickScreen() {
this.screenCode = "screen";
//
this.activeName = "first";
this.widgetOptions = screenConfig["options"];
this.$refs.middleScreen.setOptionsOnClickWidget(this.widgets.length - 1);
},
layerClick(index) {
this.widgetIndex = index;
this.widgetsClick(index);
},
//
setOptionsOnClickWidget(obj) {
console.log(obj);
this.screenCode = "";
if (typeof obj == "number") {
this.widgetOptions = this.deepClone(this.widgets[obj]["options"]);
return;
}
if (obj.index < 0 || obj.index >= this.widgets.length) {
return;
}
this.widgetIndex = obj.index;
this.widgets[obj.index].value.position = obj;
this.widgets[obj.index].options.position.forEach(el => {
for (const key in obj) {
if (el.name == key) {
el.value = obj[key];
}
}
});
this.widgetOptions = this.deepClone(this.widgets[obj.index]["options"]);
},
widgetsClick(index) {
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
if (i == index) {
this.$refs.widgets[i].$refs.draggable.setActive(true);
} else {
this.$refs.widgets[i].$refs.draggable.setActive(false);
}
}
this.setOptionsOnClickWidget(index);
this.grade = true;
},
widgetsMouseup(e) {
this.grade = false;
},
handleMouseDown() {
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
this.$refs.widgets[i].$refs.draggable.setActive(false);
}
this.$refs.middleScreen.widgetsClick(index);
},
//
widgetValueChanged(key, val) {
if (this.screenCode == "screen") {
@ -539,176 +244,12 @@ export default {
}
}
},
rightClick(event, index) {
this.rightClickIndex = index;
const left = event.clientX;
const top = event.clientY;
if (left || top) {
this.styleObj = {
left: left + "px",
top: top + "px",
display: "block"
};
}
this.visibleContentMenu = true;
return false;
changeOptions(widgetOptions) {
this.widgetOptions = widgetOptions;
},
datadragEnd(evt) {
evt.preventDefault();
this.widgets = swapArr(this.widgets, evt.oldIndex, evt.newIndex);
},
//
async saveData() {
if (!this.widgets || this.widgets.length == 0) {
this.$message.error("请添加组件");
return;
}
const screenData = {
reportCode: this.$route.query.reportCode,
dashboard: {
title: this.dashboard.title,
width: this.dashboard.width,
height: this.dashboard.height,
backgroundColor: this.dashboard.backgroundColor,
backgroundImage: this.dashboard.backgroundImage
},
widgets: this.widgets
};
const { code, data } = await insertDashboard(screenData);
if (code == "200") {
this.$message.success("保存成功!");
}
},
//
viewScreen() {
let routeUrl = this.$router.resolve({
path: "/screen/preview",
query: { reportCode: this.$route.query.reportCode }
});
window.open(routeUrl.href, "_blank");
},
//
handleRedo() {
const record = this.revoke.redo();
if (!record) {
return false;
}
this.widgets = record;
},
//
handleUndo() {
const record = this.revoke.undo();
if (!record) {
return false;
}
this.widgets = record;
},
//
handleUpload(response, file, fileList) {
//el-upload
this.$refs.upload.clearFiles();
//
this.initEchartData();
if (response.code == "200") {
this.$message({
message: "导入成功!",
type: "success"
});
} else {
this.$message({
message: response.message,
type: "error"
});
}
},
//
handleError(err) {
this.$message({
message: "上传失败!",
type: "error"
});
},
//
async exportDashboard(val) {
console.log(val);
const fileName = this.$route.query.reportCode + ".zip";
const param = {
reportCode: this.$route.query.reportCode,
showDataSet: val
};
exportDashboard(param).then(res => {
const that = this;
const type = res.type;
if (type == "application/json") {
let reader = new FileReader();
reader.readAsText(res, "utf-8");
reader.onload = function() {
const data = JSON.parse(reader.result);
that.$message.error(data.message);
};
return;
}
const blob = new Blob([res], { type: "application/octet-stream" });
if (window.navigator.msSaveOrOpenBlob) {
//msSaveOrOpenBlobbool
navigator.msSaveBlob(blob, fileName); //
} else {
const link = document.createElement("a"); //a
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
window.URL.revokeObjectURL(link.href);
}
});
},
//
deletelayer() {
this.widgets.splice(this.rightClickIndex, 1);
},
//
copylayer() {
const obj = this.deepClone(this.widgets[this.rightClickIndex]);
this.widgets.splice(this.widgets.length, 0, obj);
},
//
istopLayer() {
if (this.rightClickIndex + 1 < this.widgets.length) {
const temp = this.widgets.splice(this.rightClickIndex, 1)[0];
this.widgets.push(temp);
}
},
//
setlowLayer() {
if (this.rightClickIndex != 0) {
this.widgets.unshift(this.widgets.splice(this.rightClickIndex, 1)[0]);
}
},
//
moveupLayer() {
if (this.rightClickIndex != 0) {
this.widgets[this.rightClickIndex] = this.widgets.splice(
this.rightClickIndex - 1,
1,
this.widgets[this.rightClickIndex]
)[0];
} else {
this.widgets.push(this.widgets.shift());
}
},
//
movedownLayer() {
if (this.rightClickIndex != this.widgets.length - 1) {
this.widgets[this.rightClickIndex] = this.widgets.splice(
this.rightClickIndex + 1,
1,
this.widgets[this.rightClickIndex]
)[0];
} else {
this.widgets.unshift(this.widgets.splice(this.rightClickIndex, 1)[0]);
}
}
}
};

@ -0,0 +1,725 @@
<template>
<div class="layout">
<div class="layout-container">
<!-- 图表 -->
<el-tabs class="layout-left" type="border-card">
<el-tab-pane>
<span slot="label"><i class="el-icon-date icon"></i>工具栏</span>
<div class="chart-type">
<el-tabs class="type-left" tab-position="left">
<el-tab-pane
v-for="(item, index) in widgetTools"
:key="index"
:label="item.name"
>
<draggable
v-for="(it, idx) in item.list"
:key="idx"
@end="evt => widgetOnDragged(evt, it.code)"
>
<div class="tools-item">
<span class="tools-item-icon">
<i class="iconfont" :class="it.icon"></i>
</span>
<span class="tools-item-text">{{ it.label }}</span>
</div>
</draggable>
</el-tab-pane>
</el-tabs>
</div>
</el-tab-pane>
<el-tab-pane>
<span slot="label" class="icon"
><i class="el-icon-date icon"></i>图层</span
>
<div
v-for="(item, index) in layerWidget"
:key="'item' + index"
class="tools-item"
:class="widgetIndex == index ? 'is-active' : ''"
@click="layerClick(index)"
>
<span class="tools-item-icon">
<i class="iconfont" :class="item.icon"></i>
</span>
<span class="tools-item-text">{{ item.label }}</span>
</div>
</el-tab-pane>
</el-tabs>
<div
class="layout-middle"
:style="{
width: middleWidth + 'px',
height: middleHeight + 'px'
}"
>
<!-- 操作栏 -->
<div class="layout-bar">
<div class="bar-item" @click="saveData">
<i class="iconfont iconsave"></i>保存
</div>
<div class="bar-item" @click="viewScreen">
<i class="iconfont iconyulan"></i>预览
</div>
<div class="bar-item" @click="handleUndo">
<i class="iconfont iconundo"></i>撤销
</div>
<div class="bar-item" @click="handleRedo">
<i class="iconfont iconhuifubeifen"></i>恢复
</div>
<div class="bar-item">
<el-upload
class="el-upload"
ref="upload"
:action="uploadUrl"
:headers="headers"
accept=".zip"
:on-success="handleUpload"
:on-error="handleError"
:show-file-list="false"
:limit="1"
>
<i class="iconfont icondaoru"></i>
</el-upload>
导入
</div>
<div class="bar-item">
<i class="iconfont icondaochu"></i>
<el-dropdown @command="exportDashboard">
<span class="el-dropdown-link">
导出<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="1"
>导出(包含数据集)</el-dropdown-item
>
<el-dropdown-item command="0"
>导出(不包含数据集)</el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
<!-- 设计器 -->
<div
class="workbench-container"
:style="{
width: bigscreenWidthInWorkbench + 'px',
height: bigscreenHeightInWorkbench + 'px'
}"
@mousedown="handleMouseDown"
>
<vue-ruler-tool
v-model="dashboard.presetLine"
class="vueRuler"
:step-length="50"
:parent="true"
:position="'relative'"
:is-scale-revise="true"
:visible.sync="dashboard.presetLineVisible"
>
<div
id="workbench"
class="workbench"
:style="{
transform: workbenchTransform,
width: bigscreenWidth + 'px',
height: bigscreenHeight + 'px',
'background-color': dashboard.backgroundColor,
'background-image': 'url(' + dashboard.backgroundImage + ')',
'background-position': '0% 0%',
'background-size': '100% 100%',
'background-repeat': 'initial',
'background-attachment': 'initial',
'background-origin': 'initial',
'background-clip': 'initial'
}"
@click.self="setOptionsOnClickScreen"
>
<div v-if="grade" class="bg-grid"></div>
<widget
ref="widgets"
v-for="(widget, index) in widgets"
:key="index"
v-model="widget.value"
:index="index"
:step="step"
:type="widget.type"
:bigscreen="{ bigscreenWidth, bigscreenHeight }"
@onActivated="setOptionsOnClickWidget"
@contextmenu.prevent.native="rightClick($event, index)"
@mousedown.prevent.native="widgetsClick(index)"
@mouseup.prevent.native="widgetsMouseup"
/>
</div>
</vue-ruler-tool>
</div>
</div>
<!-- 设置 -->
<div class="layout-right">
<el-tabs v-model="activeName" type="border-card" :stretch="true">
<el-tab-pane
v-if="
isNotNull(widgetOptions.setup) ||
isNotNull(widgetOptions.collapse)
"
name="first"
label="配置"
>
<dynamicForm
ref="formData"
:options="widgetOptions.setup"
@onChanged="val => widgetValueChanged('setup', val)"
/>
</el-tab-pane>
<el-tab-pane
v-if="isNotNull(widgetOptions.data)"
name="second"
label="数据"
>
<dynamicForm
ref="formData"
:options="widgetOptions.data"
@onChanged="val => widgetValueChanged('data', val)"
/>
</el-tab-pane>
<el-tab-pane
v-if="isNotNull(widgetOptions.position)"
name="third"
label="坐标"
>
<dynamicForm
ref="formData"
:options="widgetOptions.position"
@onChanged="val => widgetValueChanged('position', val)"
/>
</el-tab-pane>
</el-tabs>
</div>
</div>
<content-menu
:visible.sync="visibleContentMenu"
:style-obj="styleObj"
@deletelayer="deletelayer"
@copylayer="copylayer"
@istopLayer="istopLayer"
@setlowLayer="setlowLayer"
@moveupLayer="moveupLayer"
@movedownLayer="movedownLayer"
/>
</div>
</template>
<script>
import {
insertDashboard,
detailDashboard,
importDashboard,
exportDashboard
} from "@/api/bigscreen";
import {
swapArr,
setDefaultValue,
handleDefaultValue,
getPXUnderScale,
handleInitEchartsData,
handleBigScreen,
handlerLayerWidget
} from "./util/screen";
import { screenConfig } from "./config/texts/screenConfig.js";
import { widgetTools, getToolByCode } from "./config/index.js";
import VueRulerTool from "vue-ruler-tool"; //
import widget from "./widget/index.vue";
import dynamicForm from "./components/dynamicForm.vue";
import draggable from "vuedraggable";
import contentMenu from "./components/contentMenu";
import { getToken } from "@/utils/auth";
import { Revoke } from "@/utils/revoke"; // 2022-02-22
export default {
components: {
VueRulerTool,
widget,
dynamicForm,
draggable,
contentMenu
},
data() {
return {
uploadUrl:
process.env.BASE_API +
"/reportDashboard/import/" +
this.$route.query.reportCode,
grade: false,
layerWidget: [],
widgetTools: widgetTools, // js
widthLeftForTools: 200, //
widthLeftForToolsHideButton: 15, //
widthLeftForOptions: 300, //
widthPaddingTools: 18,
toolIsShow: true, //
bigscreenWidth: 1920, //
bigscreenHeight: 1080,
revoke: null, // 2022-02-22
// gaea_report_dashboard
dashboard: {
id: null,
title: "", //
width: 1920, //
height: 1080, //
backgroundColor: "", //
backgroundImage: "", //
refreshSeconds: null, //
presetLine: [], // 线
presetLineVisible: true // 线
},
//
screenCode: "",
//
widgets: [
{
// typevaluegaea_report_dashboard_widget
type: "widget-text",
value: {
setup: {},
data: {},
position: {
width: 100,
height: 100,
left: 0,
top: 0,
zIndex: 0
}
},
// optionstools
options: []
}
], //
//
widgetIndex: 0,
//
widgetOptions: {
setup: [], //
data: [], //
position: [] //
},
flagWidgetClickStopPropagation: false, // click
styleObj: {
left: 0,
top: 0
},
visibleContentMenu: false,
rightClickIndex: -1,
activeName: "first"
};
},
computed: {
step() {
return Number(100 / (this.bigscreenScaleInWorkbench * 100));
},
headers() {
return {
Authorization: getToken() // token
};
},
//
middleWidth() {
let widthLeftAndRight = 0;
if (this.toolIsShow) {
widthLeftAndRight += this.widthLeftForTools; //
}
widthLeftAndRight += this.widthLeftForToolsHideButton; //
widthLeftAndRight += this.widthLeftForOptions; //
let middleWidth = this.bodyWidth - widthLeftAndRight;
return middleWidth;
},
middleHeight() {
return this.bodyHeight;
},
//
bigscreenScaleInWorkbench() {
let widthScale =
(this.middleWidth - this.widthPaddingTools) / this.bigscreenWidth;
let heightScale =
(this.middleHeight - this.widthPaddingTools) / this.bigscreenHeight;
return Math.min(widthScale, heightScale);
},
workbenchTransform() {
return `scale(${this.bigscreenScaleInWorkbench}, ${
this.bigscreenScaleInWorkbench
})`;
},
//
bigscreenWidthInWorkbench() {
return (
getPXUnderScale(this.bigscreenScaleInWorkbench, this.bigscreenWidth) +
this.widthPaddingTools
);
},
bigscreenHeightInWorkbench() {
return (
getPXUnderScale(this.bigscreenScaleInWorkbench, this.bigscreenHeight) +
this.widthPaddingTools
);
}
},
watch: {
widgets: {
handler(val) {
this.layerWidget = handlerLayerWidget(val, getToolByCode);
//
this.$nextTick(() => {
this.revoke.push(this.widgets);
});
},
deep: true
}
},
created() {
/* 以下是记录历史的 */
this.revoke = new Revoke();
},
mounted() {
this.initScreen();
//
// this.initEchartData();
this.widgets = [];
window.addEventListener("mouseup", () => {
this.grade = false;
});
},
methods: {
//
initScreen() {
this.widgetOptions = screenConfig["options"];
},
// echrats
async initEchartData() {
const reportCode = this.$route.query.reportCode;
const { code, data } = await detailDashboard(reportCode);
if (code != 200) return;
const processData = handleInitEchartsData(data, getToolByCode);
const screenData = handleBigScreen(
data.dashboard,
getToolByCode,
this.setOptionsOnClickScreen
);
this.widgets = processData;
this.dashboard = screenData;
this.bigscreenWidth = this.dashboard.width;
this.bigscreenHeight = this.dashboard.height;
},
//
widgetOnDragged(evt, widgetCode) {
let widgetType = widgetCode;
//
let eventX = evt.originalEvent.clientX; // x
let eventY = evt.originalEvent.clientY; // y
let workbenchPosition = this.getDomTopLeftById("workbench");
let widgetTopInWorkbench = eventY - workbenchPosition.top;
let widgetLeftInWorkbench = eventX - workbenchPosition.left;
// x y
let x = widgetLeftInWorkbench / this.bigscreenScaleInWorkbench;
let y = widgetTopInWorkbench / this.bigscreenScaleInWorkbench;
//
let tool = getToolByCode(widgetType);
let widgetJson = {
type: widgetType,
value: {
setup: {},
data: {},
position: {
width: 0,
height: 0,
left: 0,
top: 0,
zIndex: 0
}
},
options: tool.options
};
//
const widgetJsonValue = handleDefaultValue(widgetJson);
//20220222
widgetJsonValue.value.position.left =
x - widgetJsonValue.value.position.width / 2;
widgetJsonValue.value.position.top =
y - widgetJsonValue.value.position.height / 2;
//
this.widgets.push(this.deepClone(widgetJsonValue));
//
this.setOptionsOnClickWidget(this.widgets.length - 1);
},
//
setOptionsOnClickScreen() {
this.screenCode = "screen";
//
this.activeName = "first";
this.widgetOptions = screenConfig["options"];
},
layerClick(index) {
this.widgetIndex = index;
this.widgetsClick(index);
},
//
setOptionsOnClickWidget(obj) {
console.log(obj);
this.screenCode = "";
if (typeof obj == "number") {
this.widgetOptions = this.deepClone(this.widgets[obj]["options"]);
return;
}
if (obj.index < 0 || obj.index >= this.widgets.length) {
return;
}
this.widgetIndex = obj.index;
this.widgets[obj.index].value.position = obj;
this.widgets[obj.index].options.position.forEach(el => {
for (const key in obj) {
if (el.name == key) {
el.value = obj[key];
}
}
});
this.widgetOptions = this.deepClone(this.widgets[obj.index]["options"]);
},
widgetsClick(index) {
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
if (i == index) {
this.$refs.widgets[i].$refs.draggable.setActive(true);
} else {
this.$refs.widgets[i].$refs.draggable.setActive(false);
}
}
this.setOptionsOnClickWidget(index);
this.grade = true;
},
widgetsMouseup(e) {
this.grade = false;
},
handleMouseDown() {
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
this.$refs.widgets[i].$refs.draggable.setActive(false);
}
},
//
widgetValueChanged(key, val) {
if (this.screenCode == "screen") {
let newSetup = new Array();
this.dashboard = this.deepClone(val);
if (this.bigscreenWidth != this.dashboard.width) {
this.bigscreenWidth = this.dashboard.width;
}
if (this.bigscreenHeight != this.dashboard.height) {
this.bigscreenHeight = this.dashboard.height;
}
this.widgetOptions.setup.forEach(el => {
if (el.name == "width") {
el.value = this.bigscreenWidth;
} else if (el.name == "height") {
el.value = this.bigscreenHeight;
}
newSetup.push(el);
});
this.widgetOptions.setup = newSetup;
} else {
for (let i = 0; i < this.widgets.length; i++) {
if (this.widgetIndex == i) {
this.widgets[i].value[key] = this.deepClone(val);
setDefaultValue(this.widgets[i].options[key], val);
}
}
}
},
rightClick(event, index) {
this.rightClickIndex = index;
const left = event.clientX;
const top = event.clientY;
if (left || top) {
this.styleObj = {
left: left + "px",
top: top + "px",
display: "block"
};
}
this.visibleContentMenu = true;
return false;
},
datadragEnd(evt) {
evt.preventDefault();
this.widgets = swapArr(this.widgets, evt.oldIndex, evt.newIndex);
},
//
async saveData() {
if (!this.widgets || this.widgets.length == 0) {
this.$message.error("请添加组件");
return;
}
const screenData = {
reportCode: this.$route.query.reportCode,
dashboard: {
title: this.dashboard.title,
width: this.dashboard.width,
height: this.dashboard.height,
backgroundColor: this.dashboard.backgroundColor,
backgroundImage: this.dashboard.backgroundImage
},
widgets: this.widgets
};
const { code, data } = await insertDashboard(screenData);
if (code == "200") {
this.$message.success("保存成功!");
}
},
//
viewScreen() {
let routeUrl = this.$router.resolve({
path: "/screen/preview",
query: { reportCode: this.$route.query.reportCode }
});
window.open(routeUrl.href, "_blank");
},
//
handleRedo() {
const record = this.revoke.redo();
if (!record) {
return false;
}
this.widgets = record;
},
//
handleUndo() {
const record = this.revoke.undo();
if (!record) {
return false;
}
this.widgets = record;
},
//
handleUpload(response, file, fileList) {
//el-upload
this.$refs.upload.clearFiles();
//
this.initEchartData();
if (response.code == "200") {
this.$message({
message: "导入成功!",
type: "success"
});
} else {
this.$message({
message: response.message,
type: "error"
});
}
},
//
handleError(err) {
this.$message({
message: "上传失败!",
type: "error"
});
},
//
async exportDashboard(val) {
console.log(val);
const fileName = this.$route.query.reportCode + ".zip";
const param = {
reportCode: this.$route.query.reportCode,
showDataSet: val
};
exportDashboard(param).then(res => {
const that = this;
const type = res.type;
if (type == "application/json") {
let reader = new FileReader();
reader.readAsText(res, "utf-8");
reader.onload = function() {
const data = JSON.parse(reader.result);
that.$message.error(data.message);
};
return;
}
const blob = new Blob([res], {
type: "application/octet-stream"
});
if (window.navigator.msSaveOrOpenBlob) {
//msSaveOrOpenBlobbool
navigator.msSaveBlob(blob, fileName); //
} else {
const link = document.createElement("a"); //a
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
window.URL.revokeObjectURL(link.href);
}
});
},
//
deletelayer() {
this.widgets.splice(this.rightClickIndex, 1);
},
//
copylayer() {
const obj = this.deepClone(this.widgets[this.rightClickIndex]);
this.widgets.splice(this.widgets.length, 0, obj);
},
//
istopLayer() {
if (this.rightClickIndex + 1 < this.widgets.length) {
const temp = this.widgets.splice(this.rightClickIndex, 1)[0];
this.widgets.push(temp);
}
},
//
setlowLayer() {
if (this.rightClickIndex != 0) {
this.widgets.unshift(this.widgets.splice(this.rightClickIndex, 1)[0]);
}
},
//
moveupLayer() {
if (this.rightClickIndex != 0) {
this.widgets[this.rightClickIndex] = this.widgets.splice(
this.rightClickIndex - 1,
1,
this.widgets[this.rightClickIndex]
)[0];
} else {
this.widgets.push(this.widgets.shift());
}
},
//
movedownLayer() {
if (this.rightClickIndex != this.widgets.length - 1) {
this.widgets[this.rightClickIndex] = this.widgets.splice(
this.rightClickIndex + 1,
1,
this.widgets[this.rightClickIndex]
)[0];
} else {
this.widgets.unshift(this.widgets.splice(this.rightClickIndex, 1)[0]);
}
}
}
};
</script>
<style scoped lang="scss">
@import "../../assets/styles/screenDesigner.scss";
</style>

@ -0,0 +1,144 @@
<!--
* @Descripttion: 大屏左侧工具栏
* @version:
* @Author: qianlishi
* @Date: 2022-05-12 11:05:21
* @LastEditors: qianlishi
* @LastEditTime: 2022-05-14 11:51:45
-->
<template>
<el-tabs class="layout-left" type="border-card">
<el-tab-pane>
<span slot="label"><i class="el-icon-date icon"></i>工具栏</span>
<div class="chart-type">
<el-tabs class="type-left" tab-position="left">
<el-tab-pane
v-for="(item, index) in widgetTools"
:key="index"
:label="item.name"
>
<draggable
v-for="(it, idx) in item.list"
:key="idx"
@end="evt => widgetOnDragged(evt, it.code)"
>
<div class="tools-item">
<span class="tools-item-icon">
<i class="iconfont" :class="it.icon"></i>
</span>
<span class="tools-item-text">{{ it.label }}</span>
</div>
</draggable>
</el-tab-pane>
</el-tabs>
</div>
</el-tab-pane>
<el-tab-pane>
<span slot="label" class="icon"
><i class="el-icon-date icon"></i>图层</span
>
<div
v-for="(item, index) in layerWidget"
:key="'item' + index"
class="tools-item"
:class="widgetIndex == index ? 'is-active' : ''"
@click="layerClick(index)"
>
<span class="tools-item-icon">
<i class="iconfont" :class="item.icon"></i>
</span>
<span class="tools-item-text">{{ item.label }}</span>
</div>
</el-tab-pane>
</el-tabs>
</template>
<script>
import draggable from "vuedraggable";
export default {
components: {
draggable
},
props: {
widgetIndex: Number,
widgetTools: Array,
layerWidget: Array
},
methods: {
widgetOnDragged(evt, code) {
this.$emit("widgetOnDragged", evt, code);
},
layerClick(index) {
this.$emit("layerClick", index);
}
}
};
</script>
<style lang="scss" scoped>
/deep/.el-dropdown-menu__item {
max-width: none;
}
.layout-left {
width: 200px;
background: #242a30;
overflow-x: hidden;
overflow-y: auto;
.chart-type {
display: flex;
flex-direction: row;
overflow: hidden;
.type-left {
width: 100%;
height: calc(100vh - 80px);
text-align: center;
/deep/.el-tabs__header {
width: 30%;
margin-right: 0;
.el-tabs__nav-wrap {
&::after {
background: transparent;
}
.el-tabs__item {
text-align: center;
width: 100% !important;
color: #fff;
padding: 0;
}
}
}
/deep/.el-tabs__content {
width: 70%;
}
}
}
//
.tools-item {
display: flex;
position: relative;
width: 100%;
height: 48px;
align-items: center;
-webkit-box-align: center;
padding: 0 6px;
cursor: pointer;
font-size: 12px;
margin-bottom: 1px;
.tools-item-icon {
color: #409eff;
margin-right: 10px;
width: 53px;
height: 30px;
line-height: 30px;
text-align: center;
display: block;
border: 1px solid #3a4659;
background: #282a30;
}
.tools-item-text {
}
}
/deep/.el-tabs__content {
padding: 0;
}
}
</style>

@ -0,0 +1,260 @@
<!--
* @Descripttion: 大屏设计器
* @version:
* @Author: qianlishi
* @Date: 2022-05-12 11:05:48
* @LastEditors: qianlishi
* @LastEditTime: 2022-05-21 14:47:00
-->
<template>
<div
class="workbench-container"
:style="{
width: bigscreenWidthInWorkbench + 'px',
height: bigscreenHeightInWorkbench + 'px'
}"
@mousedown="handleMouseDown"
>
<vue-ruler-tool
v-model="dashboard.presetLine"
class="vueRuler"
:step-length="50"
:parent="true"
:position="'relative'"
:is-scale-revise="true"
:visible.sync="dashboard.presetLineVisible"
>
<div
id="workbench"
class="workbench"
:style="{
transform: workbenchTransform,
width: bigscreenWidth + 'px',
height: bigscreenHeight + 'px',
'background-color': dashboard.backgroundColor,
'background-image': 'url(' + dashboard.backgroundImage + ')',
'background-position': '0% 0%',
'background-size': '100% 100%',
'background-repeat': 'initial',
'background-attachment': 'initial',
'background-origin': 'initial',
'background-clip': 'initial'
}"
@click.self="setOptionsOnClickScreen"
>
<div v-if="grade" class="bg-grid"></div>
<widget
ref="widgets"
v-for="(widget, index) in widgets"
:key="index"
v-model="widget.value"
:index="index"
:step="step"
:type="widget.type"
:bigscreen="{ bigscreenWidth, bigscreenHeight }"
@onActivated="setOptionsOnClickWidget"
@contextmenu.prevent.native="rightClick($event, index)"
@mousedown.prevent.native="widgetsClick(index)"
@mouseup.prevent.native="widgetsMouseup"
/>
</div>
</vue-ruler-tool>
<!-- 右键 -->
<content-menu
:visible.sync="visibleContentMenu"
:style-obj="styleObj"
:widgets="widgets"
:rightClickIndex="rightClickIndex"
/>
</div>
</template>
<script>
import {
swapArr,
setDefaultValue,
handleDefaultValue,
getPXUnderScale,
handleInitEchartsData,
handleBigScreen,
handlerLayerWidget
} from "../util/screen";
import { screenConfig } from "../config/texts/screenConfig.js";
import VueRulerTool from "vue-ruler-tool";
import widget from "../widget/index.vue";
import contentMenu from "../components/contentMenu";
export default {
components: {
VueRulerTool,
widget,
contentMenu
},
props: {
dashboard: Object,
middleWidth: Number,
middleHeight: Number,
widgets: Array
},
data() {
return {
grade: false,
bigscreenWidth: 1920, //
bigscreenHeight: 1080,
widgetOptions: {},
styleObj: {
left: 0,
top: 0
},
rightClickIndex: -1,
//
visibleContentMenu: false
};
},
computed: {
step() {
return Number(100 / (this.bigscreenScaleInWorkbench * 100));
},
//
bigscreenScaleInWorkbench() {
let widthScale = this.middleWidth / this.bigscreenWidth;
let heightScale = this.middleHeight / this.bigscreenHeight;
return Math.min(widthScale, heightScale);
},
workbenchTransform() {
return `scale(${this.bigscreenScaleInWorkbench}, ${
this.bigscreenScaleInWorkbench
})`;
},
//
bigscreenWidthInWorkbench() {
return getPXUnderScale(
this.bigscreenScaleInWorkbench,
this.bigscreenWidth
);
},
bigscreenHeightInWorkbench() {
return getPXUnderScale(
this.bigscreenScaleInWorkbench,
this.bigscreenHeight
);
}
},
mounted() {
this.loadOption();
},
methods: {
//
loadOption() {
this.widgetOptions = screenConfig["options"];
this.$emit("change", this.widgetOptions);
},
handleMouseDown() {
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
this.$refs.widgets[i].$refs.draggable.setActive(false);
}
},
rightClick(event, index) {
this.rightClickIndex = index;
const left = event.clientX;
const top = event.clientY;
if (left || top) {
this.styleObj = {
left: left + "px",
top: top + "px",
display: "block"
};
}
this.visibleContentMenu = true;
return false;
},
//
setOptionsOnClickScreen() {
this.screenCode = "screen";
//
this.activeName = "first";
this.widgetOptions = screenConfig["options"];
this.$emit("change", this.widgetOptions);
},
//
setOptionsOnClickWidget(obj) {
console.log(obj);
this.screenCode = "";
if (typeof obj == "number") {
this.widgetOptions = this.deepClone(this.widgets[obj]["options"]);
return;
}
if (obj.index < 0 || obj.index >= this.widgets.length) {
return;
}
this.widgetIndex = obj.index;
this.widgets[obj.index].value.position = obj;
this.widgets[obj.index].options.position.forEach(el => {
for (const key in obj) {
if (el.name == key) {
el.value = obj[key];
}
}
});
this.widgetOptions = this.deepClone(this.widgets[obj.index]["options"]);
this.$emit("change", this.widgetOptions);
},
widgetsClick(index) {
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
if (i == index) {
this.$refs.widgets[i].$refs.draggable.setActive(true);
} else {
this.$refs.widgets[i].$refs.draggable.setActive(false);
}
}
this.setOptionsOnClickWidget(index);
this.grade = true;
},
widgetsMouseup(e) {
this.grade = false;
}
}
};
</script>
<style lang="scss" scoped>
.workbench-container {
position: relative;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
.vueRuler {
width: 100%;
padding: 18px 0px 0px 18px;
}
.workbench {
background-color: #1e1e1e;
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
margin: 0;
padding: 0;
}
.bg-grid {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: 30px 30px, 30px 30px;
background-image: linear-gradient(hsla(0, 0%, 100%, 0.1) 1px, transparent 0),
linear-gradient(90deg, hsla(0, 0%, 100%, 0.1) 1px, transparent 0);
// z-index: 2;
}
}
</style>

@ -0,0 +1,70 @@
<!--
* @Descripttion: 大屏右侧配置
* @version:
* @Author: qianlishi
* @Date: 2022-05-12 11:05:54
* @LastEditors: qianlishi
* @LastEditTime: 2022-05-14 11:52:30
-->
<template>
<div class="layout-right">
<el-tabs v-model="activeName" type="border-card" :stretch="true">
<el-tab-pane
v-if="
isNotNull(widgetOptions.setup) || isNotNull(widgetOptions.collapse)
"
name="first"
label="配置"
>
<dynamicForm
ref="formData"
:options="widgetOptions.setup"
@onChanged="val => widgetValueChanged('setup', val)"
/>
</el-tab-pane>
<el-tab-pane
v-if="isNotNull(widgetOptions.data)"
name="second"
label="数据"
>
<dynamicForm
ref="formData"
:options="widgetOptions.data"
@onChanged="val => widgetValueChanged('data', val)"
/>
</el-tab-pane>
<el-tab-pane
v-if="isNotNull(widgetOptions.position)"
name="third"
label="坐标"
>
<dynamicForm
ref="formData"
:options="widgetOptions.position"
@onChanged="val => widgetValueChanged('position', val)"
/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import dynamicForm from "../components/dynamicForm.vue";
export default {
components: {
dynamicForm
},
data() {
return {
activeName: "first"
};
},
props: {
widgetOptions: Object
},
methods: {
widgetValueChanged(type, val) {
this.$emit("widgetValueChanged", type, val);
}
}
};
</script>

@ -0,0 +1,229 @@
<!--
* @Descripttion: 大屏顶部操操按钮
* @version:
* @Author: qianlishi
* @Date: 2022-05-14 11:54:07
* @LastEditors: qianlishi
* @LastEditTime: 2022-05-14 12:52:58
-->
<template>
<!-- 操作栏 -->
<div class="layout-bar">
<div class="bar-item" @click="saveData">
<i class="iconfont iconsave"></i>保存
</div>
<div class="bar-item" @click="viewScreen">
<i class="iconfont iconyulan"></i>预览
</div>
<div class="bar-item" @click="handleUndo">
<i class="iconfont iconundo"></i>撤销
</div>
<div class="bar-item" @click="handleRedo">
<i class="iconfont iconhuifubeifen"></i>恢复
</div>
<div class="bar-item">
<el-upload
class="el-upload"
ref="upload"
:action="uploadUrl"
:headers="headers"
accept=".zip"
:on-success="handleUpload"
:on-error="handleError"
:show-file-list="false"
:limit="1"
>
<i class="iconfont icondaoru"></i>
</el-upload>
导入
</div>
<div class="bar-item">
<i class="iconfont icondaochu"></i>
<el-dropdown @command="exportDashboard">
<span class="el-dropdown-link">
导出<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="1">导出(包含数据集)</el-dropdown-item>
<el-dropdown-item command="0">导出(不包含数据集)</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import { insertDashboard, exportDashboard } from "@/api/bigscreen";
import { Revoke } from "../util/revoke";
import { getToken } from "@/utils/auth";
export default {
props: {
dashboard: Object,
widgets: Array
},
data() {
return {
uploadUrl:
process.env.BASE_API +
"/reportDashboard/import/" +
this.$route.query.reportCode,
revoke: null
};
},
computed: {
headers() {
return {
Authorization: getToken()
};
}
},
watch: {
widgets: {
handler(val) {
//
this.$nextTick(() => {
this.revoke.push(this.widgets);
});
},
deep: true
}
},
created() {
/* 以下是记录历史的 */
this.revoke = new Revoke();
},
methods: {
//
async saveData() {
if (!this.widgets || this.widgets.length == 0) {
return this.$message.error("请添加组件");
}
const screenData = {
reportCode: this.$route.query.reportCode,
dashboard: {
title: this.dashboard.title,
width: this.dashboard.width,
height: this.dashboard.height,
backgroundColor: this.dashboard.backgroundColor,
backgroundImage: this.dashboard.backgroundImage
},
widgets: this.widgets
};
const { code, data } = await insertDashboard(screenData);
if (code == "200") {
this.$message.success("保存成功!");
}
},
//
viewScreen() {
let routeUrl = this.$router.resolve({
path: "/screen/preview",
query: { reportCode: this.$route.query.reportCode }
});
window.open(routeUrl.href, "_blank");
},
//
handleUndo() {
const record = this.revoke.undo();
if (!record) {
return false;
}
this.widgets = record;
},
//
handleRedo() {
const record = this.revoke.redo();
if (!record) {
return false;
}
this.widgets = record;
},
//
handleError(err) {
this.$message({
message: "上传失败!",
type: "error"
});
},
//
handleUpload(response, file, fileList) {
//el-upload
this.$refs.upload.clearFiles();
//
this.irefresh();
if (response.code == "200") {
this.$message({
message: "导入成功!",
type: "success"
});
} else {
this.$message({
message: response.message,
type: "error"
});
}
},
//
async exportDashboard(val) {
const fileName = this.$route.query.reportCode + ".zip";
const param = {
reportCode: this.$route.query.reportCode,
showDataSet: val
};
exportDashboard(param).then(res => {
const that = this;
const type = res.type;
if (type == "application/json") {
let reader = new FileReader();
reader.readAsText(res, "utf-8");
reader.onload = function() {
const data = JSON.parse(reader.result);
that.$message.error(data.message);
};
return;
}
const blob = new Blob([res], {
type: "application/octet-stream"
});
if (window.navigator.msSaveOrOpenBlob) {
//msSaveOrOpenBlobbool
navigator.msSaveBlob(blob, fileName); //
} else {
const link = document.createElement("a"); //a
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
window.URL.revokeObjectURL(link.href);
}
});
},
refresh() {
this.$emit("refresh");
}
}
};
</script>
<style scoped lang="scss">
.layout-bar {
height: 40px;
line-height: 40px;
font-size: 12px;
padding: 0 10px;
display: flex;
flex-direction: row;
overflow: hidden;
.bar-item {
margin-right: 20px;
cursor: pointer;
.iconfont {
font-size: 12px;
margin-right: 4px;
}
.el-dropdown-link {
color: #fff;
cursor: pointer;
}
}
}
</style>

@ -13,6 +13,10 @@
│ │ ├── pieCharts(文件夹) (饼图)
│ │ ├── texts(文件夹) (文本、滚动文本、超链接、当前时间、图片、视频、表格、内联框架)
│ │ ├── wordcloudCharts(文件夹) (词云图)
│ ├── layout(文件夹)
│ │ ├── left.vue 大屏左侧工具栏
│ │ ├── middle.vue 大屏中间配置
│ │ ├── right.vue 大屏右侧配置
│ ├── util(文件夹) 公共 js
│ ├── widget(文件夹) 图表组件
│ │ ├── barCharts(文件夹) (柱状图)

@ -0,0 +1,92 @@
export class Revoke {
// 历史记录
recordList = [];
// 撤销记录,用于重做
redoList = [];
// 当前记录用currentRecord变量暂时存储当用户修改时再存放到recordList
currentRecord = null;
// 上次插入数据时间
time = 0;
/**
* @description: 插入历史记录
* @param {object}record
* @return {boolean}
*/
push(record) {
const nowTime = Date.now();
// 防止添加重复的时间当添加间隔小于100ms时则替换当前记录并取消执行添加
if (this.time + 100 > nowTime) {
this.currentRecord = JSON.stringify(record);
return false;
}
this.time = nowTime;
// 判断之前是否已经存在currentRecord记录有则存储到recordList
if (this.currentRecord) {
this.recordList.push(this.currentRecord);
//(清空记录)增加记录后则应该清空重做记录
//splice() 方法向/从数组添加/删除项目,并返回删除的项目。
this.redoList.splice(0, this.redoList.length);
}
// 将json转成字符串存储
this.currentRecord = JSON.stringify(record);
// 最多存储2000条记录超过2000条记录则删除之前的记录
if (this.length > 2000) {
//unshift() 方法将新项添加到数组的开头,并返回新的长度。
this.recordList.unshift();
}
return true;
}
/**
* @description: 撤销操作
* @param {*}
* @return {object}
*/
undo() {
// 没有记录时,返回false
// 新建的recordList里面不知为什么会存在一条记录未找到原因所以就判断长度为1时就不能撤销了。
if (this.recordList.length === 1 ) {
return false;
}
//pop() 方法用于删除并返回数组的最后一个元素。
const record = this.recordList.pop();
// 将当前记录添加到重做记录里面
if (this.currentRecord) {
this.redoList.push(this.currentRecord);
}
// 丢弃当前记录,防止重复添加
this.currentRecord = null;
//返回撤销的记录
return JSON.parse(record);
}
/**
* @description: 重做操作
* @param {*}
* @return {*}
*/
redo() {
// 没有重做记录时,返回false
if (this.redoList.length === 0) {
return false;
}
//pop() 方法用于删除并返回数组的最后一个元素。
const record = this.redoList.pop();
// 添加到重做记录里面
if (this.currentRecord) {
this.recordList.push(this.currentRecord);
}
// 丢弃当前记录,防止重复添加
this.currentRecord = null;
return JSON.parse(record);
}
}

@ -15,6 +15,7 @@ export const handlerLayerWidget = (val, getToolByCode) => {
}
export const handleBigScreen = (data, getToolByCode, callBcak) => {
console.log('aa', getToolByCode("screen"))
const optionScreen = getToolByCode("screen").options;
const setup = optionScreen.setup;
for (const key in data) {

Loading…
Cancel
Save