需求:因为是报表类的页面,客户要求一个可以自定义配置宽高、位置、可拖拽的页面
原理
使用到了jquery、jquery的插件gridster
1、通过document的id使用gridster去创建一个对象。
this.gridster = $(`#${this.bench_biz_id} ul`).gridster({
namespace: `#${this.bench_biz_id}`,
// 是否可以放大缩小单元格
resize: {
enabled: true,
axes: ["both"],
min_size: [1, 2],
max_size: [1, 8]
},
min_cols: 4,
// max_size_y: 4,
// 模块的边距,第一个参数是上下边距,第二个参数是左右边距。
widget_margins: [4, 4],
// 模块的宽高 [宽,高]
widget_base_dimensions: [172, 24],
// 序列化
// size_x: wgd.size_x, size_y: wgd.size_y,col: wgd.col, row: wgd.row
serialize_params: function ($w, wgd) {
// 返回序列化后widget的数据
return {
id: $(wgd.el).attr("data-key"),
col: wgd.col,
row: wgd.row,
size_x: wgd.size_x,
size_y: wgd.size_y
};
}
})
2、gridster.add_widget去绘制出对应的网格布局。
豆腐块的删除、新增通过gridster.remove_widget、gridster.add_widget去设置
拖拽、拉伸、移动完成后,再取gridster对象的值出来,需要数据持久化就可以调接口去存取数据 完整代码如下:
<template>
<div>
<tooltip content="自定义配置" placement="left">
<icon name="android-apps" class="config-icon" size="35" @on-click="showMsgBox"></icon>
</tooltip>
<!-- 配置弹出框 -->
<msgBox
v-model="isShow"
title="自定义配置"
width="900"
class-name="pad16"
:closable="false"
:mask-closable="false"
top="50"
>
<div class="config">
<div id="show-config" class="show-config">
<div class="show-config-body">
<checkbox v-for="portal in portals" :key="portal.id" v-model="portal.stats"
@on-change="togglePortal(portal)">
{{ portal.name }}
</checkbox>
</div>
</div>
<div class="config-dashbroad">
<div :id="bench_biz_id" class="gridster">
<ul></ul>
</div>
</div>
</div>
<div slot="footer">
<button type="ghost" @click="close">关闭</button>
<button type="primary" @click="ok">确定</button>
</div>
</msgBox>
</div>
</template>
<script>
// 引入组件
import "@trust-base-common/utils/gridster/jquery.gridster.css";
import "@trust-base-common/utils/gridster/jquery.gridster.js";
import $ from "jquery";
import { isFunction, cloneDeep } from "lodash-es";
export default {
name: "PortalConfig",
props: {
// 配置的唯一id,必填
bench_biz_id: {
type: String,
default: ""
}
},
data() {
return {
// 临时测试数据,应通过接口获取配置项
/* col 数字:起始列
row 数字: 起始行
size_x 数字 : 占用的行数
size_y 数字: 占用的列数
min_rows 最小行数
max_rows 最大行数
"min_cols": 最小列数
"max_cols": 最大列数
*/
portals: [
{
"id": "expenditures",
"name": "支出",
"stats": true,
"col": 1,
"row": 1,
"size_x": 4,
"size_y": 4,
"min_rows": 5,
"min_cols": 1,
"max_rows": 12,
"max_cols": 4
},
{
"id": "meetingSituation",
"name": "上会情况",
"stats": true,
"col": 1,
"row": 5,
"size_x": 2,
"size_y": 7,
"min_rows": 5,
"min_cols": 1,
"max_rows": 11,
"max_cols": 4
},
{
"id": "riskProjectSituation",
"name": "风险项目情况",
"stats": true,
"col": 3,
"row": 5,
"size_x": 2,
"size_y": 8,
"min_rows": 5,
"min_cols": 1,
"max_rows": 11,
"max_cols": 4
},
{
"id": "projectSituation",
"name": "项目开展情况",
"stats": true,
"col": 1,
"row": 14,
"size_x": 2,
"size_y": 3,
"min_rows": 5,
"min_cols": 1,
"max_rows": 11,
"max_cols": 4
}
],
isShow: false,
gridster: {} // gridster对象,网格拖拽
};
},
created() {
this.configPortal();
// 初始化时,抛出值的变更
this.$emit("updateGrid", true, this.portals);
},
methods: {
showMsgBox() {
this.isShow = true;
this.configPortal();
},
// 初始化表格栏栅
initGridster() {
this.gridster = $(`#${this.bench_biz_id} ul`).gridster({
namespace: `#${this.bench_biz_id}`,
// 是否可以放大缩小单元格
resize: {
enabled: true,
axes: ["both"],
min_size: [1, 2],
max_size: [1, 8]
},
min_cols: 4,
// max_size_y: 4,
// 模块的边距,第一个参数是上下边距,第二个参数是左右边距。
widget_margins: [4, 4],
// 模块的宽高 [宽,高]
widget_base_dimensions: [172, 24],
// 序列化
// size_x: wgd.size_x, size_y: wgd.size_y,col: wgd.col, row: wgd.row
serialize_params: function ($w, wgd) {
// 返回序列化后widget的数据
return {
id: $(wgd.el).attr("data-key"),
col: wgd.col,
row: wgd.row,
size_x: wgd.size_x,
size_y: wgd.size_y
};
}
})
.data("gridster");
// 添加数据
this.portals.forEach((item) => {
if (item.stats) {
const nodeModule = `<li data-key="${item.id}" class="${item.id}"><span>${item.name}</span></li>`;
// col 数字:起始列 row 数字: 起始行 size_x 数字 :占用的行数 size_y 数字:占用的列数
this.gridster.add_widget(nodeModule, item.size_x, item.size_y, item.col, item.row, [item.max_cols || 4, item.max_rows], [item.min_cols, item.min_rows]);
}
});
},
// 配置portal
configPortal() {
// 清空现有配置
this.clear();
this.portals = this.portals.filter((e) => e.stats);
// todo 对接接口获取配置数据
// 按行 从前到后排序存放
this.portals.sort((a, b) => a.row - b.row);
// 对列进行排序
this.portals.sort((a, b) => a.row < b.row ? "" : a.col - b.col);
this.$nextTick(this.initGridster);
},
// 设置需要展现portal窗口
togglePortal(portal) {
if (portal.stats) {
const nodeModule = `<li data-key="${portal.id}" class="${portal.id}"><span>${portal.name}</span></li>`;
this.gridster.add_widget(nodeModule, portal.size_x, portal.size_y, null, null, [4, portal.max_rows], [portal.min_cols, portal.min_rows]);
} else {
for (const i in $(`#${this.bench_biz_id} li`)) {
if ($(`#${this.bench_biz_id} li`).eq(i)
.attr("data-key") === portal.id) {
this.gridster.remove_widget($(`#${this.bench_biz_id} li`).eq(i));
}
}
}
},
// 确定按钮,保存数据,更新模块
ok() {
// 预处理数据格式,将grid表格数据记录下来
const newConfig = this.gridster.serialize();
let portals = cloneDeep(this.portals);
// eslint-disable-next-line guard-for-in
for (const i in portals) {
for (const j in newConfig) {
if (newConfig[j].id === portals[i].id) {
portals[i].col = newConfig[j].col;
portals[i].row = newConfig[j].row;
portals[i].size_x = newConfig[j].size_x;
portals[i].size_y = newConfig[j].size_y;
break;
}
}
}
this.portals = portals;
// todo 对接接口保存配置数据
// 通知刷新
this.$emit("updateGrid", true, this.portals);
this.isShow = false;
this.clear();
},
// 取消按钮
close(change) {
this.isShow = false;
this.$emit("updateGrid", change, this.portals);
this.clear();
},
// 清空
clear() {
this.gridster && isFunction(this.gridster.remove_all_widgets) && this.gridster.remove_all_widgets();
}
}
};
</script>
<style lang="scss" scoped>
/deep/ .pad16 .h-modal-body {
padding: 16px 16px 0;
}
.header {
height: 40px;
margin-bottom: 8px;
font-size: 14px;
font-weight: bold;
&-title {
width: 250px;
display: inline-block;
}
/deep/ .h-radio-group-button .h-radio-wrapper {
height: 28px;
line-height: 26px;
}
}
/* 是否显示配置 */
.show-config {
display: inline-block;
width: 105px;
vertical-align: top;
margin-right: 4px;
overflow-x: hidden;
&-title {
font-weight: bold;
padding-bottom: 8px;
border-bottom: 1px #d9d9d9 solid;
margin-bottom: 8px;
font-size: 14px;
&::before {
content: "";
height: 16px;
background: #4686f2;
width: 4px;
display: inline-block;
vertical-align: text-bottom;
margin-right: 4px;
}
}
&-body {
padding-bottom: 12px;
}
}
.show-config .h-checkbox-wrapper {
display: inline-block;
padding: 5px;
}
.config {
height: 402px;
width: 100%;
&-dashbroad {
display: inline-block;
width: calc(100% - 116px);
min-height: 100%;
padding: 4px 8px;
background: #f5f5f5;
border: 1px #d9d9d9 solid;
height: 100%;
overflow: auto;
}
}
.gridster {
position: relative;
display: inline-block;
vertical-align: top;
overflow-x: hidden;
overflow-y: hidden;
width: 100%;
min-height: 90px;
padding: 4px 2px;
}
.gridster > ul {
list-style: none;
padding: 0;
margin: 0;
}
.config-icon {
background-color: white;
color: #4686f2;
border-radius: 50%;
}
</style>
<style lang="scss">
/* 正常状态 */
.gridster {
.gs-w {
background: white;
cursor: move;
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.15);
border-radius: 3px;
padding: 8px;
font-size: 12px;
font-weight: bold;
overflow: hidden;
color: #666;
}
.gs-w:hover {
box-shadow: 0 3px 18px rgba(0, 0, 0, 0.3);
color: #4686f2;
}
}
.gsw-list-body {
height: calc(80% + 5px);
overflow: hidden;
.gsw-list {
margin: 20px auto;
}
}
</style>
使用时,可以用动态组件去加载
<template>
<div class="grid-main-container">
<div class="grid-container">
<!--动态布局-->
<div v-for="item in portals" :key="item.id" minH="50" class="grid-item"
:style=" `grid-column-start: ${item.col}; grid-column-end: ${item.col+item.size_x}; grid-row-start: ${item.row}; grid-row-end: ${item.row+item.size_y};`">
<component
:is="item.id"
:portal="item"
class="panel-box"></component>
</div>
</div>
<!--配置组件-->
<portalConfig
ref="portalConfig"
class="h-main-tooltip"
:bench_biz_id="bench_biz_id"
@updateGrid="updateGrid">
</portalConfig>
</div>
</template>
<script>
// 配置组件
import portalConfig from "./index.vue";
// 业务组件
import expenditures from "../components/expenditures";
import meetingSituation from "../components/meetingSituation";
import riskProjectSituation from "../components/riskProjectSituation";
import projectSituation from "../components/projectSituation";
export default {
name: "HsfundAmopPrdDataVisualization",
data() {
return {
portals: [],
bench_biz_id: "vi" // 配置组件唯一id
};
},
components: {
portalConfig,
expenditures,
meetingSituation,
riskProjectSituation,
projectSituation
},
methods: {
// 通知更新布局
updateGrid(change, portals) {
change && (this.portals = portals);
}
},
created() {
// this.getPortals();
}
};
</script>
<style lang="scss" scoped>
.h-main-tooltip {
position: fixed;
right: 25px;
bottom: 15px;
z-index: 20;
}
/* 公共外框和标题 */
.panel-box {
background: #fff;
box-shadow: 0 1px 4px 0 rgba(137, 150, 173, 0.42);
border-radius: 4px;
margin-bottom: 8px;
height: 100%;
width: 100%;
overflow: auto;
padding: 16px;
}
.h-tooltip-inner {
font-size: 12px;
min-height: 25px;
padding: 3px 7px;
}
.h-tooltip-inner {
font-size: 12px;
min-height: 25px;
padding: 3px 7px;
}
.main-tooltip {
position: absolute;
top: 60px;
left: 0;
width: 38px;
display: none;
}
.clear {
clear: both;
}
.h-modal-header {
border-bottom: 1px solid #ddd;
}
.h-modal-footer {
border-top: 0;
padding: 12px 20px;
}
.panel-box .h-table-tip {
height: 100% !important;
}
$gap: 8px; //grid网格间距
.grid-container {
display: grid;
justify-content: start;
width: calc(100% - 3 * #{$gap});
min-height: inherit;
grid-template-columns: 25% 25% 25% 25%;
grid-auto-rows: 50px;
grid-row-gap: $gap;
grid-column-gap: $gap;
}
.grid-main-container {
background: #f7f7f7;
height: 100%;
}
</style>