24 changed files with 703 additions and 157 deletions
			
			
		- 
					14index.html
 - 
					10package.json
 - 
					248pnpm-lock.yaml
 - 
					2public/serverConfig.json
 - 
					4src/layout/components/sidebar/sidebarItem.vue
 - 
					63src/layout/frameView.vue
 - 
					1src/layout/types.ts
 - 
					5src/main.ts
 - 
					11src/router/index.ts
 - 
					9src/router/modules/externalLink.ts
 - 
					7src/router/utils.ts
 - 
					2src/store/modules/multiTags.ts
 - 
					4src/style/index.scss
 - 
					4src/utils/http/index.ts
 - 
					4src/utils/http/types.d.ts
 - 
					3src/utils/is.ts
 - 
					3src/utils/link.ts
 - 
					54src/utils/loaders/index.ts
 - 
					15src/utils/operate/index.ts
 - 
					226src/utils/print.ts
 - 
					35src/utils/resize/index.ts
 - 
					116src/utils/watermark.ts
 - 
					9types/global.d.ts
 - 
					11vite.config.ts
 
@ -0,0 +1,63 @@ | 
				
			|||
<template> | 
				
			|||
  <div class="frame" v-loading="loading"> | 
				
			|||
    <iframe :src="frameSrc" class="frame-iframe" ref="frameRef"></iframe> | 
				
			|||
  </div> | 
				
			|||
</template> | 
				
			|||
<script lang="ts" setup> | 
				
			|||
import { useRoute } from "vue-router"; | 
				
			|||
import { ref, unref, onMounted, nextTick } from "vue"; | 
				
			|||
 | 
				
			|||
const loading = ref(false); | 
				
			|||
const currentRoute = useRoute(); | 
				
			|||
const frameSrc = ref<string>(""); | 
				
			|||
const frameRef = ref<HTMLElement | null>(null); | 
				
			|||
 | 
				
			|||
