場景
項目中遇到要做一個報表的儀表盤,每一個卡片內是一個報表,報表有不同類型,每一種類型有其特定的尺寸。允許選擇報表並添加到儀表盤。允許通過拖拽調整每個卡片位置和卡片的大小。最終可以保存布局好的儀表盤。
遇到的問題
vue-grid-layout通過維護一個數組(layout)實現拖拽布局,每一個卡片為一個item,每一個item含有坐標,寬高等信息。
因此添加卡片時向數組中添加一個item即可,但是這樣新item的坐標總是(0, 0),會將已經布局好的卡片擠走,無法實現選擇可容納卡片的空位添加新元素。
解決思路
卡片對象有以下屬性,其中x, y, w, h是用於記錄卡片位置和大小的關鍵信息,i是卡片的id,需要保證在添加時不出現重復卡片。minW和minH用於規定卡片的最小尺寸,type用於標記該卡片的類型。
// 卡片對象 { "i":"card1", "x": 0, "y": 0, "w":4, "h":2, "minW": 3, "minH":2, "type":"typeA" }
要實現選擇可容納該卡片的空位添加卡片,實際上就是根據現有布局(layout)和新卡片的大小(w和h),算出新卡片的坐標(x和y)。
可分為以下步驟:
- 初始化新元素:創建新卡片元素,需包含布局所需的所有屬性,最好能繼承已創建好卡片的所有其他屬性。
- 確定布局邊界:確定卡片允許添加的區域范圍
- 生成地圖:使用二維數組生成地圖並根據layout標記地圖的占位情況
- 申請位置:遍歷地圖,根據新元素的尺寸在地圖上申請位置,當有滿足其大小的空位時將其插入
vi設計http://www.maiqicn.com 辦公資源網站大全https://www.wode007.com
具體實現
<!-- grid-layout組件調用 --> <grid-layout :layout.sync="layout" :col-num="12" :row-height="72" :is-draggable="true" :is-resizable="true" :is-mirrored="false" :vertical-compact="true" :margin="[10, 10]" :autoSize="true" :use-css-transforms="true"> <grid-item v-for="item in layout" :x="item.x" :y="item.y" :w="item.w" :h="item.h" :i="item.i" :minW="item.minW" :minH="item.minH" :key="item.i"> <!-- 插入你的組件 --> </grid-item> </grid-layout>
/* 新增元素方法**/ function addItem(item, itemId, layout) { // 初始化元素 let newItem = { ...item, "i": itemId, "x": 0, "y": 0, "w": item.w, "h": item.h } // 確定邊界 let Ys = [], maxX = 0, maxY = 0, edgeX = 0, edgeY = 0 layout.map(item => { Ys.push(item.y + item.h) }) maxY = Ys.length && Math.max.apply(null, Ys) || 1 edgeX = 12 edgeY = maxY // 使用二維數組生成地圖 let gridMap = new Array() for (let x = 0; x < edgeX; x++) { gridMap[x] = new Array() for (let y = 0; y < edgeY; y++) { gridMap[x][y] = 0 } } // 標記占位 layout.map(item => { // 將layout中卡片所占區域標記為1 for (let x = item.x; x < (item.x + item.w); x++) { for (let y = item.y; y < (item.y + item.h); y++) { gridMap[x][y] = 1 } } }) // 遍歷地圖,申請位置 for (let y = 0; y < edgeY; y++) { for (let x = 0; x < edgeX; x++) { // 申請所需空間 if (edgeX - x >= item.w && edgeY - y >= item.h) { let itemSignArr = [] for (let a = x; a < (x + item.w); a++) { for (let b = y; b < (y + item.h; b++)) { itemSignArr.push(gridMap[x][y]) } } if (itemSignArr.indexOf(1) < 0) { newItem.x = x newItem.y = y layout.push(newItem) return } } } } // 無滿足條件 newItem.x = 0 newItem.y = edgeY + 1 layout.push(newItem) }
該方法的關鍵在於申請空間:
在遍歷地圖上每一個柵格時,首先需要確定橫向和縱向所剩空間是否能容納下卡片。
如果不能,直接跳出,在可布局邊界的最后一行添加元素即可。
如果可以容納,考察新元素所需空間的每一個柵格是否被占用(地圖gridMap中標記"1"位占用),這里借助一個數組itemSignArr記錄占用情況。如果這個數組中沒有出現"1",即表示所需空間是"空"的,可以再次插入卡片。否則進入下一個柵格重復申請過程。