<template> <div ref="designContainer" class="app-dashboard-design"> <div class="design-tree"> <el-select v-model="filterVal" clearable class="design-filter"> <template v-for="item of groups"> <el-option :key="item.value" :value="item.value" :label="item.name"></el-option> </template> </el-select> <div class="design-tree-content"> <el-menu v-show="!filterVal" :unique-opened="true"> <template v-for="(item, index) of list"> <el-submenu :key="item.type + index" :index="item.type + index"> <div slot="title">{{Object.is(item.type, 'app') ? $t('components.appDashboardDesign.global') : item.name}}</div> <template v-for="(item2, index2) of item.children"> <el-submenu :key="item2.type + index2" :index="item2.type + index2"> <div slot="title">{{item2.name}}</div> <el-menu-item ref="dragDivItem" :class="{'drag-div-item': true, 'is-disable': isDisabled(item3)}" v-for="(item3, index3) of item2.children" :key="item3.type + index3" :index="item3.type + index3" :tag="item3.portletCodeName"> {{item3.portletName}} </el-menu-item> </el-submenu> </template> </el-submenu> </template> </el-menu> <el-menu v-show="filterVal" :unique-opened="true" :default-openeds="[filterVal]"> <template v-for="item of groups"> <el-submenu v-show="filterVal == item.value" :key="item.value" :index="item.value"> <div slot="title">{{item.name}}</div> <template v-for="item2 of item.children"> <el-menu-item ref="dragDivItem" :class="{'drag-div-item': true, 'is-disable': isDisabled(item2)}" :key="item2.portletCodeName" :index="item.portletCodeName" :tag="item2.portletCodeName"> {{item2.portletName}} </el-menu-item> </template> </el-submenu> </template> </el-menu> </div> <div ref="dragDiv" v-if="dragItem" class="drag-tree-item">{{dragItem.caption}}</div> </div> <div class="design-panel" ref="gridLayoutPanel"> <grid-layout ref="gridLayout" :class="['app-grid-layout', isDragEnter ? 'layout-draging': '']" :layout.sync="layoutModel" :col-num="layoutColNum" :row-height="layoutRowH" :is-draggable="true" :is-resizable="true" :is-mirrored="false" :vertical-compact="true" :margin="[10, 10]" :use-css-transforms="true" :style="{minHeight: `${10 * (layoutRowH + 10) + 10}px`}" > <div class="app-grid-layout-mask" :style="{backgroundSize: `calc((100% - 10px) / ${layoutColNum}) ${layoutRowH + 10}px`}" ></div> <grid-item v-for="item in layoutModel" class="item" :x="item.x" :y="item.y" :w="item.w" :h="item.h" :i="item.i" :key="item.i" v-show="!dragItem || dragItem.portletCodeName != item.portletCodeName" > <el-card class="app-grid-layout-item"> <div slot="header"> <span>{{item.portletName}}</span> <i class="el-icon-close" @click="removeItem(item.i)"></i> </div> <component :is="item.componentName" :viewDefaultUsage="false" :context="JSON.parse(JSON.stringify(context))"></component> </el-card> </grid-item> </grid-layout> </div> </div> </template> <script lang="ts"> import { Vue, Component, Prop } from "vue-property-decorator"; import VueGridLayout from "vue-grid-layout"; import interact from "interactjs"; import AppDashboardDesignService from './app-dashboard-design-service'; import { Http } from '../../utils/http/http'; import { Subject, Subscription } from 'rxjs'; @Component({ components: { GridLayout: VueGridLayout.GridLayout, GridItem: VueGridLayout.GridItem } }) export default class AppDashboardDesign extends Vue { /** * 设计服务对象 * * @protected * @type {AppDashboardDesignService} * @memberof AppDashboardDesign */ protected designService: AppDashboardDesignService = new AppDashboardDesignService(); /** * 应用上下文 * * @type {*} * @memberof AppDashboardDesign */ @Prop({default:{}}) context?: any; /** * 视图参数 * * @type {*} * @memberof AppDashboardDesign */ @Prop({default:{}}) viewparams?: any; /** * 视图参数 * * @type {*} * @memberof AppDashboardDesign */ @Prop() utilServiceName?: any; /** * 视图通讯对象 * * @type {Subject<ViewState>} * @memberof AppDashboardDesign */ @Prop() protected viewState!: Subject<ViewState>; /** * 模型对象 * * @protected * @type {*} * @memberof AppDashboardDesign */ protected layoutModel: any[] = []; /** * 布局列数 * * @type {number} * @memberof AppDashboardDesign */ public layoutColNum: number = 12; /** * 布局行高 * * @type {number} * @memberof AppDashboardDesign */ public layoutRowH: number = 80; /** * 拖拽对象 * * @type {(any | null)} * @memberof AppDashboardDesign */ public dragItem: any | null = null; /** * 是否拖拽中 * * @type {boolean} * @memberof AppDashboardDesign */ public isDragEnter: boolean = false; /** * 门户项列表 * * @type {any[]} * @memberof AppDashboardDesign */ public list: any[] = []; /** * 门户项列表 * * @type {any[]} * @memberof AppDashboardDesign */ public portlets: any[] = []; /** * 视图状态事件 * * @protected * @type {(Subscription | undefined)} * @memberof AppDashboardDesign */ protected viewStateEvent: Subscription | undefined; /** * 分组集合 * * @type {any[]} * @memberof AppDashboardDesign */ public groups: any[] = []; /** * 分组过滤值 * * @type {string} * @memberof AppDashboardDesign */ public filterVal: string = ''; /** * 生命周期 * * @memberof AppDashboardDesign */ public created() { this.loadList(); this.load(); if (this.viewState) { this.viewStateEvent = this.viewState.subscribe(({ tag, action, data }) => { if (Object.is('save', action)) { this.save(); } }); } } /** * 执行destroyed后的逻辑 * * @memberof AppDashboardDesign */ protected afterDestroy() { if (this.viewStateEvent) { this.viewStateEvent.unsubscribe(); } } /** * 加载门户列表 * * @memberof AppDashboardDesign */ public loadList() { let post = this.designService.loadPortletList(this.context, this.viewparams); post.then((result: any) => { this.portlets = result.data; this.list = result.result; this.groups = result.groups; this.$nextTick(() => { this.addEventListener(); }) }).catch((response: any) => { console.log(response); }); } /** * 加载 * * @memberof AppDashboardDesign */ public load() { let post = this.designService.loadModel(this.utilServiceName, this.context, this.viewparams); post.then((response: any) => { if(response.status == 200) { this.layoutModel = response.data; } }).catch((response: any) => { console.log(response); }); } /** * 保存 * * @memberof AppDashboardDesign */ public save() { let param: any = {}; Object.assign(param, { model: this.layoutModel, ...this.viewparams }); let post = this.designService.saveModel(this.utilServiceName, this.context, param); post.then((response: any) => { this.$emit("save", response.data); }).catch((response: any) => { console.log(response); }); } /** * 是否禁止拖动 * * @param {*} item * @returns * @memberof AppDashboardDesign */ public isDisabled(item: any) { const index: any = this.layoutModel.findIndex((a: any) => Object.is(a.i, item.portletCodeName) ); if (index >= 0) { return true; } return false; } /** * 删除项 * * @param {string} id * @memberof AppDashboardDesign */ public removeItem(id: string) { const index: any = this.layoutModel.findIndex((item: any) => Object.is(item.i, id) ); if (index !== -1) { this.layoutModel.splice(index, 1); } let item = this.portlets.find((item: any) => Object.is(item.portletCodeName, id)); if (item) { item.moved = false; } } /** * 添加事件 * * @memberof AppDashboardDesign */ public addEventListener() { if (this.$refs.dragDivItem) { let dragDivItems: any = this.$refs.dragDivItem; dragDivItems.forEach((dragDivItem: any) => { let interactObj = interact(dragDivItem.$el); interactObj.draggable({}); interactObj.on("dragstart dragmove dragend", event => { this.handleDrag(event); }); }); } } /** * 拖动 * * @param {*} event * @returns * @memberof AppDashboardDesign */ public handleDrag(event: any) { let tag = event.currentTarget.getAttribute("tag"); let item = this.portlets.find((item: any) => Object.is(item.portletCodeName, tag)); if (item.moved) { return; } switch (event.type) { case "dragstart": { if (item) { this.dragItem = item; let dragDiv: any = this.$refs.dragDiv; } break; } case "dragmove": { if (!this.dragItem) { return; } let dragDiv: any = this.$refs.dragDiv; let mouseX = Math.round(event.client.x); let mouseY = Math.round(event.client.y); if (dragDiv) { dragDiv.style.left = mouseX + "px"; dragDiv.style.top = mouseY + "px"; } let index = this.layoutModel.findIndex(item => Object.is(item.portletCodeName, this.dragItem.portletCodeName) ); let gridLayoutPanel: any = this.$refs.gridLayoutPanel; let gridLayout: any = this.$refs.gridLayout; let ctainRect = gridLayoutPanel.getBoundingClientRect(); if ( mouseX > ctainRect.x && mouseX < ctainRect.x + ctainRect.width && mouseY > ctainRect.y && mouseY < ctainRect.y + ctainRect.height ) { if (index === -1) { let addItem: any = { x: 0, y: 0, w: 4, h: 3, i: this.dragItem.portletCodeName }; Object.assign(addItem, this.dragItem); delete addItem.moved; this.layoutModel.push(addItem); } let x = Math.round( (mouseX - ctainRect.x) / ((ctainRect.width - 10) / this.layoutColNum) ); if (x >= 1) { x -= 1; } else { 0; } let y = Math.round((mouseY - ctainRect.y) / (this.layoutRowH + 10)); if (y >= 2) { y -= 2; } else { y = 0; } gridLayout.dragEvent(event.type, this.dragItem.portletCodeName, x, y, 3, 4); this.isDragEnter = true; } else { if (index !== -1) { gridLayout.dragEvent("dragleave", this.dragItem.portletCodeName, 0, 0, 0, 0); this.layoutModel.splice(index, 1); } this.isDragEnter = false; } break; } case "dragend": { let item = this.layoutModel.find(item => Object.is(item.portletCodeName, this.dragItem.portletCodeName) ); let gridLayout: any = this.$refs.gridLayout; if (item) { this.dragItem.moved = true; gridLayout.dragEvent( event.type, this.dragItem.portletCodeName, item.x, item.y, item.h, item.w ); } this.dragItem = null; this.isDragEnter = false; break; } } } } </script> <style lang="less"> @import "./app-dashboard-design.less"; </style>