if (unref(currentRoute.meta)?.frameSrc) { | 
				
			|||
  frameSrc.value = unref(currentRoute.meta)?.frameSrc as string; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
function hideLoading() { | 
				
			|||
  loading.value = false; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
function init() { | 
				
			|||
  nextTick(() => { | 
				
			|||
    const iframe = unref(frameRef); | 
				
			|||
    if (!iframe) return; | 
				
			|||
    const _frame = iframe as any; | 
				
			|||
    if (_frame.attachEvent) { | 
				
			|||
      _frame.attachEvent("onload", () => { | 
				
			|||
        hideLoading(); | 
				
			|||
      }); | 
				
			|||
    } else { | 
				
			|||
      iframe.onload = () => { | 
				
			|||
        hideLoading(); | 
				
			|||
      }; | 
				
			|||
    } | 
				
			|||
  }); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
onMounted(() => { | 
				
			|||
  loading.value = true; | 
				
			|||
  init(); | 
				
			|||
}); | 
				
			|||
</script> | 
				
			|||
 | 
				
			|||
<style lang="scss" scoped> | 
				
			|||
.frame { | 
				
			|||
  height: 100vh; | 
				
			|||
  z-index: 998; | 
				
			|||
 | 
				
			|||
  .frame-iframe { | 
				
			|||
    width: 100%; | 
				
			|||
    height: 100%; | 
				
			|||
    overflow: hidden; | 
				
			|||
    border: 0; | 
				
			|||
    box-sizing: border-box; | 
				
			|||
  } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.main-content { | 
				
			|||
  margin: 0 !important; | 
				
			|||
} | 
				
			|||
</style> | 
				
			|||
@ -0,0 +1,54 @@ | 
				
			|||
interface ProxyLoader { | 
				
			|||
  loadCss(src: string): any; | 
				
			|||
  loadScript(src: string): Promise<any>; | 
				
			|||
  loadScriptConcurrent(src: Array<string>): Promise<any>; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
class loaderProxy implements ProxyLoader { | 
				
			|||
  constructor() {} | 
				
			|||
 | 
				
			|||
  protected scriptLoaderCache: Array<string> = []; | 
				
			|||
 | 
				
			|||
  public loadCss = (src: string): any => { | 
				
			|||
    const element: HTMLLinkElement = document.createElement("link"); | 
				
			|||
    element.rel = "stylesheet"; | 
				
			|||
    element.href = src; | 
				
			|||
    document.body.appendChild(element); | 
				
			|||
  }; | 
				
			|||
 | 
				
			|||
  public loadScript = async (src: string): Promise<any> => { | 
				
			|||
    if (this.scriptLoaderCache.includes(src)) { | 
				
			|||
      return src; | 
				
			|||
    } else { | 
				
			|||
      const element: HTMLScriptElement = document.createElement("script"); | 
				
			|||
      element.src = src; | 
				
			|||
      document.body.appendChild(element); | 
				
			|||
      element.onload = () => { | 
				
			|||
        return this.scriptLoaderCache.push(src); | 
				
			|||
      }; | 
				
			|||
    } | 
				
			|||
  }; | 
				
			|||
 | 
				
			|||
  public loadScriptConcurrent = async ( | 
				
			|||
    srcList: Array<string> | 
				
			|||
  ): Promise<any> => { | 
				
			|||
    if (Array.isArray(srcList)) { | 
				
			|||
      const len: number = srcList.length; | 
				
			|||
      if (len > 0) { | 
				
			|||
        let count = 0; | 
				
			|||
        srcList.map(src => { | 
				
			|||
          if (src) { | 
				
			|||
            this.loadScript(src).then(() => { | 
				
			|||
              count++; | 
				
			|||
              if (count === len) { | 
				
			|||
                return; | 
				
			|||
              } | 
				
			|||
            }); | 
				
			|||
          } | 
				
			|||
        }); | 
				
			|||
      } | 
				
			|||
    } | 
				
			|||
  }; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
export const loader = new loaderProxy(); | 
				
			|||
@ -0,0 +1,226 @@ | 
				
			|||
interface PrintFunction { | 
				
			|||
  extendOptions: Function; | 
				
			|||
  getStyle: Function; | 
				
			|||
  setDomHeight: Function; | 
				
			|||
  toPrint: Function; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
const Print = function (dom, options?: object): PrintFunction { | 
				
			|||
  options = options || {}; | 
				
			|||
  // @ts-expect-error
 | 
				
			|||
  if (!(this instanceof Print)) return new Print(dom, options); | 
				
			|||
  this.conf = { | 
				
			|||
    styleStr: "", | 
				
			|||
    // Elements that need to dynamically get and set the height
 | 
				
			|||
    setDomHeightArr: [], | 
				
			|||
    // Echart dom List
 | 
				
			|||
    echartDomArr: [], | 
				
			|||
    // Callback before printing
 | 
				
			|||
    printBeforeFn: null, | 
				
			|||
    // Callback after printing
 | 
				
			|||
    printDoneCallBack: null | 
				
			|||
  }; | 
				
			|||
  for (const key in this.conf) { | 
				
			|||
    // eslint-disable-next-line no-prototype-builtins
 | 
				
			|||
    if (key && options.hasOwnProperty(key)) { | 
				
			|||
      this.conf[key] = options[key]; | 
				
			|||
    } | 
				
			|||
  } | 
				
			|||
  if (typeof dom === "string") { | 
				
			|||
    this.dom = document.querySelector(dom); | 
				
			|||
  } else { | 
				
			|||
    this.dom = this.isDOM(dom) ? dom : dom.$el; | 
				
			|||
  } | 
				
			|||
  if (this.conf.setDomHeightArr && this.conf.setDomHeightArr.length) { | 
				
			|||
    this.setDomHeight(this.conf.setDomHeightArr); | 
				
			|||
  } | 
				
			|||
  this.init(); | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
Print.prototype = { | 
				
			|||
  /** | 
				
			|||
   * init | 
				
			|||
   */ | 
				
			|||
  init: function (): void { | 
				
			|||
    const content = this.getStyle() + this.getHtml(); | 
				
			|||
    this.writeIframe(content); | 
				
			|||
  }, | 
				
			|||
  /** | 
				
			|||
   * Configuration property extension | 
				
			|||
   * @param {Object} obj | 
				
			|||
   * @param {Object} obj2 | 
				
			|||
   */ | 
				
			|||
  extendOptions: function <T>(obj, obj2: T): T { | 
				
			|||
    for (const k in obj2) { | 
				
			|||
      obj[k] = obj2[k]; | 
				
			|||
    } | 
				
			|||
    return obj; | 
				
			|||
  }, | 
				
			|||
  /** | 
				
			|||
    Copy all styles of the original page | 
				
			|||
  */ | 
				
			|||
  getStyle: function (): string { | 
				
			|||
    let str = ""; | 
				
			|||
    const styles: NodeListOf<Element> = document.querySelectorAll("style,link"); | 
				
			|||
    for (let i = 0; i < styles.length; i++) { | 
				
			|||
      str += styles[i].outerHTML; | 
				
			|||
    } | 
				
			|||
    str += `<style>.no-print{display:none;}${this.conf.styleStr}</style>`; | 
				
			|||
    return str; | 
				
			|||
  }, | 
				
			|||
  // form assignment
 | 
				
			|||
  getHtml: function (): Element { | 
				
			|||
    const inputs = document.querySelectorAll("input"); | 
				
			|||
    const selects = document.querySelectorAll("select"); | 
				
			|||
    const textareas = document.querySelectorAll("textarea"); | 
				
			|||
    for (let k = 0; k < inputs.length; k++) { | 
				
			|||
      if (inputs[k].type == "checkbox" || inputs[k].type == "radio") { | 
				
			|||
        if (inputs[k].checked == true) { | 
				
			|||
          inputs[k].setAttribute("checked", "checked"); | 
				
			|||
        } else { | 
				
			|||
          inputs[k].removeAttribute("checked"); | 
				
			|||
        } | 
				
			|||
      } else if (inputs[k].type == "text") { | 
				
			|||
        inputs[k].setAttribute("value", inputs[k].value); | 
				
			|||
      } else { | 
				
			|||
        inputs[k].setAttribute("value", inputs[k].value); | 
				
			|||
      } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    for (let k2 = 0; k2 < textareas.length; k2++) { | 
				
			|||
      if (textareas[k2].type == "textarea") { | 
				
			|||
        textareas[k2].innerHTML = textareas[k2].value; | 
				
			|||
      } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    for (let k3 = 0; k3 < selects.length; k3++) { | 
				
			|||
      if (selects[k3].type == "select-one") { | 
				
			|||
        const child = selects[k3].children; | 
				
			|||
        for (const i in child) { | 
				
			|||
          if (child[i].tagName == "OPTION") { | 
				
			|||
            // @ts-ignore
 | 
				
			|||
            if (child[i].selected == true) { | 
				
			|||
              child[i].setAttribute("selected", "selected"); | 
				
			|||
            } else { | 
				
			|||
              child[i].removeAttribute("selected"); | 
				
			|||
            } | 
				
			|||
          } | 
				
			|||
        } | 
				
			|||
      } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    return this.dom.outerHTML; | 
				
			|||
  }, | 
				
			|||
  /** | 
				
			|||
    create iframe | 
				
			|||
  */ | 
				
			|||
  writeIframe: function (content) { | 
				
			|||
    let w: Document | Window; | 
				
			|||
    let doc: Document; | 
				
			|||
    const iframe: HTMLIFrameElement = document.createElement("iframe"); | 
				
			|||
    const f: HTMLIFrameElement = document.body.appendChild(iframe); | 
				
			|||
    iframe.id = "myIframe"; | 
				
			|||
    iframe.setAttribute( | 
				
			|||
      "style", | 
				
			|||
      "position:absolute;width:0;height:0;top:-10px;left:-10px;" | 
				
			|||
    ); | 
				
			|||
    // eslint-disable-next-line prefer-const
 | 
				
			|||
    w = f.contentWindow || f.contentDocument; | 
				
			|||
    // eslint-disable-next-line prefer-const
 | 
				
			|||
    doc = f.contentDocument || f.contentWindow.document; | 
				
			|||
    doc.open(); | 
				
			|||
    doc.write(content); | 
				
			|||
    doc.close(); | 
				
			|||
    // eslint-disable-next-line @typescript-eslint/no-this-alias
 | 
				
			|||
    const _this = this; | 
				
			|||
    iframe.onload = function (): void { | 
				
			|||
      // Before popping, callback
 | 
				
			|||
      if (_this.conf.printBeforeFn) { | 
				
			|||
        _this.conf.printBeforeFn({ doc }); | 
				
			|||
      } | 
				
			|||
 | 
				
			|||
      _this.drawEchartImg(doc).then(() => { | 
				
			|||
        _this.toPrint(w); | 
				
			|||
        setTimeout(function () { | 
				
			|||
          document.body.removeChild(iframe); | 
				
			|||
          // After popup, callback
 | 
				
			|||
          if (_this.conf.printDoneCallBack) { | 
				
			|||
            _this.conf.printDoneCallBack(); | 
				
			|||
          } | 
				
			|||
        }, 100); | 
				
			|||
      }); | 
				
			|||
    }; | 
				
			|||
  }, | 
				
			|||
  /** | 
				
			|||
   * echarts printing | 
				
			|||
   * @param {Object} doc iframe window | 
				
			|||
   */ | 
				
			|||
  drawEchartImg(doc): Promise<void> { | 
				
			|||
    return new Promise<void>(resolve => { | 
				
			|||
      if (this.conf.echartDomArr && this.conf.echartDomArr.length > 0) { | 
				
			|||
        this.conf.echartDomArr.forEach(e => { | 
				
			|||
          const dom = doc.querySelector("#" + e.$el.id); | 
				
			|||
          const img = new Image(); | 
				
			|||
          const w = dom.offsetWidth + "px"; | 
				
			|||
          const H = dom.offsetHeight + "px"; | 
				
			|||
 | 
				
			|||
          img.style.width = w; | 
				
			|||
          img.style.height = H; | 
				
			|||
          img.src = e.imgSrc; | 
				
			|||
          dom.innerHTML = ""; | 
				
			|||
          dom.appendChild(img); | 
				
			|||
        }); | 
				
			|||
      } | 
				
			|||
      resolve(); | 
				
			|||
    }); | 
				
			|||
  }, | 
				
			|||
  /** | 
				
			|||
    Print | 
				
			|||
  */ | 
				
			|||
  toPrint: function (frameWindow): void { | 
				
			|||
    try { | 
				
			|||
      setTimeout(function () { | 
				
			|||
        frameWindow.focus(); | 
				
			|||
        try { | 
				
			|||
          if (!frameWindow.document.execCommand("print", false, null)) { | 
				
			|||
            frameWindow.print(); | 
				
			|||
          } | 
				
			|||
        } catch (e) { | 
				
			|||
          frameWindow.print(); | 
				
			|||
        } | 
				
			|||
        frameWindow.close(); | 
				
			|||
      }, 10); | 
				
			|||
    } catch (err) { | 
				
			|||
      console.error(err); | 
				
			|||
    } | 
				
			|||
  }, | 
				
			|||
  isDOM: | 
				
			|||
    typeof HTMLElement === "object" | 
				
			|||
      ? function (obj) { | 
				
			|||
          return obj instanceof HTMLElement; | 
				
			|||
        } | 
				
			|||
      : function (obj) { | 
				
			|||
          return ( | 
				
			|||
            obj && | 
				
			|||
            typeof obj === "object" && | 
				
			|||
            obj.nodeType === 1 && | 
				
			|||
            typeof obj.nodeName === "string" | 
				
			|||
          ); | 
				
			|||
        }, | 
				
			|||
  /** | 
				
			|||
   * Set the height of the specified dom element by getting the existing height of the dom element and setting | 
				
			|||
   * @param {Array} arr | 
				
			|||
   */ | 
				
			|||
  setDomHeight(arr) { | 
				
			|||
    if (arr && arr.length) { | 
				
			|||
      arr.forEach(name => { | 
				
			|||
        const domArr = document.querySelectorAll(name); | 
				
			|||
        domArr.forEach(dom => { | 
				
			|||
          dom.style.height = dom.offsetHeight + "px"; | 
				
			|||
        }); | 
				
			|||
      }); | 
				
			|||
    } | 
				
			|||
  } | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
export default Print; | 
				
			|||
@ -0,0 +1,35 @@ | 
				
			|||
import ResizeObserver from "resize-observer-polyfill"; | 
				
			|||
 | 
				
			|||
const isServer = typeof window === "undefined"; | 
				
			|||
 | 
				
			|||
const resizeHandler = (entries: any[]): void => { | 
				
			|||
  for (const entry of entries) { | 
				
			|||
    const listeners = entry.target.__resizeListeners__ || []; | 
				
			|||
    if (listeners.length) { | 
				
			|||
      listeners.forEach((fn: () => any) => { | 
				
			|||
        fn(); | 
				
			|||
      }); | 
				
			|||
    } | 
				
			|||
  } | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
export const addResizeListener = (element: any, fn: () => any): any => { | 
				
			|||
  if (isServer) return; | 
				
			|||
  if (!element.__resizeListeners__) { | 
				
			|||
    element.__resizeListeners__ = []; | 
				
			|||
    element.__ro__ = new ResizeObserver(resizeHandler); | 
				
			|||
    element.__ro__.observe(element); | 
				
			|||
  } | 
				
			|||
  element.__resizeListeners__.push(fn); | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
export const removeResizeListener = (element: any, fn: () => any): any => { | 
				
			|||
  if (!element || !element.__resizeListeners__) return; | 
				
			|||
  element.__resizeListeners__.splice( | 
				
			|||
    element.__resizeListeners__.indexOf(fn), | 
				
			|||
    1 | 
				
			|||
  ); | 
				
			|||
  if (!element.__resizeListeners__.length) { | 
				
			|||
    element.__ro__.disconnect(); | 
				
			|||
  } | 
				
			|||
}; | 
				
			|||
@ -0,0 +1,116 @@ | 
				
			|||
import { | 
				
			|||
  ref, | 
				
			|||
  Ref, | 
				
			|||
  unref, | 
				
			|||
  shallowRef, | 
				
			|||
  onBeforeUnmount, | 
				
			|||
  getCurrentInstance | 
				
			|||
} from "vue"; | 
				
			|||
import { isDef } from "/@/utils/is"; | 
				
			|||
import { useRafThrottle } from "/@/utils/operate"; | 
				
			|||
import { addResizeListener, removeResizeListener } from "/@/utils/resize"; | 
				
			|||
 | 
				
			|||
const domSymbol = Symbol("watermark-dom"); | 
				
			|||
 | 
				
			|||
type attr = { | 
				
			|||
  font?: string; | 
				
			|||
  fillStyle?: string; | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
export function useWatermark( | 
				
			|||
  appendEl: Ref<HTMLElement | null> = ref(document.body) as Ref<HTMLElement> | 
				
			|||
) { | 
				
			|||
  const func = useRafThrottle(function () { | 
				
			|||
    const el = unref(appendEl); | 
				
			|||
    if (!el) return; | 
				
			|||
    const { clientHeight: height, clientWidth: width } = el; | 
				
			|||
    updateWatermark({ height, width }); | 
				
			|||
  }); | 
				
			|||
  const id = domSymbol.toString(); | 
				
			|||
  const watermarkEl = shallowRef<HTMLElement>(); | 
				
			|||
 | 
				
			|||
  const clear = () => { | 
				
			|||
    const domId = unref(watermarkEl); | 
				
			|||
    watermarkEl.value = undefined; | 
				
			|||
    const el = unref(appendEl); | 
				
			|||
    if (!el) return; | 
				
			|||
    domId && el.removeChild(domId); | 
				
			|||
    removeResizeListener(el, func); | 
				
			|||
  }; | 
				
			|||
 | 
				
			|||
  function createBase64(str: string, attr?: attr) { | 
				
			|||
    const can = document.createElement("canvas"); | 
				
			|||
    const width = 300; | 
				
			|||
    const height = 240; | 
				
			|||
    Object.assign(can, { width, height }); | 
				
			|||
 | 
				
			|||
    const cans = can.getContext("2d"); | 
				
			|||
    if (cans) { | 
				
			|||
      cans.rotate((-20 * Math.PI) / 120); | 
				
			|||
      cans.font = attr?.font ?? "15px Reggae One"; | 
				
			|||
      cans.fillStyle = attr?.fillStyle ?? "rgba(0, 0, 0, 0.15)"; | 
				
			|||
      cans.textAlign = "left"; | 
				
			|||
      cans.textBaseline = "middle"; | 
				
			|||
      cans.fillText(str, width / 20, height); | 
				
			|||
    } | 
				
			|||
    return can.toDataURL("image/png"); | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  function updateWatermark( | 
				
			|||
    options: { | 
				
			|||
      width?: number; | 
				
			|||
      height?: number; | 
				
			|||
      str?: string; | 
				
			|||
      attr?: attr; | 
				
			|||
    } = {} | 
				
			|||
  ) { | 
				
			|||
    const el = unref(watermarkEl); | 
				
			|||
    if (!el) return; | 
				
			|||
    if (isDef(options.width)) { | 
				
			|||
      el.style.width = `${options.width}px`; | 
				
			|||
    } | 
				
			|||
    if (isDef(options.height)) { | 
				
			|||
      el.style.height = `${options.height}px`; | 
				
			|||
    } | 
				
			|||
    if (isDef(options.str)) { | 
				
			|||
      el.style.background = `url(${createBase64( | 
				
			|||
        options.str, | 
				
			|||
        options.attr | 
				
			|||
      )}) left top repeat`;
 | 
				
			|||
    } | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  const createWatermark = (str: string, attr?: attr) => { | 
				
			|||
    if (unref(watermarkEl)) { | 
				
			|||
      updateWatermark({ str, attr }); | 
				
			|||
      return id; | 
				
			|||
    } | 
				
			|||
    const div = document.createElement("div"); | 
				
			|||
    watermarkEl.value = div; | 
				
			|||
    div.id = id; | 
				
			|||
    div.style.pointerEvents = "none"; | 
				
			|||
    div.style.top = "0px"; | 
				
			|||
    div.style.left = "0px"; | 
				
			|||
    div.style.position = "absolute"; | 
				
			|||
    div.style.zIndex = "100000"; | 
				
			|||
    const el = unref(appendEl); | 
				
			|||
    if (!el) return id; | 
				
			|||
    const { clientHeight: height, clientWidth: width } = el; | 
				
			|||
    updateWatermark({ str, width, height, attr }); | 
				
			|||
    el.appendChild(div); | 
				
			|||
    return id; | 
				
			|||
  }; | 
				
			|||
 | 
				
			|||
  function setWatermark(str: string, attr?: attr) { | 
				
			|||
    createWatermark(str, attr); | 
				
			|||
    addResizeListener(document.documentElement, func); | 
				
			|||
    const instance = getCurrentInstance(); | 
				
			|||
    if (instance) { | 
				
			|||
      onBeforeUnmount(() => { | 
				
			|||
        clear(); | 
				
			|||
      }); | 
				
			|||
    } | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  return { setWatermark, clear }; | 
				
			|||
} | 
				
			|||
						Write
						Preview
					
					
					Loading…
					
					Cancel
						Save
					
		Reference in new issue