RuanXuSong / 我的元素

// ==UserScript==
// @name         我的元素
// @namespace    https://github.com/RuanXuSong/myElement
// @version      1.4
// @description  用于在页面中插入元素,改变元素样式用于展示
// @author       Ruan Xusong
// @match        http://*/*
// @match        https://*/*
// @license      MIT
// @grant        none
// @require      https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js
// ==/UserScript==

(function() {
  "use strict";
  $.noConflict(); // 兼容网页中$
  // dom加载完成后,运行jquery函数域
  jQuery(document).ready(function($) {
    // 元素列表(注:initialStyle中不可有空格)
    const myElementsList = [
      {
        name: "div",
        content: "",
        type: "div",
        initialStyle:
          "position:fixed;left:50%;top:50%;width:100px;height:100px;background:#0189fb;z-index:100;cursor:pointer;"
      },
      {
        name: "button",
        content: "button",
        type: "button",
        initialStyle:
          "position:fixed;left:50%;top:50%;width:70px;height:25px;background:#fff;border-radius:10px;z-index:100;cursor:pointer;"
      },
      // line中的长宽属性无效
      {
        name: "line",
        content: "",
        type: "line",
        initialStyle:
          "position:fixed;left:50%;top:50%;background:#000;z-index:100"
      },
      {
        name: "cover",
        content: "",
        type: "cover",
        initialStyle:
          "position:fixed;left:50%;top:50%;width:70px;height:25px;background:#fff;border-radius:10px;z-index:100;cursor:pointer;"
      }
    ];
    /**
     * @功能描述: 初始化元素
     * @参数:
     * @返回值:
     */
    const initialCom = new InitialCom();
    // 初始化元素,插入元素
    initialCom.initialAll();

    /**
     * @功能描述: 删除元素
     * @参数:
     * @返回值:
     */
    $("#cancelBtn").click(function() {
      if (!$(this).hasClass("cancelDelete")) {
        initialCom.deleteBtnclick(this);
      } else {
        initialCom.cancelBtnClick(this);
      }
    });

    // 一键填充功能
    //===start===

    /**
     * @功能描述: 查看元素信息
     * @参数:
     * @返回值:
     */

    $("#inspectBtn").click(function() {
      const datePoint = new Date().getTime();
      const attrFunc = new AttrFunc(datePoint);
      // 查看元素信息
      attrFunc.pythonListen();
    });

    /**
     * @功能描述: 一键填充表单按钮点击
     * @参数:
     * @返回值:
     */
    $("#fillFormBtn").click(function() {
      initialCom.fillForm();
    });

    /**
     * @功能描述: 改变填充表单名字input
     * @参数:
     * @返回值:
     */

    $("#fillFormName").change(function() {
      if ($("#fillFormInput").val() !== "") {
        // 一键填充表单值
        initialCom.handleFillInputChange();
      }
    });

    /**
     * @功能描述: 改变填充表单值input
     * @参数:
     * @返回值:
     */

    $("#fillFormInput").change(function() {
      if ($("#fillFormName").val() !== "") {
        // 一键填充表单值
        initialCom.handleFillInputChange();
      }
    });

    // 一键填充功能
    //===end===

    /** 启用/禁用链接跳转 */
    $("#hrefBtn").click(function() {
      // 切换链接模式
      initialCom.hrefToggle();
    });

    // 查看元素功能
    //===start===

    /** 转换HTML为字符串 */
    function ToHtmlString(htmlStr) {
      return toTXT(htmlStr).replace(
        /\&lt\;br[\&ensp\;|\&emsp\;]*[\/]?\&gt\;|\r\n|\n/g,
        "<br/>"
      );
    }

    function toTXT(str) {
      var RexStr = /\<|\>|\"|\'|\&| | /g;
      str = str.replace(RexStr, function(MatchStr) {
        switch (MatchStr) {
          case "<":
            return "&lt;";
            break;
          case ">":
            return "&gt;";
            break;
          case '"':
            return "&quot;";
            break;
          case "'":
            return "&#39;";
            break;
          case "&":
            return "&amp;";
            break;
          case " ":
            return "&ensp;";
            break;
          case " ":
            return "&emsp;";
            break;
          default:
            break;
        }
      });
      return str;
    }

    /** 将html转换为可以存储的片段,替换<>间的"为' */
    function toStorageString(str) {
      var RexStr = /\<(.*)(")(.*)\>/g;
      str = str.replace(RexStr, function(MatchStr) {
        return MatchStr.replace(/"/g, "'");
      });
      return str;
    }

    // 查看元素功能
    //===end===
    /**
     * @功能描述: 点击切换列表
     * @参数: 无
     * @返回值: 无
     */
    $(".tabsBtnWrap .tabsBtn").click(function() {
      initialCom.tabsSwitchIndex($(this).index());
    });

    /**
     * @功能描述: 点击下载按钮
     * @参数:
     * @返回值:
     */
    $("#downloadBtn").click(function() {
      initialCom.downloadBtnClick();
    });

    /**
     * @功能描述: 点击按钮新增属性input
     * @参数: 无
     * @返回值: 无
     */
    $(".addInfoBtn").click(function() {
      const datePoint = new Date().getTime();
      const attrFunc = new AttrFunc(datePoint);
      if ($(".infoUl li").length > 0) {
        attrFunc.appendInputLi();
        // 属性名称改变事件
        $(`#cssType${datePoint}`).change(attrFunc.cssTypeChange);
        // 属性内容改变事件
        $(`#input${datePoint}`).change(attrFunc.contentChange);
      } else {
        alert("当前属性为空,请先新建实例元素,再双击选择添加属性!");
      }
    });

    /**
     * @功能描述: 点击页面中插入列表元素
     * @参数: 无
     * @返回值: 无
     */
    $(".elementListLi").click(function() {
      const datePoint = new Date().getTime();
      const elementFunc = new ElementFunc(datePoint);
      let lineFlag = false;
      let type = $(this).attr("type");
      // 解绑上次的线点击事件
      elementFunc.cancelBind();
      if (type === "line") {
        elementFunc.cancelDrawLine();
        lineFlag = true;
      } else {
        const initialStyle = $(this).attr("initialStyle");
        // 文字内容
        const content = $(this).attr("content") || "";
        // 创建元素
        const element = elementFunc.createEle(
          datePoint,
          type,
          content,
          initialStyle
        );
      }

      if (type === "cover") {
        elementFunc.createCover();
        return;
      }

      // 线-绑定全局鼠标点击事件
      if (lineFlag) {
        const lineFunc = new ElementFunc("");
        // 生成线条消抖
        setTimeout(function() {
          $("body").bind("click", lineFunc.handleLineClick);
        }, 1);
      } else {
        // 绑定element鼠标点击事件
        $(`#${datePoint}`).bind("mousedown", elementFunc.elementMousedown);
        // 绑定松开鼠标事件
        $(`#${datePoint}`).bind("mouseup", function() {
          $("body").unbind("mousemove");
        });
        // 绑定双击事件
        $(`#${datePoint}`).dblclick(elementFunc.elementDblclick);
      }
    });

    /**
     * @功能描述: json字符串转css
     * @参数: json的字符串
     * @返回值:css的字符串
     */

    function jsonToCss(json) {
      let cssString = "{";
      Object.keys(json).map(item => {
        cssString += `${item}:${json[item]};`;
      });
      cssString += "}";
      return cssString;
    }

    /**
     * @功能描述:日期转换(长)
     * @参数: date
     * @返回值: 返回类似2019-08-17,19:18:10的字符串
     */

    function longDateFormate(dateObj) {
      const year = dateObj.getFullYear();
      const month = dateObj.getMonth() + 1;
      const date = dateObj.getDate();
      const hour = dateObj.getHours();
      const minute = dateObj.getMinutes();
      const second = dateObj.getSeconds();
      return `${year}-${month < 10 ? "0" + month : month}-${date < 10 ? "0" + date : date},${hour < 10 ? "0" + hour : hour}:${minute < 10 ? "0" + minute : minute}:${second < 10 ? "0" + second : second}`;
    }

    /**
     * @功能描述:日期转换(短)
     * @参数: date
     * @返回值: 返回类似19时18分10秒的字符串
     */

    function shortDateFormate(dateObj) {
      const hour = dateObj.getHours();
      const minute = dateObj.getMinutes();
      const second = dateObj.getSeconds();
      return `${
        hour < 10 ? "0" + hour : hour
      }:${minute < 10 ? "0" + minute : minute}:${second < 10 ? "0" + second : second}`;
    }

    /**
     * @功能描述: 画点函数
     * @参数:key:唯一标识位,x:x坐标,y:y坐标,color:点颜色
     * @返回值:画线时返回点容器
     */
    function drawDot(key, x, y, color, dotContainer) {
      const dotDiv = `<div class="line${key}" style="background:${color ||
        "#000"};position:fixed;left:${x}px;top:${y}px;width:5px;height:5px;transform:translate(-50%,-50%);border-radius: 100%;z-index:1000"></div>`;
      if (dotContainer) {
        $(dotContainer).append(dotDiv);
        return $(dotContainer);
      } else {
        $(`#lineConatiner${key}`).append(dotDiv);
      }
    }

    /**
     * @功能描述: 画线函数
     * @参数:key:唯一标识位,x1:起点x坐标,y1:起点y坐标,x2:终点x坐标,y2:终点y坐标,color:线颜色,lineType:dotted(为dotted的时候为虚线,默认实线)
     * @返回值:无
     */
    function drawLine(key, x1, y1, x2, y2, color, lineType) {
      let dotContainer = `<div class="dotContainer${key}"></div>`;
      // 线长度
      const lineLength = Math.floor(
        Math.sqrt(Math.abs((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)))
      );
      // 初始化间隔为实线
      const initialPadding = 1;
      // 间隔
      const padding = lineType && lineType === "dotted" ? 8 : initialPadding;
      // 每一次变化的x,y值
      const stepX = (Math.abs(x2 - x1) / lineLength) * padding;
      const stepY = (Math.abs(y2 - y1) / lineLength) * padding;
      for (let i = 0; i <= lineLength / padding; i++) {
        let drawX;
        let drawY;
        if (x2 > x1) {
          drawX = x1 + i * stepX;
        } else {
          drawX = x1 - i * stepX;
        }
        if (y2 > y1) {
          drawY = y1 + i * stepY;
        } else {
          drawY = y1 - i * stepY;
        }
        dotContainer = drawDot(key, drawX, drawY, color, dotContainer);
        if (i === Math.floor(lineLength / padding)) {
          $(`#lineConatiner${key}`).append(dotContainer);
        }
      }
    }

    /**
     * @功能描述: 属性事件构造函数
     * @参数:datePoint:唯一标识
     * @返回值:
     */
    function AttrFunc(datePoint) {
      this.selectedDom = "";
      this.domObj = {};
      this.idx = 0;
      this.innerFlag = false;
      this.domAlertId = "spyon-container";
      this.areaWrapId = "area-wrap-container";
      this.posBuffer = 3;
      this.appendInputLi = () => {
        let inputLi = `<li class="detailLi ${datePoint}"><input id='cssType${datePoint}'/><div class="fillFormColon">:</div><input cssType='' id='input${datePoint}'/></li>`;
        $("#siderBar .infoUl").append(inputLi);
      };
      this.appendPythonInput = () => {
        let inputLi = `<li class="fillFormLi ${datePoint}"><input id='pythonTitle${datePoint}' class="pythonName"/><div class="fillFormColon">:</div><button class="selectBtn" id='pythonDom${datePoint}'>请选择</button></li>`;
        $(".pythonUl").append(inputLi);
        $(".pythonEmptyBox").hide();
      };

      /** 新增默认python字段 */
      this.appendInitialInput = selectArr => {
        const _this = this;
        if (selectArr.length > 0) {
          selectArr.forEach(({ name, label }, idx) => {
            const modifiedDatePoint = datePoint + idx;
            let inputLi = `<div class="fillFormLabel">${label}:</div><li class="fillFormLi ${modifiedDatePoint}"><input id='pythonTitle${modifiedDatePoint}' value="${name}" disabled class="pythonName"/><div class="fillFormColon">:</div><button class="selectBtn" id='pythonDom${modifiedDatePoint}'>请选择</button></li>`;
            $(".pythonUl").append(inputLi);
            $(".pythonEmptyBox").hide();
            // Dom选择内容点击事件
            $(`#pythonDom${modifiedDatePoint}`).click(function() {
              _this.idx = idx;
              _this.pythonListen();
            });
          });
        }
      };

      /**
       * @功能描述: python下拉type改变事件
       * @参数:
       * @返回值:
       */

      this.pythonTypeChange = type => {
        // 清空表单项和label
        $(".pythonUl .fillFormLi,.fillFormLabel").remove();
        let pythonSelect = [];
        switch (type) {
          case "cardItem":
            this.innerFlag = false;
            pythonSelect = [
              { name: "firstSelect", label: "第一个cardItem" },
              { name: "secondSelect", label: "第二个cardItem" },
              { name: "lastSelect", label: "最后一个cardItem" }
            ];

            break;
          case "cardInfo":
            this.innerFlag = false;
            pythonSelect = [
              { name: "title", label: "标题" },
              { name: "url", label: "链接" }
            ];
            break;
          case "page":
            this.innerFlag = true;
            pythonSelect = [{ name: "pageDom", label: "分页器" }];
            break;
          case "pageInfo":
            this.innerFlag = false;
            break;
          default:
            break;
        }
        this.appendInitialInput(pythonSelect);
      };

      /**
       * @功能描述: python字段name改变事件
       * @参数:
       * @返回值:
       */
      this.pythonNameChange = function() {
        const ls = window.localStorage;
        $(`#pythonDom${datePoint}`).attr("pythonName", $(this).val());
        if ($(`#pythonDom${datePoint}`).attr("pythonType") !== "") {
        }
      };

      /** python-创造监听样式弹窗 */
      this.pythonCreate = () => {
        const div = document.createElement("div");
        const areaWrap = document.createElement("div");
        div.id = this.domAlertId;
        areaWrap.id = this.areaWrapId;
        div.setAttribute(
          "style",
          `
      position: absolute;
      left: 0;
      top: 0;
      width: auto;
      height: auto;
      padding: 10px;
      box-sizing: border-box;
      color: #fff;
      background-color: rgba(66,67,67,0.7);
      z-index: 100000;
      font-size: 12px;
      border-radius: 5px;
      line-height: 20px;
      max-width: 45%;
      text-align: left;
      `
        );
        areaWrap.setAttribute(
          "style",
          `
      position: absolute;
      left: 0;
      top: 0;
      width: auto;
      height: auto;
      padding: 0px;
      box-sizing: border-box;
      background-color: rgba(0,0,0,0.3);
      z-index: -10;
      border-radius: 5px;
      border:1px solid #ddd;
      `
        );
        document.body.appendChild(areaWrap);
        document.body.appendChild(div);
      };

      /** python-显示弹窗 */
      this.pythonShow = e => {
        const spyContainer = document.getElementById(this.domAlertId);
        const wrapContainer = document.getElementById(this.areaWrapId);
        if (!spyContainer) {
          this.pythonCreate();
          return;
        }
        if (spyContainer.style.display !== "block") {
          spyContainer.style.display = "block";
        }
        if (wrapContainer.style.display !== "block") {
          wrapContainer.style.display = "block";
        }
      };

      /** python-隐藏弹窗 */
      this.pythonHide = e => {
        document.getElementById(this.domAlertId).style.display = "none";
        document.getElementById(this.areaWrapId).style.display = "none";
      };

      /** python-获得srcoll的宽度高度 */
      this.getScrollPos = () => {
        const ieEdge = document.all ? false : true;
        if (!ieEdge) {
          return {
            left: document.body.scrollLeft,
            top: document.body.scrollTop
          };
        } else {
          return {
            left: document.documentElement.scrollLeft,
            top: document.documentElement.scrollTop
          };
        }
      };

      /** python-滑过元素 */
      this.pythonGlide = e => {
        const spyContainer = document.getElementById(this.domAlertId);
        const wrapContainer = document.getElementById(this.areaWrapId);
        if (!spyContainer) {
          this.pythonCreate();
          return;
        }
        const left = e.clientX + this.getScrollPos().left + this.posBuffer;
        const top = e.clientY + this.getScrollPos().top + this.posBuffer;
        const wrapLeft =
          e.target.getBoundingClientRect().left +
          document.documentElement.scrollLeft;
        const wrapTop =
          e.target.getBoundingClientRect().top +
          document.documentElement.scrollTop;
        const wrapWidth = e.target.offsetWidth;
        const wrapHeight = e.target.offsetHeight;
        spyContainer.innerHTML = this.showAttributes(e.target);
        wrapContainer.style.left = wrapLeft + "px";
        wrapContainer.style.top = wrapTop + "px";
        wrapContainer.style.width = wrapWidth + "px";
        wrapContainer.style.height = wrapHeight + "px";
        if (left + spyContainer.offsetWidth > window.innerWidth) {
          spyContainer.style.left = left - spyContainer.offsetWidth + "px";
        } else {
          spyContainer.style.left = left + "px";
        }
        spyContainer.style.top = top + "px";
      };

      /** python-获得dom属性 */
      this.showAttributes = el => {
        const nodeName = `${el.nodeName.toLowerCase()}`;
        // 单标签
        const singleTags = ["img", "input", "area"];
        const nodeNameDom = `<span style="font-weight:bold;">${nodeName}</span><br/>`;
        const attrArr = Array.from(el.attributes);
        const attributes = attrArr.reduce((attrs, attr) => {
          attrs += `${attr.nodeName}="${attr.nodeValue}" `;
          return attrs;
        }, "");
        const attributesDom = attrArr.reduce((attrs, attr) => {
          attrs += `<span style="color:#ffffcc;">${attr.nodeName}</span>="${attr.nodeValue}"<br/>`;
          return attrs;
        }, "");
        const htmlDom = `<div>&lt;${nodeName} ${attributes}&gt; ${ToHtmlString(
          el.innerHTML
        )} &lt;/${nodeName}&gt;</div><br/>`;
        this.selectedDom = `<${nodeName} ${attributes}> ${
          !singleTags.includes(nodeName)
            ? `${this.innerFlag ? el.innerHTML : ""} </${nodeName}>`
            : ""
        }`;
        return nodeNameDom + attributesDom + htmlDom;
      };
      /** python-存储点击的元素 */
      this.selectPythonDom = el => {
        // 移除监听事件
        this.removePythonListen();
      };

      /**
       * @功能描述: 打开python监听当前元素
       * @参数:
       * @返回值:
       */
      this.pythonListen = () => {
        document.body.addEventListener("mousemove", this.pythonGlide);
        document.body.addEventListener("mouseover", this.pythonShow);
        document.body.addEventListener("mouseleave", this.pythonHide);
        document.body.addEventListener("mousedown", this.selectPythonDom);
      };

      /**
       * @功能描述:移除python监听元素
       * @参数:
       * @返回值:
       */
      this.removePythonListen = () => {
        document.body.removeEventListener("mousemove", this.pythonGlide);
        document.body.removeEventListener("mouseover", this.pythonShow);
        document.body.removeEventListener("mouseleave", this.pythonHide);
        document.body.removeEventListener("mousedown", this.selectPythonDom);
        document.getElementById(this.domAlertId).style.display = "none";
        document.getElementById(this.areaWrapId).style.display = "none";
      };

      /**
       * @功能描述: 改变css名称的事件
       * @参数:
       * @返回值:
       */
      this.cssTypeChange = function() {
        const ls = window.localStorage;
        $(`#input${datePoint}`).attr("cssType", $(this).val());
        if ($(`#input${datePoint}`).val() !== "") {
          const selectedElement = ls.getItem("selectedElement");
          if (selectedElement !== "") {
            if ($(`#input${datePoint}`).attr("cssType") !== "") {
              const cssType = $(`#input${datePoint}`).attr("cssType");
              const val = $(`#input${datePoint}`).val();
              // 缓存中设置新的CSS样式
              let oldSiderBarCssObj = JSON.parse(ls.getItem("siderBarCssObj"));
              let oldCssObj = oldSiderBarCssObj[selectedElement] || {};
              let newCssObj = { ...oldCssObj, [cssType]: val };
              ls.setItem(
                "siderBarCssObj",
                JSON.stringify({
                  ...oldSiderBarCssObj,
                  [selectedElement]: newCssObj
                })
              );
              $(`#${selectedElement}`).css(cssType, val);
            } else {
              alert("请先选择style名称!");
            }
          } else {
            alert("请先双击选择实例元素!");
          }
        }
      };
      /**
       * @功能描述: 改变css内容的事件
       * @参数:
       * @返回值:
       */
      this.contentChange = function() {
        const ls = window.localStorage;
        const selectedElement = ls.getItem("selectedElement");
        if (selectedElement !== "") {
          if ($(this).attr("cssType") !== "") {
            const cssType = $(this).attr("cssType");
            const val = $(this).val();

            // 缓存中设置新的CSS样式
            let oldSiderBarCssObj = JSON.parse(ls.getItem("siderBarCssObj"));
            let oldCssObj = oldSiderBarCssObj[selectedElement] || {};
            let newCssObj = { ...oldCssObj, [cssType]: val };
            ls.setItem(
              "siderBarCssObj",
              JSON.stringify({
                ...oldSiderBarCssObj,
                [selectedElement]: newCssObj
              })
            );
            $(`#${selectedElement}`).css(cssType, val);
          }
        } else {
          alert("请先双击选择实例元素!");
        }
      };
    }

    /**
     * @功能描述: 元素事件构造函数
     * @参数:datePoint:唯一标识
     * @返回值:
     */
    function ElementFunc(datePoint) {
      this.o = {};
      this.ls = window.localStorage;
      // 直线位置对象
      this.lineObj = {};
      // 直线标识名
      this.lineDatePoint = "";

      /**
       * @功能描述: 创造元素
       * @参数: key:唯一标识,type:类型,content:内容,initialStyle:初始样式字符串
       * @返回值: 无
       */
      this.createEle = (key, type, content, initialStyle) => {
        // 生成元素
        const element = document.createElement(type);
        // 生成文字节点
        const contentNode = document.createTextNode(content);
        element.setAttribute("style", initialStyle);
        element.setAttribute("id", key);
        element.setAttribute("class", "createdElement");
        element.appendChild(contentNode);
        $("body").append(element);
      };

      // 点击元素事件
      this.elementMousedown = e => {
        const position = $(`#${datePoint}`).position();
        this.o.initialX = e.clientX;
        this.o.initialY = e.clientY;
        this.o.initialLeft = position.left;
        this.o.initialTop = position.top;
        // 绑定移动事件
        $("body").bind("mousemove", this.bodyMousemove);
      };

      // 点击元素以后鼠标移动
      this.bodyMousemove = e => {
        let mouseLeft = event.clientX;
        let mouseTop = event.clientY;
        let elementLeft = this.o.initialLeft + mouseLeft - this.o.initialX;
        let elementTop = this.o.initialTop + mouseTop - this.o.initialY;
        $(`#${datePoint}`).css({ left: elementLeft, top: elementTop });
        // 移动同时改变input的值
        $(`#left${datePoint}`).val(elementLeft + "px");
        $(`#top${datePoint}`).val(elementTop + "px");
        // 存储left和top的位置
        this.setLsStyle("left", elementLeft + "px");
        this.setLsStyle("top", elementTop + "px");
      };

      // 双击元素事件
      this.elementDblclick = __this => {
        // 保存当前点击对象
        this.ls.setItem("selectedElement", datePoint);
        // 清除原先input
        $(".detailLi").remove();
        // 清除空提示
        $(".emptyBox").hide();
        // localStorage的css初始化
        if (!this.ls.getItem("siderBarCssObj")) {
          this.ls.setItem("siderBarCssObj", JSON.stringify({}));
        }

        // 详情属性列表
        const lsdetailObj = JSON.parse(this.ls.getItem("siderBarCssObj"))[
          datePoint
        ];
        // 默认样式表
        const initialList = [
          "text",
          "color",
          "width",
          "height",
          "background",
          "z-index",
          "border-radius",
          "position",
          "top",
          "left"
        ];

        /**
         * @功能描述: 判断是否没有样式或只有left,top就先用默认样式表
         * @参数:
         * @返回值: boolean(为空true,否则false)
         */
        function withoutPositionEmpty(obj) {
          const copiedObj = shallowCopy(obj);
          delete copiedObj["top"];
          delete copiedObj["left"];
          return JSON.stringify(copiedObj) === "{}";
        }
        // 浅拷贝
        function shallowCopy(src) {
          const dst = {};
          for (let prop in src) {
            if (src.hasOwnProperty(prop)) {
              dst[prop] = src[prop];
            }
          }
          return dst;
        }
        // 如果没有样式或只有left,top就先用默认样式表
        const detailList =
          lsdetailObj && !withoutPositionEmpty(lsdetailObj)
            ? Object.keys(lsdetailObj)
            : initialList;
        // 获得初始化样式
        const initialStyleString = $(__this)[0].target.style.cssText;
        let styleArr = initialStyleString.split(";");
        styleArr = styleArr.map(item => item.split(":"));
        // 批量存储初始化样式
        styleArr.map(([cssType, value]) => {
          if (cssType && value) {
            this.setLsStyle(cssType.trim(), value.trim());
          }
        });
        // 插入详情list元素
        detailList.map((item, idx) => {
          const mixedId = datePoint + idx;
          // 初始样式数组
          const initialArr = styleArr.find(([cssType, value]) => {
            return cssType.trim() === item;
          });
          // 标记left,top的inputId
          const inputId =
            item === "left" || item === "top"
              ? item + datePoint
              : `input${mixedId}`;
          const initialStyle =
            initialArr && initialArr[1] ? initialArr[1].trim() : "";
          const _this = this;
          // 编辑后的样式
          let modifyLi = `<li class="detailLi ${datePoint}"><span>${item}:</span><input cssType=${item} id='${inputId}' value="${
            lsdetailObj &&
            !withoutPositionEmpty(lsdetailObj) &&
            lsdetailObj[item]
              ? lsdetailObj[item]
              : ""
          }"/></li>`;
          // 初始化的样式
          let initialLi = `<li class="detailLi ${datePoint}"><span>${item}:</span><input cssType=${item} id='${inputId}' value="${initialStyle}"/></li>`;
          let detailLi =
            lsdetailObj && !withoutPositionEmpty(lsdetailObj)
              ? modifyLi
              : initialLi;
          $("#siderBar .infoUl").append(detailLi);
          $(`#input${mixedId}`).on("change", function() {
            const cssType = $(this).attr("cssType");
            const val = $(this).val();
            // 缓存中保存样式
            _this.setLsStyle(cssType, val);
          });
        });
      };

      /**
       * @功能描述: 缓存中保存样式
       * @参数: cssType:样式名,val:样式值
       * @返回值:
       */
      this.setLsStyle = (cssType, val) => {
        if (!this.ls.getItem("siderBarCssObj")) {
          this.ls.setItem("siderBarCssObj", JSON.stringify({}));
        }
        // 缓存中设置新的CSS样式
        let oldSiderBarCssObj = JSON.parse(this.ls.getItem("siderBarCssObj"));
        let oldCssObj =
          JSON.parse(this.ls.getItem("siderBarCssObj"))[datePoint] || {};
        let newCssObj = { ...oldCssObj, [cssType]: val };
        this.ls.setItem(
          "siderBarCssObj",
          JSON.stringify({
            ...oldSiderBarCssObj,
            [datePoint]: newCssObj
          })
        );
        if (cssType === "text") {
          $(`#${datePoint}`).text(val);
        } else {
          $(`#${datePoint}`).css(cssType, val);
        }
      };

      // 生成取消画线按钮
      this.cancelDrawLine = () => {
        $("#lineCancelBtn").show();
        // 取消画线事件绑定
        $("#lineCancelBtn").on("click", this.cancelBind);
      };

      // 取消画线事件绑定
      this.cancelBind = () => {
        // 删除按钮初始化
        if ($("#cancelBtn").hasClass("cancelDelete")) {
          $("#cancelBtn")
            .removeClass("cancelDelete")
            .text("删除元素");
          $(".createdElement")
            .unbind("click")
            .css("cursor", "pointer");
          $(".lineConatiner")
            .unbind("click")
            .css("cursor", "pointer");
        }
        $("body").unbind("click");
        $("body").unbind("mousemove");
        $("#lineCancelBtn").hide();
      };

      // 处理线的点击事件函数
      this.handleLineClick = e => {
        this.lineDatePoint = new Date().getTime();
        this.lineObj.x1 = e.clientX;
        this.lineObj.y1 = e.clientY;
        // 加入线容器
        const lineContainer = `<div class="lineConatiner" id="lineConatiner${this.lineDatePoint}"></div>`;
        $("body").append(lineContainer);
        // 画出初始点
        drawDot(this.lineDatePoint, this.lineObj.x1, this.lineObj.y1, "#000");
        $("body").unbind("click");
        // 鼠标移动重新渲染线
        $("body").bind("mousemove", e => {
          this.lineObj.x2 = e.clientX;
          this.lineObj.y2 = e.clientY;
          $(`#lineConatiner${this.lineDatePoint} .line${this.lineDatePoint}`)
            .siblings(0)
            .remove();
          drawLine(
            this.lineDatePoint,
            this.lineObj.x1,
            this.lineObj.y1,
            this.lineObj.x2,
            this.lineObj.y2,
            "#000"
          );
        });
        // 重新绑定点击事件,画终点
        $("body").bind("click", e => {
          $("body").unbind("mousemove");
          this.lineObj.x2 = e.clientX;
          this.lineObj.y2 = e.clientY;
          drawLine(
            this.lineDatePoint,
            this.lineObj.x1,
            this.lineObj.y1,
            this.lineObj.x2,
            this.lineObj.y2,
            "#000"
          );
          $("body").unbind("click");
          // 绑定下一次起点点击事件
          $("body").bind("click", this.handleLineClick);
        });
      };

      /** 生成动态背景遮罩 */
      this.createCover = () => {
        function n(n, e, t) {
          return n.getAttribute(e) || t;
        }
        function e(n) {
          return document.getElementsByTagName(n);
        }
        function t() {
          var t = e("script"),
            o = t.length,
            i = t[o - 1];
          return {
            l: o,
            z: n(i, "zIndex", -1),
            o: n(i, "opacity", 0.5),
            c: n(i, "color", "0,0,0"),
            n: n(i, "count", 99)
          };
        }
        function o() {
          (a = m.width =
            window.innerWidth ||
            document.documentElement.clientWidth ||
            document.body.clientWidth),
            (c = m.height =
              window.innerHeight ||
              document.documentElement.clientHeight ||
              document.body.clientHeight);
        }
        function i() {
          r.clearRect(0, 0, a, c);
          var n, e, t, o, m, l;
          s.forEach(function(i, x) {
            for (
              i.x += i.xa,
                i.y += i.ya,
                i.xa *= i.x > a || i.x < 0 ? -1 : 1,
                i.ya *= i.y > c || i.y < 0 ? -1 : 1,
                r.fillRect(i.x - 0.5, i.y - 0.5, 1, 1),
                e = x + 1;
              e < u.length;
              e++
            )
              (n = u[e]),
                null !== n.x &&
                  null !== n.y &&
                  ((o = i.x - n.x),
                  (m = i.y - n.y),
                  (l = o * o + m * m),
                  l < n.max &&
                    (n === y &&
                      l >= n.max / 2 &&
                      ((i.x -= 0.03 * o), (i.y -= 0.03 * m)),
                    (t = (n.max - l) / n.max),
                    r.beginPath(),
                    (r.lineWidth = t / 2),
                    (r.strokeStyle = "rgba(" + d.c + "," + (t + 0.2) + ")"),
                    r.moveTo(i.x, i.y),
                    r.lineTo(n.x, n.y),
                    r.stroke()));
          }),
            x(i);
        }
        var a,
          c,
          u,
          m = document.createElement("canvas"),
          d = t(),
          l = "c_n" + d.l,
          r = m.getContext("2d"),
          x =
            window.requestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame ||
            window.oRequestAnimationFrame ||
            window.msRequestAnimationFrame ||
            function(n) {
              window.setTimeout(n, 1e3 / 45);
            },
          w = Math.random,
          y = { x: null, y: null, max: 2e4 };
        (m.id = l),
          (m.style.cssText =
            "position:fixed;top:0;left:0;z-index:" + d.z + ";opacity:" + d.o),
          e("body")[0].appendChild(m),
          o(),
          (window.onresize = o),
          (window.onmousemove = function(n) {
            (n = n || window.event), (y.x = n.clientX), (y.y = n.clientY);
          }),
          (window.onmouseout = function() {
            (y.x = null), (y.y = null);
          });
        for (var s = [], f = 0; d.n > f; f++) {
          var h = w() * a,
            g = w() * c,
            v = 2 * w() - 1,
            p = 2 * w() - 1;
          s.push({ x: h, y: g, xa: v, ya: p, max: 6e3 });
        }
        (u = s.concat([y])),
          setTimeout(function() {
            i();
          }, 100);
      };
    }

    /**
     * @功能描述: 初始化事件构造函数
     * @参数:无
     * @返回值:无
     */
    function InitialCom() {
      this.ls = window.localStorage;
      this.hrefFlag = false;
      this.appendInputLi = () => {
        let inputLi = `<li class="detailLi ${datePoint}"><input id='cssType${datePoint}'/><div>:</div><input cssType='' id='input${datePoint}'/></li>`;
        $("#siderBar .infoUl").append(inputLi);
      };
      // 侧边栏div
      this.siderBarHtml = `
      <div id="siderBar">
        <div class="myelement-container">
          <div class="tabsBtnWrap">
            <div class="tabsBtn active">元素</div>
            <div class="tabsBtn">属性</div>
          </div>
          <div class="siderBarUlWrap">
            <ul class="siderBarUl">
              <div class="fillFormWrap">
                <div class="fillFormInputWrap">
                  <li class="fillFormLi">
                    <input id="fillFormName" class="fillFormName" placeholder="请输入key值"/>
                    <div class="fillFormColon">:</div>
                    <input id="fillFormInput" class="fillFormInput" placeholder="请输入value值"/>
                  </li>
                </div>
                <textArea id="stringTextarea" autofocus placeholder="请输入JSON字符串,也可通过上方输入框自动生成,通过更改input可添加新的键值对"/>
              </div>
               <div class="pythonWrap">
                <div class="fillFormInputWrap">
                  <select id="pythonType">
                    <option value="">请选择python字段类型</option>
                    <option value="cardItem">cardItem</option>
                    <option value="cardInfo">cardInfo</option>
                    <option value="page">page</option>
                    <option value="pageInfo">pageInfo</option>
                  </select>
                  <div class="pythonUl" style="display:block">
                  </div>
                  <div class="pythonEmptyBox">
                    当前python为空,请先选择python字段类型,再点击新建,选择对应dom元素!
                  </div>
                  <div class="addPythonBtn">+</div>
                </div>
              </div>
              <div class="funcBtnWrap" style="bottom:54px">
                <div id="downloadBtn" class="downloadBtn">下载样式</div>
                <div id="cancelBtn" class="cancelBtn">删除元素</div>
                
                <div id="pythonDownload" class="downloadBtn"">下载Python</div>
                <div id="clearBtn" class="cancelBtn">清空Python</div>

                
                <div id="lineCancelBtn" class="cancelBtn">点击取消画线</div>
              </div>
              <div class="funcBtnWrap" style="bottom:28px">
                <div id="fillFormBtn" class="fillFormBtn">一键填充表单</div>
                <div id="inspectCurrentPython" class="downloadBtn">查看Python</div>
                <div id="sendPython" class="downloadBtn">发送Python</div>
              </div> 
              <div class="funcBtnWrap">
                <div id="hrefBtn" class="fillFormBtn">禁用跳转</div>
                <div id="inspectBtn" class="fillFormBtn">查看元素</div>
              </div>              
            </ul>
            <ul class="infoUl">
              <div class="emptyBox">
                当前属性为空,请先新建实例元素,再双击选择添加属性!
              </div>
              <div class="addInfoBtn">+</div>
            </ul>
          </div>
        </div>
			</div>`;

      // 收缩弹出按钮
      this.collapseBtn = $(`<div class="collapseBtn"><</div>`);

      this.initialAll = () => {
        // 清除上一个页面缓存
        this.ls.removeItem("siderBarCssObj");
        this.ls.removeItem("selectedElement");
        // 插入siderBar
        $("body").append(this.siderBarHtml);
        // 插入list元素
        this.appendList();
        // 插入收缩弹出按钮
        this.appendCollapseBtn();
        // 导入CSS
        this.importCss();
        // 初始关闭状态
        this.toggleCollapse();
      };

      /**
       * @功能描述: 启用/禁用跳转
       * @参数:
       * @返回值:
       */
      this.hrefToggle = () => {
        const _this = this;
        // 若为禁用跳转
        if (this.hrefFlag) {
          this.hrefFlag = false;
          $("#hrefBtn").text("禁用跳转");
        } else {
          this.hrefFlag = true;
          $("#hrefBtn").text("启用跳转");
        }
        $("a").click(function(e) {
          if (!_this.hrefFlag) {
            e.returnValue = true;
          } else {
            e.preventDefault();
          }
        });
      };

      /**
       * @功能描述:点击下载按钮
       * @参数:
       * @返回值:
       */
      this.downloadBtnClick = () => {
        const datePoint = longDateFormate(new Date());
        const contentObj = this.ls.getItem("siderBarCssObj")
          ? JSON.parse(this.ls.getItem("siderBarCssObj"))
          : "";
        const transContentObj = Object.keys(contentObj).map((item, idx) => {
          const cssName = `createdAt:${shortDateFormate(
            new Date(JSON.parse(item))
          )},itemOrder:${idx + 1}`;
          const cssContent = jsonToCss(contentObj[item]);
          return {
            [cssName]: cssContent
          };
        });
        const content = JSON.stringify(transContentObj);
        const fileName = `${datePoint}下载的样式`;
        this.createHref(fileName, content);
      };

      /**
       * @功能描述:点击python下载按钮
       * @参数:
       * @返回值:
       */
      this.pythonDownloadClick = () => {
        const datePoint = longDateFormate(new Date());
        const contentObj = this.ls.getItem("pythonObj")
          ? this.ls.getItem("pythonObj")
          : "";
        const fileName = `${datePoint}下载的python`;
        this.createHref(fileName, contentObj);
      };

      /**
       * @功能描述:  创建下载文件的链接
       * @参数: fileName(string) 文件名 content(string)文件内容
       * @返回值:
       */
      this.createHref = (fileName, content) => {
        // 创建a标签
        const aTag = document.createElement("a");
        // 创建blob对象并给他内容
        const blob = new Blob([content]);
        // 给a标签下载文件名
        aTag.download = fileName;
        // 给a标签一个由URL方法转换blob来的下载链接
        aTag.href = URL.createObjectURL(blob);
        // 模拟点击
        aTag.click();
        // 注销blob的URL
        URL.revokeObjectURL(blob);
      };

      /**
       * @功能描述:点击删除按钮
       * @参数:
       * @返回值:
       */
      this.deleteBtnclick = _this => {
        const __this = this;
        $(_this)
          .addClass("cancelDelete")
          .text("退出删除");
        $(".createdElement")
          .bind("click", function() {
            const deletedId = $(this).attr("id");
            // 缓存中设置删除对应的CSS样式
            let oldSiderBarCssObj = JSON.parse(
              __this.ls.getItem("siderBarCssObj")
            );
            delete oldSiderBarCssObj[deletedId];
            __this.ls.setItem(
              "siderBarCssObj",
              JSON.stringify({
                ...oldSiderBarCssObj
              })
            );
            $(this).remove();
          })
          .css("cursor", "crosshair");
        $(".lineConatiner")
          .bind("click", function() {
            $(this).remove();
          })
          .css("cursor", "crosshair");
      };

      /**
       * @功能描述:点击退出删除按钮
       * @参数:
       * @返回值:
       */
      this.cancelBtnClick = _this => {
        $(_this)
          .removeClass("cancelDelete")
          .text("删除元素");
        $(".createdElement")
          .unbind("click")
          .css("cursor", "pointer");
        $(".lineConatiner")
          .unbind("click")
          .css("cursor", "pointer");
      };

      /**
       * @功能描述: 点击一键表单填充按钮
       * @参数:
       * @返回值:
       */
      this.fillForm = () => {
        const _this = this;
        $("#fillFormBtn")
          .text("退出表单填充模式")
          .click(function() {
            $("#fillFormBtn").text("一键填充表单");
            $(".elementListLi").show();
            $(".fillFormWrap").hide();
            $(this).click(function() {
              _this.fillForm();
            });
          });
        $(".elementListLi").hide();
        $(".fillFormWrap").show();
        $("#stringTextarea").change(function() {
          _this.handleFillForm();
        });
      };

      /**
       * @功能描述: 一键表单填充功能
       * @参数:
       * @返回值:
       */
      this.handleFillForm = () => {
        try {
          const jsonObj = JSON.parse($("#stringTextarea").val());
          Object.entries(jsonObj).map(item => {
            const [name, val] = item;
            $(`#${name}`).val(val);
          });
        } catch (err) {
          alert("请先输入正确格式的JSON字符串!\n错误信息:" + err);
          return;
        }
      };

      this.handleFillInputChange = () => {
        // 若原JSON输入框内容为空,先初始化为{}
        if ($("#stringTextarea").val() === "") {
          $("#stringTextarea").val("{}");
        }
        try {
          // 原有string
          const originString = $("#stringTextarea").val();
          // 新key
          const attrName = $("#fillFormName").val();
          // 新val
          const attrVal = $("#fillFormInput").val();
          // 原有JSON
          const originJson = JSON.parse(originString);
          // 新增键值对或改掉原来的值
          originJson[attrName] = attrVal;
          const modifiedString = JSON.stringify(originJson);
          $("#stringTextarea").val(modifiedString);
          // 校验并填入表单的值
          this.handleFillForm();
        } catch (err) {
          alert("请先输入正确格式的JSON字符串!\n错误信息:" + err);
          return;
        }
      };

      /**
       * @功能描述:切换Tabs
       * @参数: index(number)
       * @返回值:
       */
      this.tabsSwitchIndex = index => {
        if (
          !$(".tabsBtnWrap .tabsBtn")
            .eq(index)
            .hasClass("active")
        ) {
          $(".tabsBtnWrap .tabsBtn")
            .eq(index)
            .addClass("active")
            .siblings(index)
            .removeClass("active");
          $(".siderBarUlWrap ul")
            .eq(index)
            .show()
            .siblings(index)
            .hide();
        }
      };

      // 插入list元素
      this.appendList = () => {
        myElementsList.map(({ name, initialStyle, type, content }) => {
          let element = `<li class="elementListLi" type=${type} initialStyle=${initialStyle} content=${content}>${name}</li>`;
          $("#siderBar .siderBarUl").append(element);
        });
      };

      // 点击收缩按钮事件
      this.toggleCollapse = () => {
        // 若已经折叠,则打开
        if ($(".collapseBtn").hasClass("collapsed")) {
          $(".collapseBtn").removeClass("collapsed");
          $(".collapseBtn").text("<");
          $("#siderBar").css({
            left: "0px",
            "min-height": "200px",
            height: "auto"
          });
          $(".tabsBtnWrap").show();
          $(".siderBarUlWrap").show();
        } else {
          // 若未折叠,则折叠
          $(".collapseBtn").addClass("collapsed");
          $(".collapseBtn").text(">");
          $("#siderBar").css({
            left: "-184px",
            "min-height": "unset",
            height: "40px"
          });
          $(".tabsBtnWrap").hide();
          $(".siderBarUlWrap").hide();
        }
      };

      // 插入收缩弹出按钮
      this.appendCollapseBtn = () => {
        $("#siderBar").append(this.collapseBtn);
        $(".collapseBtn").click(() => {
          this.toggleCollapse();
        });
      };

      // 导入css样式
      this.importCss = () => {
        const cssString = `
      .detailLi input{width: 100%;}
      #clearBtn,#inspectCurrentPython,#sendPython,#pythonDownload{display: none;}
      #pythonType{margin-bottom: 10px;outline: none;height: 22px;}
      .fillFormLabel{text-align: left;margin-bottom: 8px;}
      .siderBarUlWrap{display:flex;}
      .myelement-container{width: 90%;}
      .selectBtn{width: 100%;background: #0189fb;color: #fff;cursor: pointer;border-radius: 4px;outline: none;border: 0;}
      #lineCancelBtn{display:none;z-index:10;position: absolute;width: 96%;}
      .fillFormWrap{display:none;width: 100%;}
      .pythonWrap{display:none;width: 100%;}
      #stringTextarea{width: 98%;min-height: 68px;font-size: 12px;}
      .fillFormInputWrap{display: flex;flex-direction: column;width: 100%;}
      .fillFormLi{height: 22px;margin-bottom: 6px;display: flex;width: 100%;align-items: stretch;}
      .pythonName,.fillFormName,.fillFormInput{width: 100%;outline: none;}
      .fillFormColon{color: #fff;margin: 0 2px;line-height: 20px;}
			.funcBtnWrap{display:flex;position: absolute;bottom: 0px;width: 100%;justify-content:space-between;}
		  .elementListLi:hover{box-shadow: 0px 0px 10px #0189fb;border: 1px solid #0189fb;}
			.collapseBtn{user-select: none;width: 20px;height: 20px;transform: translate(0px, 10px);background: rgba(255,255,255,0.6);text-align: center;border-radius: 10px;color: #333;font-weight: bold;line-height: 20px;cursor: pointer;position: absolute;right: 2px;top: 25%}
			.siderBarUl{width: 100%;position:relative;padding: 0;padding-bottom: 78px;display: flex;flex-direction: column;}
			.detailLi{display:flex;margin-bottom: 10px;border-bottom: 1px solid #eee;padding: 10px 0 5px 0;width: 100%;}
			.detailLi span{margin-right: 5px;color:#fff;text-align: right;white-space: nowrap;}
			.collapsed{top: 50%;transform: translate(0px, -50%);right:8px;}
			.fillFormBtn,.cancelBtn,.downloadBtn{flex:1;border-radius: 5px;font-size: 14px;background: #fff;text-align: center;height: 20px;line-height: 20px;margin: 0px 4px;cursor:pointer;white-space:nowrap;}
			.fillFormBtn:hover,.cancelBtn:hover,.downloadBtn:hover{box-shadow: 0px 0px 10px #0189fb;}
			.emptyBox,.pythonEmptyBox{color:#fff;margin-top: 20px;}
			.addInfoBtn{width: 50px;height: 20px;position: absolute;bottom: 6px;left: 50%;font-size: 20px;transform: translate(-50%, 0px);color: #fff;line-height: 20px;border-radius: 5px;background: #0189fb;text-align: center;cursor:pointer;}
			.addPythonBtn{margin:20px auto;width: 50px;height: 20px;font-size: 20px;color: #fff;line-height: 20px;border-radius: 5px;background: #0189fb;text-align: center;cursor:pointer;}
      .infoUl,.pythonUl{width: 100%;padding: 0;display: flex;flex-direction: column;display:none;padding-bottom: 20px;}
			.elementListLi{background: #fff;list-style: none;height: 20px;line-height: 20px;padding-left: 10%;border-radius: 5px;margin-bottom: 10px;border: 0;cursor:pointer;border: 1px solid #fff;text-align:left;}
			.tabsBtnWrap{display: flex;width: 100%;margin-bottom: 10px;justify-content: space-between;}
			.tabsBtn:hover{box-shadow: 0px 0px 10px #0189fb;border: 1px solid #0189fb;}
			.tabsBtn{color:#0189fb;border: 1px solid #fff;text-align:center;width:48%;flex: 1;background: #fff;border-radius: 5px 5px 0 0;cursor:pointer;}
			.tabsBtnWrap .active{color:#fff;background:#0189fb;border: 1px solid #0189fb;}
			#siderBar{overflow: hidden;flex-direction: column;z-index:1000;background: rgba(0,0,0,0.3);position: fixed;left: 0;top: 10%;width: 200px;padding-bottom: 60px;border-radius: 0 5px 5px 0;padding: 10px;display:flex;transition:all 0.3s ease}`;
        var style = document.createElement("style");
        style.type = "text/css";
        if (style.stylesheet) {
          //IE
          style.stylesheet.cssText = cssString;
        } else {
          //w3c
          style.innerHTML = cssString;
        }
        var heads = document.getElementsByTagName("head");
        if (heads.length) {
          heads[0].appendChild(style);
        } else {
          document.documentElement.appendChild(style);
        }
      };
    }
  });
})();