screenMixins.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. import { Revoke } from "@/utils/revoke";
  2. import { getToken } from "@/utils/auth";
  3. import { getToolByCode } from "@/views/bigscreenDesigner/designer/tools/index";
  4. import { insertDashboard, detailDashboard, exportDashboard, } from "@/api/bigscreen";
  5. const mixin = {
  6. data() {
  7. return {
  8. reportCode: this.$route.query.reportCode,
  9. uploadUrl: process.env.BASE_API + "/reportDashboard/import/" + this.$route.query.reportCode,
  10. revoke: null, //处理历史记录
  11. rightClickIndex: -1,
  12. rightClickWidget: null,
  13. }
  14. },
  15. computed: {
  16. step() {
  17. return Number(100 / (this.bigscreenScaleInWorkbench * 100));
  18. },
  19. headers() {
  20. return {
  21. Authorization: getToken(),
  22. };
  23. },
  24. // 初始的缩放百分比 和 下标
  25. defaultSize() {
  26. const obj = {
  27. index: -1,
  28. size: "50",
  29. };
  30. this.sizeRange.some((item, index) => {
  31. if (item <= 100 * this.bigscreenScaleInWorkbench) {
  32. obj.index = index;
  33. obj.size = 100 * this.bigscreenScaleInWorkbench;
  34. }
  35. });
  36. if (obj.index === -1) {
  37. obj.index = 0;
  38. obj.size = this.sizeRange[0];
  39. }
  40. return obj;
  41. },
  42. },
  43. watch: {
  44. defaultSize: {
  45. handler(val) {
  46. if (val !== -1) {
  47. this.currentSizeRangeIndex = val.index;
  48. this.scaleNum = val.size;
  49. }
  50. },
  51. immediate: true,
  52. },
  53. bigscreenWidth() {
  54. this.initVueRulerTool();
  55. },
  56. bigscreenHeight() {
  57. this.initVueRulerTool();
  58. },
  59. },
  60. created() {
  61. this.revoke = new Revoke();
  62. this.getData();
  63. },
  64. methods: {
  65. /**
  66. * @param num: 0缩小 1放大 2默认比例
  67. * sizeRange: [20, 40, 60, 72, 100, 150, 200, 300, 400]
  68. */
  69. setSize(num) {
  70. switch (num) {
  71. case 0: this.currentSizeRangeIndex === 0 ? '' : this.currentSizeRangeIndex -= 1;
  72. break;
  73. case 1: this.currentSizeRangeIndex === 8 ? '' : this.currentSizeRangeIndex += 1;
  74. break;
  75. case 2: this.currentSizeRangeIndex = this.defaultSize.index;
  76. }
  77. this.scaleNum = this.currentSizeRangeIndex === this.defaultSize.index ? this.defaultSize.size : this.sizeRange[this.currentSizeRangeIndex];
  78. },
  79. // 初始化 修正插件样式
  80. initVueRulerTool() {
  81. const vueRulerToolDom = this.$refs["vue-ruler-tool"].$el; // 操作面板 第三方插件工具
  82. const contentDom = vueRulerToolDom.querySelector(".vue-ruler-content");
  83. const vueRulerX = vueRulerToolDom.querySelector(".vue-ruler-h"); // 横向标尺
  84. const vueRulerY = vueRulerToolDom.querySelector(".vue-ruler-v"); // 纵向标尺
  85. contentDom.style.width = "100%";
  86. contentDom.style.height = "100%";
  87. let xHtmlContent = "";
  88. let yHtmlContent = "";
  89. let currentNum = 0;
  90. while (currentNum < +this.bigscreenWidth) {
  91. xHtmlContent += `<span class="n" style="left: ${currentNum + 2}px;">${currentNum}</span>`;
  92. currentNum += 50;
  93. }
  94. currentNum = 0;
  95. while (currentNum < +this.bigscreenHeight) {
  96. yHtmlContent += `<span class="n" style="top: ${currentNum + 2}px;">${currentNum}</span>`;
  97. currentNum += 50;
  98. }
  99. vueRulerX.innerHTML = xHtmlContent;
  100. vueRulerY.innerHTML = yHtmlContent;
  101. },
  102. // 初始化接口数据
  103. async getData() {
  104. const { code, data } = await detailDashboard(this.reportCode);
  105. if (code != 200) return;
  106. this.widgets = this.initWidgetsData(data);
  107. this.dashboard = this.initScreenData(data.dashboard);
  108. this.bigscreenWidth = this.dashboard.width;
  109. this.bigscreenHeight = this.dashboard.height;
  110. },
  111. // 组件数据
  112. initWidgetsData(data) {
  113. const widgets = data.dashboard ? data.dashboard.widgets : [];
  114. const widgetsData = [];
  115. for (let i = 0; i < widgets.length; i++) {
  116. const widget = widgets[i]
  117. const { setup, data, position } = { ...widget.value }
  118. const obj = {
  119. type: widget.type,
  120. value: { setup, data, position }
  121. };
  122. const tool = this.deepClone(getToolByCode(widget.type));
  123. if (!tool) {
  124. const message = "暂未提供该组件或该组件下线了,组件code: " + widget.type;
  125. if (process.env.NODE_ENV === "development") {
  126. this.$message.error(message);
  127. }
  128. continue; // 找不到就跳过,避免整个报表都加载不出来
  129. }
  130. obj.options = this.setDefaultWidgetConfigValue(widget.value, tool.options);
  131. obj.value.widgetId = obj.value.setup.widgetId;
  132. widgetsData.push(obj);
  133. }
  134. return widgetsData;
  135. },
  136. // 重写默认数据
  137. setDefaultWidgetConfigValue(data, option) {
  138. this.setConfigValue(data.setup, option.setup)
  139. this.setConfigValue(data.position, option.position)
  140. this.setConfigValue(data.data, option.data)
  141. return option;
  142. },
  143. setConfigValue(objValue, setup) {
  144. Object.keys(objValue).forEach(key => {
  145. setup.forEach(item => {
  146. if (this.isObjectFn(item) && key == item.name) {
  147. item.value = objValue[key]
  148. }
  149. if (this.isArrayFn(item)) {
  150. item.forEach(itemChild => {
  151. itemChild.list.forEach(el => {
  152. if (key == el.name) {
  153. el.value = objValue[key]
  154. }
  155. })
  156. })
  157. }
  158. })
  159. })
  160. },
  161. // 大屏数据
  162. initScreenData(data) {
  163. const optionScreen = getToolByCode("screen").options;
  164. this.setConfigValue(data, optionScreen.setup)
  165. this.setOptionsOnClickScreen();
  166. return {
  167. backgroundColor:
  168. (data && data.backgroundColor) || (!data ? "#1e1e1e" : ""),
  169. backgroundImage: (data && data.backgroundImage) || "",
  170. height: (data && data.height) || "1080",
  171. title: (data && data.title) || "",
  172. width: (data && data.width) || "1920",
  173. };
  174. },
  175. // 保存数据
  176. async saveData() {
  177. if (!this.widgets || this.widgets.length == 0) {
  178. return this.$message.error("请添加组件");
  179. }
  180. const { title, width, height, backgroundColor, backgroundImage, refreshSeconds } = { ...this.dashboard }
  181. const screenData = {
  182. reportCode: this.reportCode,
  183. dashboard: { title, width, height, backgroundColor, backgroundImage, refreshSeconds },
  184. widgets: this.widgets,
  185. };
  186. screenData.widgets.forEach((widget) => {
  187. widget.value.setup.widgetId = widget.value.widgetId;
  188. widget.value.setup.widgetCode = widget.type
  189. });
  190. const { code, data } = await insertDashboard(screenData);
  191. if (code == "200") return this.$message.success("保存成功!");
  192. },
  193. // 预览
  194. viewScreen() {
  195. let routeUrl = this.$router.resolve({
  196. path: "/bigscreen/viewer",
  197. query: { reportCode: this.reportCode },
  198. });
  199. window.open(routeUrl.href, "_blank");
  200. },
  201. async exportDashboard(val) {
  202. const fileName = this.reportCode + ".zip";
  203. const param = {
  204. reportCode: this.reportCode,
  205. showDataSet: val,
  206. };
  207. exportDashboard(param).then((res) => {
  208. const that = this;
  209. const type = res.type;
  210. if (type == "application/json") {
  211. let reader = new FileReader();
  212. reader.readAsText(res, "utf-8");
  213. reader.onload = function () {
  214. const data = JSON.parse(reader.result);
  215. that.$message.error(data.message);
  216. };
  217. return;
  218. }
  219. const blob = new Blob([res], { type: "application/octet-stream" });
  220. if (window.navigator.msSaveOrOpenBlob) {
  221. //msSaveOrOpenBlob方法返回bool值
  222. navigator.msSaveBlob(blob, fileName); //本地保存
  223. } else {
  224. const link = document.createElement("a"); //a标签下载
  225. link.href = window.URL.createObjectURL(blob);
  226. link.download = fileName;
  227. link.click();
  228. window.URL.revokeObjectURL(link.href);
  229. }
  230. });
  231. },
  232. handleUndo() {
  233. const record = this.revoke.undo();
  234. if (!record) return false;
  235. this.widgets = record;
  236. },
  237. handleRedo() {
  238. const record = this.revoke.redo();
  239. if (!record) return false;
  240. this.widgets = record;
  241. },
  242. handleUpload(response, file, fileList) {
  243. this.$refs.upload.clearFiles();
  244. this.getData();
  245. if (response.code == "200") return this.$message.success('导入成功!')
  246. this.$message.error(response.message)
  247. },
  248. // 右键
  249. rightClick(event, index) {
  250. this.rightClickIndex = index;
  251. this.rightClickWidget = this.widgets[index];
  252. const left = event.clientX;
  253. const top = event.clientY;
  254. if (left || top) {
  255. this.styleObj = {
  256. left: left + "px",
  257. top: top + "px",
  258. display: "block",
  259. };
  260. }
  261. //设置多选和单选的菜单项展示
  262. document.getElementsByName("singleSelect").forEach(e=>{
  263. e.style.display= this.selectedWidgets.length >= 2 ?"none":"block";
  264. });
  265. document.getElementsByName("mulSelect").forEach(e=>{
  266. e.style.display= this.selectedWidgets.length >= 2 ?"block":"none";
  267. })
  268. this.visibleContentMenu = true;
  269. return false;
  270. },
  271. // 数组 元素互换位置
  272. swapArr(arr, oldIndex, newIndex) {
  273. arr[oldIndex] = arr.splice(newIndex, 1, arr[oldIndex])[0];
  274. return arr;
  275. },
  276. // 删除
  277. deletelayer() {
  278. let _this = this;
  279. this.widgets.splice(this.rightClickIndex, 1);
  280. this.selectedWidgets.forEach(sw=>{
  281. _this.widgets = _this.widgets.filter(w=>w.value.widgetId !== sw.value.widgetId);
  282. })
  283. },
  284. // 锁定
  285. lockLayer() {
  286. const obj = this.widgets[this.rightClickIndex];
  287. this.$set(obj.value.position, "disabled", true);
  288. },
  289. // 解除锁定
  290. noLockLayer() {
  291. const obj = this.widgets[this.rightClickIndex];
  292. this.$set(obj.value.position, "disabled", false);
  293. },
  294. // 复制
  295. copylayer() {
  296. const obj = this.deepClone(this.widgets[this.rightClickIndex]);
  297. obj.value.position.top += 40; // 复制的元素向右下角偏移一点
  298. obj.value.position.left += 40;
  299. obj.value.widgetId = Number(Math.random().toString().substr(2)).toString(
  300. 36
  301. );
  302. this.widgets.splice(this.widgets.length, 0, obj);
  303. this.$nextTick(() => {
  304. this.layerClick(this.widgets.length - 1); // 复制后定位到最新的组件
  305. });
  306. },
  307. // 置顶
  308. istopLayer() {
  309. if (this.rightClickIndex + 1 < this.widgets.length) {
  310. const temp = this.widgets.splice(this.rightClickIndex, 1)[0];
  311. this.widgets.push(temp);
  312. }
  313. this.widgetIndex = this.widgets.indexOf(this.rightClickWidget);
  314. this.widgetsClickFocus(this.widgetIndex);
  315. },
  316. // 置底
  317. setlowLayer() {
  318. if (this.rightClickIndex != 0) {
  319. this.widgets.unshift(this.widgets.splice(this.rightClickIndex, 1)[0]);
  320. }
  321. this.widgetIndex = this.widgets.indexOf(this.rightClickWidget);
  322. this.widgetsClickFocus(this.widgetIndex);
  323. },
  324. // 上移一层
  325. moveupLayer() {
  326. if (this.rightClickIndex != 0) {
  327. this.widgets[this.rightClickIndex] = this.widgets.splice(
  328. this.rightClickIndex - 1,
  329. 1,
  330. this.widgets[this.rightClickIndex]
  331. )[0];
  332. } else {
  333. this.widgets.push(this.widgets.shift());
  334. }
  335. this.widgetIndex = this.widgets.indexOf(this.rightClickWidget);
  336. this.widgetsClickFocus(this.widgetIndex);
  337. },
  338. // 下移一层
  339. movedownLayer() {
  340. if (this.rightClickIndex != this.widgets.length - 1) {
  341. this.widgets[this.rightClickIndex] = this.widgets.splice(
  342. this.rightClickIndex + 1,
  343. 1,
  344. this.widgets[this.rightClickIndex]
  345. )[0];
  346. } else {
  347. this.widgets.unshift(this.widgets.splice(this.rightClickIndex, 1)[0]);
  348. }
  349. this.widgetIndex = this.widgets.indexOf(this.rightClickWidget);
  350. this.widgetsClickFocus(this.widgetIndex);
  351. },
  352. //对齐
  353. alignment(align) {
  354. if(this.selectedWidgets.length <= 1) {
  355. this.$message.error("请至少选择两个组件对齐");
  356. return;
  357. }
  358. console.log("对齐方式:" + align);
  359. let topWidget = this.selectedWidgets[0]; //最上组件(左右对齐使用)
  360. let leftWidget = this.selectedWidgets[0]; //最左组件(上下对齐使用)
  361. let minTop = this.selectedWidgets[0].value.position.top;
  362. let minLeft = this.selectedWidgets[0].value.position.left;
  363. //如果是框选的话,以【最上组件】【最左组件】为标准对齐;如果是Ctrl多选方式,则以第一个选中的组件为标准对齐,组合多选以框选逻辑为准
  364. if(this.kuangSelectFlag){
  365. for(let i = 0; i< this.selectedWidgets.length; i++){
  366. let widget = this.selectedWidgets[i];
  367. if( minTop > widget.value.position.top){
  368. minTop = widget.value.position.top;
  369. topWidget = widget;
  370. }
  371. if( minLeft > widget.value.position.left){
  372. minLeft = widget.value.position.left;
  373. leftWidget = widget;
  374. }
  375. }
  376. }
  377. for(let i = 0; i< this.selectedWidgets.length; i++){
  378. let widget = this.selectedWidgets[i];
  379. this.$refs.widgets.forEach(w=>{
  380. if(w.value.widgetId === widget.value.widgetId){
  381. w.$refs.draggable.setActive(false);
  382. }
  383. });
  384. this.widgets.forEach(w=>{
  385. if(w.value.widgetId === widget.value.widgetId){
  386. switch (align){
  387. case "left": //左对齐
  388. w.value.position.left = topWidget.value.position.left;
  389. break;
  390. case "right": //右对齐
  391. w.value.position.left = topWidget.value.position.left + topWidget.value.position.width - w.value.position.width;
  392. break;
  393. case "horizontal_center": //左右居中对齐
  394. w.value.position.left = topWidget.value.position.left + topWidget.value.position.width/2 - w.value.position.width /2;
  395. break;
  396. case "top": //上对齐
  397. w.value.position.top = leftWidget.value.position.top;
  398. break;
  399. case "bottom": //下对齐
  400. w.value.position.top = leftWidget.value.position.top + leftWidget.value.position.height - w.value.position.height;
  401. break;
  402. case "vertical_center": //上下居中对齐
  403. w.value.position.top = leftWidget.value.position.top + leftWidget.value.position.height/2 - w.value.position.height /2;
  404. break;
  405. }
  406. }
  407. });
  408. }
  409. this.selectedWidgets = [];
  410. if(this.rect){
  411. document.getElementById("workbench").removeChild(this.rect);
  412. }
  413. this.kuangSelectFlag = false;
  414. }
  415. }
  416. }
  417. export default mixin