• Jquery会议室布局含门入口和投影位置调整,并自动截图


    一、关于下载

    1、文章中罗列了主要代码,如需使用,请前往CSDN下载进行下载,包中包含所有文件素材,开箱即用

    2、下载链接:https://download.csdn.net/download/zlxls/88305636

    二、有这么一个需求

    1、会场进行布局,需要设置布局包含的座位,桌子,设置在指定的位置,按照领导席位进行排布。
    2、桌子可能时U型(四个开口方向)、圆形、方形。
    3、根据每个座位序号设置水牌。
    4、保存为图片,方便直接打印,图片为base64,可在服务端进行转储。
    5、功能包括:新增位置、去除/恢复编辑样式、门入口设置、投影设置、显示/隐藏水牌、保存数据(数据保存时进行截图,截图格式为去除编辑样式后的截图)。
    6、保存数据时,会将所有数据打包为json进行打印。
    7、修改时,传入初始化数据,会进行数据初始化。
    8、包中包含所有文件素材,开箱即用。
    9、文件包含一个index.html文件,为主文件。
    10、index-base.html文件为原始拖动插件。
    11、其他js、css等用到的插件已经全部打包,没有文件遗漏。
    12、本插件全部使用js+Jquery进行编写,代码没有进行紧密封装,可以根据自己需要进行修改和封装,中间使用到的jquery选择器比较多,但是主要功能操作都进行了注释,方便读者了解编写时的逻辑,方便进行二次开发和修改。
    12、如有疑问可联系作者,欢迎指出bug和不足之处,以便完善内容。

    三、实现的效果图如下

    四、主要逻辑代码

    1. <!DOCTYPE html>
    2. <html lang="zh">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    7. <title>会议室布局</title>
    8. <link rel="stylesheet" type="text/css" href="css/bootstrap.css">
    9. <link rel="stylesheet" type="text/css" href="css/font-awesome.min.css"/>
    10. <link rel="stylesheet" type="text/css" href="css/default.css">
    11. <link rel="stylesheet" type="text/css" href="css/gridstack.css"/>
    12. <!--[if IE]>
    13. <script src="js/html5.min.js"></script>
    14. <![endif]-->
    15. </head>
    16. <body class="bgcolor-3">
    17. <div class="jq22-container">
    18. <div class="jq22-content">
    19. <div class="container-top">
    20. <div class="btns">
    21. <div data-bind="click: add_new_widget" class="btn btn-primary">+ 新增一个位置(上限 <span class="add-span">0</span> 当前 <span class="add-span">0</span></div>
    22. <div onclick="clear_style(this);" class="btn btn-warning clear_style">去除编辑样式</div>
    23. <div class="btn btn-default">门入口设置:</div>
    24. <div class="btn btn-success door-op" data-type="0" data-index="0">上边</div>
    25. <div class="btn btn-success door-op" data-type="1" data-index="0">右边</div>
    26. <div class="btn btn-success door-op" data-type="2" data-index="0">下边</div>
    27. <div class="btn btn-success door-op" data-type="3" data-index="0">左边</div>
    28. <div class="btn btn-danger door-op" data-type="4" data-index="0">左移</div>
    29. <div class="btn btn-danger door-op" data-type="5" data-index="0">右移</div>
    30. <div class="btn btn-default">投影设置:</div>
    31. <div class="btn btn-success door-op" data-type="0" data-index="1">上边</div>
    32. <div class="btn btn-success door-op" data-type="1" data-index="1">右边</div>
    33. <div class="btn btn-success door-op" data-type="2" data-index="1">下边</div>
    34. <div class="btn btn-success door-op" data-type="3" data-index="1">左边</div>
    35. <div class="btn btn-danger door-op" data-type="4" data-index="1">左移</div>
    36. <div class="btn btn-danger door-op" data-type="5" data-index="1">右移</div>
    37. <div onclick="submitHandler();" class="btn btn-success" style="float: right;">保存数据</div>
    38. <div onclick="names_style(this);" class="btn btn-primary" style="float: right;">隐藏水牌</div>
    39. </div>
    40. <p class="tips" style="">请根据会场进行布局,位置按钮可以拖拽放大缩小,上下位置,不能有空位,可使用隐藏功能占位</p>
    41. </div>
    42. <div id="container-data">
    43. <div class="names"></div>
    44. <div class="container-fluid" style="padding: 15px;">
    45. <div data-bind="component: {name: 'dashboard-grid', params: $data}" class="template-base"></div>
    46. <div class="door-ty door"><img src="img/door.png"></div>
    47. <div class="door-ty ty"><img src="img/ty.png"></div>
    48. </div>
    49. </div>
    50. </div>
    51. </div>
    52. <input name="id" type="hidden" value="">
    53. <input name="type" type="hidden" value="">
    54. <script src="js/jquery.min.js"></script>
    55. <script src="js/jquery-ui.min.js"></script>
    56. <script src="js/bootstrap.min.js"></script>
    57. <script src="js/lodash.min.js"></script>
    58. <script src="js/knockout-min.js"></script>
    59. <script src="js/gridstack.js"></script>
    60. <script src="js/html2canvas.min.js"></script>
    61. <script src="layer/layer.min.js"></script>
    62. <script type="text/javascript">
    63. //数据
    64. //格式:[{x: 0, y: 0, width: 1, height: 1, input: '姓名1', hide: '隐藏', class: '',rate:0}];
    65. var json = "";
    66. //数量
    67. var roomIdNum = "60";
    68. roomIdNum = parseInt(roomIdNum);
    69. $(".add-span").html(roomIdNum);
    70. //虚拟对齐显示数量,用来美化页面
    71. var roomIdNumYushu = roomIdNum % 10;
    72. var roomIdNumShow = roomIdNum+(roomIdNumYushu>0?(10-roomIdNumYushu):0);
    73. //操作类型
    74. var type = "0";
    75. //门、投影的位置
    76. //格式:[{"t":"bottom","t1":"0","l":"left","l1":"50"},{"t":"top","t1":"0","l":"left","l1":"50"}]
    77. //下标0为门位置1为投影位置
    78. var doors = "";
    79. //水牌
    80. //格式:['张三','李四']
    81. var names = "";
    82. //初始化组件相关参数
    83. ko.components.register('dashboard-grid', {
    84. viewModel: {
    85. createViewModel: function (controller, componentInfo) {
    86. var ViewModel = function (controller, componentInfo) {
    87. var grid = null;
    88. this.widgets = controller.widgets;
    89. this.afterAddWidget = function (items) {
    90. if (grid == null) {
    91. grid = $(componentInfo.element).find('.grid-stack').gridstack({
    92. auto: false
    93. }).data('gridstack');
    94. }
    95. var item = _.find(items, function (i) { return i.nodeType == 1 });
    96. grid.add_widget(item);
    97. ko.utils.domNodeDisposal.addDisposeCallback(item, function () {
    98. grid.remove_widget(item);
    99. });
    100. };
    101. };
    102. return new ViewModel(controller, componentInfo);
    103. }
    104. },
    105. template:
    106. [
    107. '
      ',
    108. '
      ': $data.input,\'data-class\': $data.class,\'data-hide\': $data.hide,\'data-rate\': $data.rate,\'data-gs-x\': $data.x, \'data-gs-y\': $data.y, \'data-gs-width\': $data.width, \'data-gs-height\': $data.height, \'data-gs-auto-position\': $data.auto_position}">',
    109. '
      ' +
    110. ' ' +
    111. ' ' +
    112. ' ☐型' +
    113. ' u型' +
    114. ' ⊂型' +
    115. ' ∩型' +
    116. ' ⊃型' +
    117. ' ○型' +
    118. ' ' +
    119. ' ' +
    120. '
      ' +
  • ' ',
  • '
    ',
  • '
    '
  • ].join('')
  • });
  • $(function () {
  • //模块形状点击事件
  • $(".template-base").on('click','.grid-style-span span',function(){
  • $(this).parent().parent().removeClass("grid-style-0").removeClass("grid-style-1").removeClass("grid-style-2").removeClass("grid-style-3").removeClass("grid-style-4").removeClass("grid-style-5").addClass("grid-style-"+$(this).data("type"));
  • });
  • //显示或隐藏组件
  • $(".template-base").on('click','.opacity-span',function(){
  • if($(this).parent().hasClass("active")){
  • $(this).parent().find(".grid-style-span").show();
  • $(this).parent().find("input").removeClass("input-show");
  • $(this).parent().removeClass("active");
  • $(this).html("隐藏");
  • }else{
  • $(this).parent().find(".grid-style-span").hide();
  • $(this).parent().find("input").addClass("input-show");
  • $(this).parent().addClass("active");
  • $(this).html("显示");
  • }
  • getCount();
  • });
  • //初始化组件相关事件
  • var Controller = function (widgets) {
  • var self = this;
  • this.widgets = ko.observableArray(widgets);
  • //新增一个组件
  • this.add_new_widget = function () {
  • this.widgets.push({x: 0, y: 0, width: 1, height: 1, input:'', hide:'隐藏', rate:0, class:'', auto_position: true});
  • getCount();
  • };
  • //删除一个组件
  • this.delete_widget = function (item) {
  • self.widgets.remove(item);
  • getCount();
  • };
  • };
  • //初始数据初始化
  • var widgets = [];
  • if(json) {
  • widgets = JSON.parse(json);
  • //计算个数
  • $(".add-span").eq(1).html(json.match(new RegExp(/\隐藏/g)).length);
  • }
  • var controller = new Controller(widgets);
  • ko.applyBindings(controller);
  • //门、投影的位置点击事件
  • setDoorOpClick();
  • //门、投影的位置 初始化
  • //延时加载,不然获取的宽度不是数据初始化的宽度
  • setTimeout(function () {
  • initDoorOp();
  • },500);
  • //水牌 初始化
  • initNames();
  • });
  • /**
  • * 数据提交
  • */
  • function submitHandler() {
  • var serialized_data = _.map($('.grid-stack > .grid-stack-item:visible'), function (el) {
  • el = $(el);
  • var node = el.data('_gridstack_node');
  • return {x : node.x, y : node.y, width : node.width, height : node.height, input : el.find("input").val(), hide : el.find(".opacity-span").html(), rate : getRateFlag(el), class : el.find(".grid-stack-item-content").hasClass("active")?'active':'',};
  • }, this);
  • if(serialized_data.length<1){
  • layer.alert("请完善布局再提交");
  • return false;
  • }
  • var count = serialized_data.length - $('.grid-stack > .grid-stack-item:visible > .active').length;
  • if(count>roomIdNum){
  • layer.alert("该会场容纳人数最多为"+roomIdNum+",当前布局"+count);
  • return false;
  • }
  • names = [];
  • $("[name='names[]']").each(function () {
  • names.push($(this).val());
  • });
  • var _data = {
  • json : JSON.stringify(serialized_data),
  • doors : JSON.stringify(doors),
  • names : JSON.stringify(names),
  • type : $("[name='type']").val(),
  • id : $("[name='id']").val()
  • };
  • //begin截图时要处理的问题
  • //1、如果是编辑样式,则去除
  • var clear_style_flag = 0;
  • if(!$(".template-base").hasClass("template-base-style")){
  • clear_style(".clear_style");
  • clear_style_flag = 1;
  • }
  • //2、输入框的背景颜色去除
  • $(".input-show").css("background","unset");
  • //3、输入框截图存在错位,需要动态替换为div标签显示
  • $(".names-name").each(function () {
  • $(this).find("div").html($(this).find("input").val());
  • $(this).find("input").hide();
  • $(this).find("div").show();
  • });
  • //end
  • html2canvas(document.getElementById("container-data")).then(function(canvas) {
  • //begin截图后进行数据恢复
  • //1、如果去除了编辑样式,则恢复
  • if(clear_style_flag == 1){
  • clear_style(".clear_style");
  • }
  • //2、输入框的背景颜色恢复
  • $(".input-show").css("background","#18bc9c");
  • //3、动态恢复为input标签显示
  • $(".names-name").each(function () {
  • $(this).find("input").show();
  • $(this).find("div").hide();
  • });
  • //end
  • _data.img = canvas.toDataURL("image/png");
  • console.info(_data);
  • return false;
  • $.ajax({
  • url: "http://localhost:8183/layout/editData",
  • type: "post",
  • dataType: "json",
  • data:_data,
  • success: function(result) {
  • layer.alert(result.msg);
  • }
  • });
  • });
  • }
  • /**
  • * 计算组件个数
  • */
  • function getCount() {
  • var _count = $('.grid-stack > .grid-stack-item:visible').length - $('.grid-stack > .grid-stack-item:visible > .active').length;
  • $(".add-span").eq(1).html(_count);
  • }
  • /**
  • * 清楚组件编辑样式
  • */
  • function clear_style(_this) {
  • if($(".template-base").hasClass("template-base-style")){
  • $(".template-base").removeClass("template-base-style");
  • $(_this).html("去除编辑样式");
  • $(".grid-stack-item-content input").attr("placeholder","未填写序号或名称");
  • $(".names-name input").attr("placeholder","请输入姓名");
  • }else{
  • $(".template-base").addClass("template-base-style");
  • $(_this).html("恢复编辑样式");
  • $("input").removeAttr("placeholder");
  • }
  • }
  • /**
  • * 水牌名称编辑样式
  • */
  • function names_style(_this) {
  • if($(_this).html()=='隐藏水牌'){
  • $(".names").hide();
  • $(_this).html("显示水牌");
  • }else{
  • $(".names").show();
  • $(_this).html("隐藏水牌");
  • }
  • }
  • /**
  • * 获取形状编号
  • * @param el
  • * @returns {number}
  • */
  • function getRateFlag(el) {
  • if(el.find(".grid-stack-item-content").hasClass("grid-style-1")){return 1;
  • }else if(el.find(".grid-stack-item-content").hasClass("grid-style-2")){return 2;
  • }else if(el.find(".grid-stack-item-content").hasClass("grid-style-3")){return 3;
  • }else if(el.find(".grid-stack-item-content").hasClass("grid-style-4")){return 4;
  • }else if(el.find(".grid-stack-item-content").hasClass("grid-style-5")){return 5;
  • }else{return 0;}
  • }
  • /**
  • * 门和投影点击事件
  • */
  • function setDoorOpClick() {
  • $(".door-op").on('click',function(){
  • var numOne = 5;
  • var type = parseInt($(this).data("type"));
  • var index = parseInt($(this).data("index"));
  • var indexBegin = getDoorOpIndexBegin(index);
  • switch (type) {
  • case 0:doors[index].t = "top"; doors[index].t1 = "0"; doors[index].l = "left"; doors[index].l1 = "50";break;//
  • case 1:doors[index].t = "right"; doors[index].t1 = "0"; doors[index].l = "top"; doors[index].l1 = "50";break;//
  • case 2:doors[index].t = "bottom"; doors[index].t1 = "0"; doors[index].l = "left"; doors[index].l1 = "50";break;//
  • case 3:doors[index].t = "left"; doors[index].t1 = "0"; doors[index].l = "top"; doors[index].l1 = "50";break;//
  • case 4://左移 上移
  • var _val = parseInt(doors[index].l1)-numOne;
  • doors[index].l1 = _val<=0?0:_val;
  • break;
  • default://右移 下移
  • var _val = parseInt(doors[index].l1)+numOne;
  • doors[index].l1 = _val>=100?100:_val;
  • break;
  • }
  • setDoorOpStyle();
  • });
  • }
  • /**
  • * 门和投影移动方向
  • */
  • function setDoorOpStyle() {
  • setDoorOp(0);
  • setDoorOp(1);
  • //获取当前方向标签的长度或者高度
  • var max0 = getDoorOpStyleMax(0);
  • var max1 = getDoorOpStyleMax(1);
  • //计算当前比例下的实际定位长度或者高度
  • var aspect0 = (max0/100)*parseInt(doors[0].l1);
  • var aspect1 = (max1/100)*parseInt(doors[1].l1);
  • //当前定位方向
  • var position0 = doors[0].l;
  • var position1 = doors[1].l;
  • //如果为左侧定位&&定位数值为100%时,换为右侧定位&&数值为0%
  • if(doors[0].l=='left' && parseFloat(doors[0].l1)==100){
  • aspect0 = 0;
  • position0 = 'right';
  • }
  • if(doors[1].l=='left' && parseFloat(doors[1].l1)==100){
  • aspect1 = 0;
  • position1 = 'right';
  • }
  • $(".door").css("top","unset").css("left","unset").css("right","unset").css("bottom","unset").css(doors[0].t,doors[0].t1+"px").css(position0,aspect0+"px");
  • $(".ty" ).css("top","unset").css("left","unset").css("right","unset").css("bottom","unset").css(doors[1].t,doors[1].t1+"px").css(position1,aspect1+"px");
  • }
  • /**
  • * 门和投影选择器
  • * @param _index
  • * @returns {number}
  • */
  • function getDoorOpIndexBegin(_index){
  • return 6*(_index+1);
  • }
  • /**
  • * 门和投影写入操作
  • * @param _index
  • */
  • function setDoorOp(_index){
  • if(doors[_index].t == "top"||doors[_index].t == "bottom"){
  • $(".door-op").eq(getDoorOpIndexBegin(_index)-2).html("左移");$(".door-op").eq(getDoorOpIndexBegin(_index)-1).html("右移");
  • }else{
  • $(".door-op").eq(getDoorOpIndexBegin(_index)-2).html("上移");$(".door-op").eq(getDoorOpIndexBegin(_index)-1).html("下移");
  • }
  • }
  • /**
  • * 门和投影获取实际长了宽
  • * @param _index
  • * @returns {jQuery}
  • */
  • function getDoorOpStyleMax(_index) {
  • if(doors[_index].t == "top"||doors[_index].t == "bottom"){
  • return $(".container-fluid").width();
  • }else{
  • return $(".container-fluid").height();
  • }
  • }
  • /**
  • * 门、投影的位置 初始化
  • */
  • function initDoorOp(){
  • if(doors && $.isArray(JSON.parse(doors))) {
  • doors = JSON.parse(doors);
  • }else{
  • doors = [{"t":"bottom","t1":"0","l":"left","l1":"50"},{"t":"top","t1":"0","l":"left","l1":"50"}];
  • }
  • setDoorOpStyle();
  • }
  • /**
  • * 水牌 初始化
  • * 由于容纳人数roomIdNum是动态的,
  • * 这里需要动态处理已有的水牌数据,
  • * 例如原本容纳人数为20人,修改时可能变成15人或者25人,
  • * 要以实际能容纳的人数为基准。
  • */
  • function initNames(){
  • var _names = names?JSON.parse(names):[];
  • names = [];
  • for (var i=0;i<roomIdNumShow;i++){
  • names.push(_names.length>i?_names[i]:"");
  • }
  • setNames();
  • }
  • /**
  • * 水牌名称设置
  • */
  • function setNames() {
  • var html = "";
  • for (var i=0;i<names.length;i++){
  • if(i<roomIdNum){
  • html+='
    '+(i+1)+'
    +names[i]+'" placeholder="请输入姓名">
    '
    ;
  • }else{
  • //补齐一排的样式,必然比较难看
  • html+='
    '
    ;
  • }
  • }
  • $(".names").html(html);
  • }
  • </script>
  • </body>
  • </html>
  • 相关阅读:
    servlet相关
    知道密码,如何去除Word文档的各种保护?
    Python复习知识点(二)
    jdbc——行文架构
    Nginx的进程结构
    gitee git 打一个 tag
    数据离散化
    快速上手Flask(一) 认识框架Flask、项目结构、开发环境
    iText7画发票PDF——小tips
    请问怎么编程菜单和好多东西学校都没教
  • 原文地址:https://blog.csdn.net/zlxls/article/details/132710619