var base_function = {
  data: () => {
    return {
      Version: "0.0.1",
      ActionUrl: {
        login: "./action/login.php",
        logout: "./action/logout.php",
        alarm: "./action/alarm.php",
        hmitag: "./action/HMI_Tag_RW.php",
        webcfg: "./action/webSettings.php",
        webapi: "./action/apiRequest.php",
      },
      // 登入相關設置
      LoginSetup: {
        // 必需
        required: true,
        // 登入狀態
        status: false,
        // 定時檢查 (min)
        chkSec: 1,
        // 定時器
        timer: null,
        // 使用者ID
        uid: "",
        user_name: "",
        // 權限等級
        priv_no: null,
        // 啟用轉址
        // is_reback: false,
        roles: [], // 角色
        menus: [], // 選單
      },
      // 選單相關設置
      MenuSetup: {
        // 是否展開
        opened: true,
        // 頁面連結
        pages: [
          {
            icon: "home",
            name: "Home",
            href: "index.php",
            visibled: true,
            opened: false,
            tagDef: [],
            chart: [],
            MQTT: [],
            subs: [],
          },
        ],
        // 頁面所在
        active: "",
      },
      // 頁面功能
      PageMethodBtns: [],
      // 即時警報
      RealAlarm: {
        Enable: false,
        Display: false,
        ManuShow: false,
        Audio: new Audio("./audio/src/alarm.mp3"),
        Datas: [],
        Selection: "multiple",
        Selected: [],
        RowsPerPageOptions: [],
        Pagination: {
          page: 1,
          rowsPerPage: 7,
        },
        RowKey: "al_tag",
        Columns: [
          /*{
					name: "Al_Tag",
					field: "al_tag",
					label: "Soft",
					align: "center",
					required: true
				}, */ {
            name: "start_time",
            field: "al_start_time",
            label: "警報時間",
            align: "center",
            style: "width: 246px",
          },
          {
            name: "ack_time",
            field: "al_ack_time",
            label: "確認時間",
            align: "center",
            style: "width: 246px",
          },
          {
            name: "message",
            field: "message",
            label: "訊息",
            align: "center",
          },
        ],
        Timer: null,
        TriggerLoop: 5,
        TriggerTimer: null,
      },
      // 閒置登出相關設置
      IdleSetup: {
        // 閒置時間 (min)
        time: 0,
        // 定時器
        timer: null,
        // 倒數值
        countdown: null,
      },
      // 網頁基本設置
      WebSetup: {
        // 底圖
        mainImg: true,
        // logo
        logoImg: "img/logo.png",
        // logo網址
        homeHref: "index.php",
      },
      // Soft監控啟動
      IsSoftStartup: false,
      // MQTT監控啟動
      IsMQTTStartup: false,
      SoftConfig: {
        host: document.domain,
        port: window.location.protocol == "https:" ? "9999" : "8888",
        useSSL: window.location.protocol == "https:",
        autoConnect: false,
        reconnect: false,
      },
      SoftConnectObj: [new OPCTechs_Soft()],
      MQTTConfig: {
        host: "192.168.1.5",
        port: window.location.protocol == "https:" ? "8884" : "1884",
        topic_base: "Sunware/",
        topic_pots: [],
        client_id: "JS_" + new Date().getTime().toString(),
        use_ssl: window.location.protocol == "https:",
        reconnect: false,
        delay_sec: 10,
        json_value: false,
        qos: 0,
        retained: false,
      },
      //MQTTALMTopic: "SunwareALM/",
      //MQTTALMTopic: "$SYS/broker/messages/#",
      MQTTConnectObj: new OPCTechs_MQTT(),
      MQTTConnectALM: new OPCTechs_MQTT(),
      // Vue準備完成
      IsVueComplete: false,
      IsVueReady: false,
      // 本地快取
      WebStorage: new WebStorageCache({
        storage: "sessionStorage",
        exp: 24 * 3600,
      }),
      // 系統時間
      SystemTime: new Date().pattern("yyyy/MM/dd HH:mm:ss"),
      // Soft監控
      SoftPoints: {},
      SoftTagName: {},
      MQTTPoints: {},
      MQTTALMPoints: { default: "" },
      BrowserInfo: {
        userAgent: navigator.userAgent,
        beforeunload_time: 0,
        gap_time: 0,
      },
    };
  },
  mounted() {
    var self = this;
    // 接受其他頁面的資訊
    window.addEventListener("message", self.receiveMessage, false);
    // 監聽頁面卸載事件
    window.addEventListener("beforeunload", function (e) {
      self.FnPageBeforeunload(e);
    });
    window.addEventListener("unload", async function (e) {
      self.FnPageUnload(e);
    });
    // 系統時間顯示
    setInterval(function () {
      self.SystemTime = new Date().pattern("yyyy/MM/dd HH:mm:ss");
    }, 1000);
    // 檢查登入
    if (this.VarMenuPage != "login") {
      if (self.LoginSetup.required && self.VarIsRequiredLogin) {
        self.FnLoginToken();
      }
    }
    self.RealAlarm.Audio.loop = true;
    // 處理選單
    self.FnMenuActive();
  },
  computed: {
    bodyFontSize() {
      return (
        window
          .getComputedStyle(document.querySelector("body"))
          .fontSize.replace(/[^0-9]/gi, "") || "16"
      );
    },
    menuStatus() {
      return this.MenuSetup.opened ? "more_vert" : "menu";
    },
    menuMain() {
      return this.MenuSetup.active;
    },
    menuSub() {
      var actives = this.MenuSetup.active.split("::");
      return actives.length > 1 ? actives[1] : null;
    },
    menuActives() {
      return this.MenuSetup.active != ""
        ? this.MenuSetup.active.split("::")
        : [0];
    },
    realAlarmShow() {
      return this.RealAlarm.Display == true || this.RealAlarm.ManuShow == true;
    },
    tagDefs() {
      var obj = this.FnGetPageCFGs();
      return obj.tagDef;
    },
    MQTTs() {
      var obj = this.FnGetPageCFGs();
      return obj.MQTT;
    },
    charts() {
      var obj = this.FnGetPageCFGs();
      return obj.chart;
    },
    localConnected() {
      return ["127.0.0.1", "localhost"].includes(document.domain)
        ? true
        : false;
    },
  },
  watch: {
    // 頁面激活完成
    "MenuSetup.active": {
      immediate: true,
      async handler(nv, ov) {
        if (nv) {
          if (nv == "-1") {
            this.FnSetVueReadyTrue();
            return true;
          }
          await this.FnVueInitAgain();
          this.FnSetVueReadyTrue();
        }
      },
    },
    // 登入狀態變更
    "LoginSetup.status": {
      immediate: true,
      handler(nv, ov) {
        if (!this.LoginSetup.required || !this.LoginSetup.chkSec) {
          return;
        }
        var self = this;
        if (nv == true) {
          this.LoginSetup.timer = setInterval(function () {
            self.FnLoginToken();
          }, this.LoginSetup.chkSec * 60000);
          return;
        }
        clearInterval(this.LoginSetup.timer);
      },
    },
    // 即時警報啟用
    "RealAlarm.Enable": {
      immediate: true,
      handler(nv, ov) {
        if (nv == true) {
          this.FnMQTTALMStartup();
        }
      },
    },
    // 即時警報顯示
    "RealAlarm.Display": {
      immediate: true,
      handler(nv, ov) {
        if (typeof ov != "undefined") {
          if (nv == true) {
            this.RealAlarm.Audio.currentTime = 0;
            this.RealAlarm.Audio.play();
          } else {
            this.RealAlarm.Audio.pause();
          }
        }
      },
    },
    // 即時警報顯示
    "RealAlarm.ManuShow": {
      immediate: true,
      handler(nv, ov) {
        if (typeof ov != "undefined") {
          if (nv == true) {
            this.RealAlarmTrigger(true, 5);
          }
        }
      },
    },
    // Soft通訊狀態
    IsSoftStartup: {
      immediate: true,
      handler(nv, ov) {
        if (typeof ov != "undefined") {
          if (nv == true) {
            this.FnSoftStartup();
          } else {
            this.FnSoftShutdown();
          }
        }
      },
    },
    // MQTT通訊狀態
    IsMQTTStartup: {
      immediate: true,
      handler(nv, ov) {
        if (typeof ov != "undefined") {
          if (nv == true) {
            this.FnMQTTStartup();
          } else {
            this.FnMQTTShutdown();
          }
        }
      },
    },
    // 畫面完成
    IsVueComplete: {
      // immediate: true,
      handler(nv, ov) {
        if (nv == true) {
          this.$delete(this.MQTTALMPoints, "default", "");
        }
      },
    },
    IsVueReady: {
      immediate: true,
      async handler(nv, ov) {
        if (typeof ov != "undefined") {
          if (nv == true) {
            this.FnVueReady();
            await delay(1500);
            this.$set(this, "IsVueComplete", true);
          }
        }
      },
    },
    MQTTALMPoints: {
      immediate: true,
      deep: true,
      handler(nv, ov) {
        if (!this.IsVueComplete) {
          return;
        }

        // 處理即時警報內容
        let _datas = Object.values(nv)
          .reduce((prev, al_obj) => {
            if (typeof al_obj == "object") {
              prev.push(al_obj);
            }
            return prev;
          }, [])
          .sort(
            (a, b) =>
              new Date(b.al_start_time).getTime() -
              new Date(a.al_start_time).getTime()
          );

        this.$set(this.RealAlarm, "Datas", _datas);
        // 有未確認內容自動顯示
        const arr = _datas.filter((item) => !item.al_ack_time);
        /*
				if (this.RealAlarm.Enable) {
					this.RealAlarm.AudioPlay = arr.length > 0 ? true : false;
				}
				this.RealAlarm.Display = arr.length > 0 ? true : false;
				*/
      },
    },
  },
  methods: {
    // 頁面關閉，發送登出請求
    FnPageBeforeunload(e) {
      this.BrowserInfo.beforeunload_time = new Date().getTime();
    },
    FnPageUnload(e) {
      this.BrowserInfo.gap_time =
        new Date().getTime() - this.BrowserInfo.beforeunload_time;
      // 系統關閉
      if (this.BrowserInfo.gap_time <= 5 && this.LoginSetup.required) {
        var xhr = new XMLHttpRequest();
        xhr.open("POST", this.ActionUrl.logout, true);
        xhr.setRequestHeader(
          "Content-type",
          "application/x-www-form-urlencoded"
        );
        xhr.send(
          new URLSearchParams({
            jwt: this.WebStorage.get("sunware_token"),
            mode: "close",
          }).toString()
        );
      }
    },
    FnGetPageCFGs() {
      var obj = this.MenuSetup.pages;
      this.menuActives.forEach(function ($i) {
        obj = typeof obj.subs != "undefined" ? obj.subs[$i] : obj[$i];
      });
      return obj;
    },
    // 選單激活設置
    FnMenuActive() {
      var self = this;
      var _url = new URL(location.href);
      var _path = _url.pathname.split("/");
      var _now = strip_extension(basename(_path.slice(-1)[0] || "index.php"));
      // 處理選單
      this.MenuSetup.pages.forEach(function (main, i) {
        main.subs.forEach(function (sub, j) {
          if (strip_extension(basename(sub.href)) == _now) {
            self.MenuSetup.pages[i].opened = true;
            self.MenuSetup.active = i + "::" + j;
          }
        });
        if (strip_extension(basename(main.href)) == _now) {
          self.MenuSetup.active = i.toString();
        }
      });
      // 沒有找到選單則設置-1
      if (this.MenuSetup.active == "") {
        if (_now == "index") {
          this.FnPageSwitch(
            this.MenuSetup.pages[0].subs.length > 0
              ? this.MenuSetup.pages[0].subs[0].href
              : this.MenuSetup.pages[0].href
          );
          return false;
        }
        this.MenuSetup.active = "-1";
      }
    },
    FnSetVueReadyTrue() {
      var self = this;
      setTimeout(function () {
        self.IsVueReady = true;
      }, 200);
    },
    async FnVueInitAgain() {
      // clear first
      this.$set(this, "SoftPoints", {});
      this.$set(this, "SoftTagName", {});
      this.$set(this, "MQTTPoints", {});
      // start init
      var self = this;
      var obj = self.FnGetPageCFGs();
      if (
        obj.tagDef.length > 0 ||
        obj.chart.length > 0 ||
        obj.MQTT.length > 0
      ) {
        // 自動取得設定檔
        await this.FnWebCFGGet({
          tagDef: obj.tagDef.join(","),
          chart: obj.chart.join(","),
          MQTT: obj.MQTT.join(","),
        }).then(function (res) {
          // 設置Soft點位
          obj.tagDef.forEach(function (fn) {
            if (typeof res.data[fn] != "undefined") {
              res.data[fn].forEach(function ($obj) {
                if ($obj.Soft.length > 0) {
                  // 兩組KEY(Soft, Name)接對應同一組Value
                  // Soft point
                  self.$set(self.SoftPoints, $obj.Soft, {
                    OPCTag: $obj.Soft,
                    decimal: $obj.Decimal,
                    value: "0",
                  });
                  // Soft name
                  setTimeout(function () {
                    self.$set(
                      self.SoftTagName,
                      $obj.Name,
                      self.SoftPoints[$obj.Soft]
                    );
                  }, 50);
                }
              });
            }
          });
          // 設置MQTT點位
          obj.MQTT.forEach(function (fn) {
            if (typeof res.data[fn] != "undefined") {
              res.data[fn].forEach(function ($obj) {
                // MQTT point
                self.$set(self.MQTTPoints, $obj.Soft, {
                  decimal: $obj.Decimal,
                  value: "0",
                });
              });
            }
          });
          // 設置圖形
          obj.chart.forEach(function (fn) {
            if (typeof res.data[fn] != "undefined") {
              res.data[fn].forEach(function ($obj) {
                // Soft
                if ($obj.Keyin == "Soft") {
                  $obj.Soft.forEach(function (_Soft, _Index) {
                    self.$set(self.SoftPoints, _Soft, {
                      OPCTag: _Soft,
                      decimal: $obj.Decimal,
                      value: "",
                    });
                  });
                }
                // MQTT
                else if ($obj.Keyin == "MQTT") {
                  $obj.Soft.forEach(function (_Soft, _Index) {
                    self.$set(self.MQTTPoints, _Soft, {
                      decimal: $obj.Decimal,
                      value: "",
                    });
                  });
                }
              });
            }
          });
          // 頁面初始callback
          if (typeof self.fnInitial == "function") {
            self.fnInitial && self.fnInitial(res.data);
          }
        });
      }
    },
    FnVueReady() {
      /*
			if (this.RealAlarm.Enable) {
				this.FnMQTTALMStartup();
			}
			*/
      // 頁面完成callback
      if (typeof this.fnVueReady == "function") {
        this.fnVueReady && this.fnVueReady();
      }
    },
    // 閒置登出函式
    FnIdleReset() {
      // 重置
      if (!this.LoginSetup.required && !this.IdleSetup.time) {
        return false;
      }
      clearInterval(this.IdleSetup.timer);
      this.FnIdleCountdown();
    },
    FnIdleCountdown() {
      // 開始倒數
      this.IdleSetup.countdown = this.IdleSetup.time * 60;
      this.IdleSetup.timer = setInterval(this.FnIdleTimer, 1000);
    },
    FnIdleTimer() {
      // 倒數計時器
      this.IdleSetup.countdown = this.IdleSetup.countdown - 1;
      if (!this.IdleSetup.countdown) {
        this.FnCountdownExpired();
      }
    },
    FnCountdownExpired() {
      // 過期登出
      clearInterval(this.IdleSetup.timer);
      this.FnLogout();
    },
    // 登入/登出相關函式
    FnLogout(mode) {
      // 登出
      var self = this;
      var _objParams = new FormData();
      _objParams.append("jwt", this.WebStorage.get("sunware_token"));
      _objParams.append("mode", mode || "idle");
      // 送出登出
      axios.post(this.ActionUrl.logout, _objParams);
      this.WebStorage.delete("sunware_token");
      // 導回登入頁
      setTimeout(function () {
        self.FnLoginAgain(true);
      }, 200);
    },
    FnLoginAgain(isLogout) {
      // 前往登入頁
      // 正常登出時,不會在登入時導回原頁
      // 只有開啟無登入資訊時,才會嚐試在登入時導回原頁(無權限頁面因無$$is_login_redirect,也不會在登入時導回原頁)
      let loginUrl = "login.php";
      //if (!_.isUndefined(this.VarMenuPage)) {
      if (
        !isLogout &&
        !this.$q.sessionStorage.get.item("$$is_login_redirect")
      ) {
        // 登入時會嚐試導回原本頁面,但若原本頁面無權限,為避免一直重導,在第二次時會導到index.php
        // 取得目前URL, 過濾掉系統名稱,保留其餘字串
        var parsedURL = new URL(window.location.href);
        var currentPath = parsedURL.pathname;
        if (parsedURL.searchParams != "") {
          currentPath += "?" + parsedURL.searchParams;
        }
        var match = currentPath.match(/\/([^\/]+)\//);
        var sysName = "";
        if (match && match.length > 1) {
          sysName = match[1];
        }
        // 取出 currentPath 拿掉 sysName 的其餘字串
        var remainPath = currentPath.replace("/" + sysName + "/", "");
        this.$q.sessionStorage.remove("$$is_login_redirect");
        // 不能檢查置後為 .php 的字串, 因為有些頁面會有參數, 例如: index.php?test=1
        if (
          remainPath != "" &&
          !remainPath.toLowerCase().endsWith("index.php")
        ) {
          this.$q.sessionStorage.set("$$redirect", remainPath); // 因為 ?redirect=xxx.php?yyy=1&bbb=2 會在 login.js中將 redirect=xxx.php?yyy=1, bbb=2 故改用 sessionStorage
          // loginUrl += "?redirect=" + remainPath;
        }
      }
      this.$q.sessionStorage.remove("$$is_login_redirect");
      this.FnPageSwitch(loginUrl);
    },
    async FnLoginToken() {
      // Token檢查
      var self = this;
            var _token = this.WebStorage.get("sunware_token");
      var _datas = _token ? jwt_decode(_token) : {};
            if (!Object.keys(_datas).length) {
        this.LoginSetup.status = false;
        if (this.$t("login.plz_login_again") != "login.plz_login_again") {
          // 有些情況抓不到語系的訊息(通常是未登入連網頁), 以此判斷避過
          await this.FnAlert(
            this.$t("login.plz_login_again"),
            this.$t("login.login_expire")
          );
        }
        /*var _params = {};
				if (this.LoginSetup.is_reback) {
					_params["reback"] = encodeURIComponent(location.href);
				}
				this.FnLoginAgain(_params);*/
        this.FnLoginAgain();
        return false;
      }
      this.LoginSetup.status = true;
      this.LoginSetup.uid = _datas.user_id;
      this.LoginSetup.user_name = _datas.user_name;
      this.LoginSetup.roles = _datas.roles;
      this.LoginSetup.menus = _datas.menus;
      this.LoginSetup.priv_no = _datas.priv_no;
      // 額外導到其它首頁(待開發)
      //if (this.VarMenuPage.toLowerCase() == "index") {
      //location.href = "test.php";
      //return;
      //}
      if (!this.FnChkLoginMenu()) {
        // 檢查目前使用者是否有權限使用本網頁
        await this.FnNotify("Unauthorized", "warning");
        this.FnLogout();
      } else {
        this.$q.sessionStorage.remove("$$is_login_redirect");
      }
    },
    // 切換頁面
    FnPageSwitch(linkto, params, isNewTab) {
      var _arrTmps = [];
      if (typeof params != "undefined" && params != null) {
        Object.keys(params).forEach(function (_k) {
          _arrTmps.push(_k + "=" + params[_k]);
        });
      }
      if (typeof isNewTab == "boolean" && isNewTab) {
        window.open(
          linkto + (_arrTmps.length > 0 ? "?" + _arrTmps.join("&") : ""),
          "_blank"
        );
      } else {
        window.location.href =
          linkto + (_arrTmps.length > 0 ? "?" + _arrTmps.join("&") : "");
      }
    },
    // Axios GET POST add Authorization
    FnWebAPIGet(inputs, isShowLoading) {
      var that = this;
      if (_.isBoolean(isShowLoading) && isShowLoading) {
        if (_.isUndefined(this.$root.VarTmpLoadingMap)) {
          this.$root.VarTmpLoadingMap = {};
        }
        this.$root.VarTmpLoadingMap[inputs["Action"]] = true;
        this.$q.loading.show();
      }
      // 為了供頁面專屬WebAPI使用, 呼叫此函式時也可自行指令menu_page
      if (this.VarMenuPage && _.isUndefined(inputs["menu_page"])) {
        inputs["menu_page"] = this.VarMenuPage;
      }
      return axios
        .get(this.ActionUrl.webapi, {
          headers: {
            Authorization: "Bearer " + this.WebStorage.get("sunware_token"),
          },
          params: inputs,
        })
        .catch(function (err) {
          that.FnNotify(that.$t("sys.message.connect_error"), "error");
          that.FnLogout();
        })
        .finally(() => {
          if (_.isBoolean(isShowLoading) && isShowLoading) {
            this.$root.VarTmpLoadingMap = _.omit(
              this.$root.VarTmpLoadingMap,
              inputs["Action"]
            );
            if (_.isEmpty(this.$root.VarTmpLoadingMap)) {
              this.$q.loading.hide();
            }
          }
        });
    },
    FnWebAPIPost(inputs, isShowLoading) {
      // 讓此函式支援 FormData & object
      /*
				上傳檔案的使用範例
				=== html ===
				<input type="file" @change="hander1Files" multiple />
				=== vue 將 file input 值寫入 vue data ===
				file1Hander (event) {
					this.files1 = event.target.files;
				}
				file2Hander (event) {
					this.files2 = event.target.files;
				}
				=== ajax 呼叫 ===
				await this.FnWebAPIPost({
					"Action": "Test",
					"Datas": { "欄位1": 1, "欄位2": "abc", "欄位3": [1, 2, 3] },
					"Files": { "檔案欄位1": this.files1,"檔案欄位2": this.files2 }
				}).then((res) => {
					console.log(res);
				});
				=== PHP 端使用 ===
				$_FILES["檔案欄位1"], $_FILES["檔案欄位2"]
			*/
      if (_.isBoolean(isShowLoading) && isShowLoading) {
        if (_.isUndefined(this.$root.VarTmpLoadingMap)) {
          this.$root.VarTmpLoadingMap = {};
        }
        this.$root.VarTmpLoadingMap[inputs["Action"]] = true;
        this.$q.loading.show();
      }
      let formData = new FormData();
      if (inputs instanceof FormData) {
        formData = inputs;
      } else if (typeof inputs == "object") {
        formData.append("Action", inputs["Action"]);
        formData.append("Datas", JSON.stringify(inputs["Datas"]));
        if (!_.isUndefined(inputs["menu_page"])) {
          formData.append("menu_page", inputs["menu_page"]);
        }
        if (!_.isUndefined(inputs["Files"])) {
          _.each(inputs["Files"], (fs, n) => {
            //for (let i = 0; i < this.files.length; i++) {
            for (let i = 0; i < fs.length; i++) {
              formData.append(n + "[]", fs[i]);
            }
          });
        }
      } else {
        console.log("FnWebAPIPost(only FormData or object)");
        return;
      }
      // 為了供頁面專屬WebAPI使用, 呼叫此函式時也可自行指令menu_page
      if (this.VarMenuPage && !formData.has("menu_page")) {
        formData.append("menu_page", this.VarMenuPage);
      }
      return axios
        .post(this.ActionUrl.webapi, formData, {
          headers: {
            Authorization: "Bearer " + this.WebStorage.get("sunware_token"),
            "Content-Type": "multipart/form-data",
          },
        })
        .catch(function (err) {
          that.FnNotify(that.$t("sys.message.connect_error"), "error");
          that.FnLogout();
        })
        .finally(() => {
          if (_.isBoolean(isShowLoading) && isShowLoading) {
            this.$root.VarTmpLoadingMap = _.omit(
              this.$root.VarTmpLoadingMap,
              inputs["Action"]
            );
            if (_.isEmpty(this.$root.VarTmpLoadingMap)) {
              this.$q.loading.hide();
            }
          }
        });
    },
    FnWebAPIDownload(inputs, fileName, isShowLoading) {
      // 下載檔案
      /*
				usage:
					[js]
						Download () {
							this.FnWebAPIDownload({ Action: "Test", Datas: { "no": 1 } }, "test.txt");
						}
					[php]
						public function DownloadTest($params) // 函式名前置需為DownloadXXX()
						{
							return 'uploads/params.txt'; // 直接回傳要下載檔案的路徑,以apiRequest.php為起始目錄
						}
			*/
      if (_.isBoolean(isShowLoading) && isShowLoading) {
        if (_.isUndefined(this.$root.VarTmpLoadingMap)) {
          this.$root.VarTmpLoadingMap = {};
        }
        this.$root.VarTmpLoadingMap[inputs["Action"]] = true;
        this.$q.loading.show();
      }
      let formData = new FormData();
      if (inputs instanceof FormData) {
        formData = inputs;
      } else if (typeof inputs == "object") {
        formData.append("Action", inputs["Action"]);
        formData.append("Datas", JSON.stringify(inputs["Datas"]));
        if (!_.isUndefined(inputs["menu_page"])) {
          formData.append("menu_page", inputs["menu_page"]);
        }
        formData.append("is_download", true);
      } else {
        console.log("FnWebAPIDownload(only FormData or object)");
        return;
      }
      // 為了供頁面專屬WebAPI使用, 呼叫此函式時也可自行指令menu_page
      if (this.VarMenuPage && !formData.has("menu_page")) {
        formData.append("menu_page", this.VarMenuPage);
      }
      return axios
        .post(this.ActionUrl.webapi, formData, {
          responseType: "blob",
          headers: {
            Authorization: "Bearer " + this.WebStorage.get("sunware_token"),
            "Content-Type": "application/x-www-form-urlencoded",
          },
        })
        .then((response) => {
          // 創建一個 Blob 物件
          const blob = new Blob([response.data], {
            type: response.headers["content-type"],
          });
          // 創建一個下戴鏈結並模擬點擊下載
          const link = document.createElement("a");
          link.href = URL.createObjectURL(blob);
          if (!_.isString(fileName)) {
            fileName = "temp";
          }
          link.download = fileName;
          link.click();
        })
        .catch((err) => {
          console.log(err.message);
          this.FnNotify(this.$t("sys.message.download_error"), "error");
        })
        .finally(() => {
          if (_.isBoolean(isShowLoading) && isShowLoading) {
            this.$root.VarTmpLoadingMap = _.omit(
              this.$root.VarTmpLoadingMap,
              inputs["Action"]
            );
            if (_.isEmpty(this.$root.VarTmpLoadingMap)) {
              this.$q.loading.hide();
            }
          }
        });
    },
    FnWebAPIImage(inputs, selfData, attrName, idx, isShowLoading) {
      // 下載圖片並回傳圖片內容
      /*
				usage:
					[html & js]
						// 需將圖片內容傳入一個變數, 如下
						<img :src="imgUrlList[0]" alt="Image" width="300" height="200">
						<img :src="imgUrlList[1]" alt="Image" width="300" height="200">
						<img :src="imgUrl" alt="Image" width="300" height="200">
						{
							data () {
								return {
									"imgUrlList": [],
									"imgUrl": "",
								}
							},
							mounted () {
								this.imgUrlList = [];
								this.FnWebAPIImage({ Action: 'Image', Datas: { 'img': "img1.png" } }, this.$data, "imgUrlList", 0); // 使用陣列
								this.FnWebAPIImage({ Action: 'Image', Datas: { 'img': "main.png" } }, this.$data, "imgUrlList", 1);
								this.FnWebAPIImage({ Action: 'Image', Datas: { 'img': "sun.ico" } }, this.$data, "imgUrl"); // 使用字串
							}
						}
					[php]
						public function DownloadImage($params) // 函式名前置需為DownloadXXX()
						{
							return '../img/main.png'; // 直接回傳要下載圖片路徑,以apiRequest.php為起始目錄
						}
			*/
      if (_.isBoolean(isShowLoading) && isShowLoading) {
        if (_.isUndefined(this.$root.VarTmpLoadingMap)) {
          this.$root.VarTmpLoadingMap = {};
        }
        this.$root.VarTmpLoadingMap[inputs["Action"]] = true;
        this.$q.loading.show();
      }
      let formData = new FormData();
      if (inputs instanceof FormData) {
        formData = inputs;
      } else if (typeof inputs == "object") {
        formData.append("Action", inputs["Action"]);
        formData.append("Datas", JSON.stringify(inputs["Datas"]));
        if (!_.isUndefined(inputs["menu_page"])) {
          formData.append("menu_page", inputs["menu_page"]);
        }
        formData.append("is_download", true);
      } else {
        console.log("FnWebAPIImage(only FormData or object)");
        return;
      }
      // 為了供頁面專屬WebAPI使用, 呼叫此函式時也可自行指令menu_page
      if (this.VarMenuPage && !formData.has("menu_page")) {
        formData.append("menu_page", this.VarMenuPage);
      }
      return axios
        .post(this.ActionUrl.webapi, formData, {
          responseType: "blob",
          headers: {
            Authorization: "Bearer " + this.WebStorage.get("sunware_token"),
            "Content-Type": "application/x-www-form-urlencoded",
          },
        })
        .then((response) => {
          // 創建一個 Blob 物件
          const blob = new Blob([response.data], {
            type: response.headers["content-type"],
          });
          if (_.isUndefined(idx)) {
            this.$set(selfData, attrName, URL.createObjectURL(blob));
          } else {
            this.$set(selfData[attrName], idx, URL.createObjectURL(blob));
          }
        })
        .finally(() => {
          if (_.isBoolean(isShowLoading) && isShowLoading) {
            this.$root.VarTmpLoadingMap = _.omit(
              this.$root.VarTmpLoadingMap,
              inputs["Action"]
            );
            if (_.isEmpty(this.$root.VarTmpLoadingMap)) {
              this.$q.loading.hide();
            }
          }
        });
    },
    FnWebCFGGet(inputs) {
      return axios
        .get(this.ActionUrl.webcfg, {
          params: inputs,
        })
        .catch(function (err) {
          console.log(err.message);
        });
    },
    // Soft連線想關函式
    FnSoftStartup() {
      // 啟動
      var self = this;
      var pots = JSON.parse(JSON.stringify(this.SoftPoints));
      if (!Object.keys(pots).length) {
        console.log("SoftPoints Is Empty.");
        return false;
      }
      // 連線通訊點位最多大值999 超過則進行切割
      var subPots = self.SoftConnectObj[0].Slice(pots, 999);
      for (var intIdx in subPots) {
        if (isNaN(intIdx)) {
          continue;
        }
        if (intIdx > 0) {
          self.SoftConnectObj.push(new OPCTechs_Soft());
        }
        // 設置參數
        self.SoftConnectObj[intIdx].Options(self.SoftConfig);
        // 設置收值回調
        self.SoftConnectObj[intIdx].SetFnArrived(function (rtn) {
          self.FnDataArrived(rtn);
        });
        // 設置成功回調
        self.SoftConnectObj[intIdx].SetFnSucc(function (id) {
          if (typeof self.fnSoftStartupSucc == "function") {
            self.fnSoftStartupSucc && self.fnSoftStartupSucc(id);
          }
        });
        // 設置失敗回調
        self.SoftConnectObj[intIdx].SetFnFail(function (rtn) {
          if (typeof self.fnSoftStartupFail == "function") {
            self.fnSoftStartupFail && self.fnSoftStartupFail(err);
          }
        });
        // 啟動連線
        self.SoftConnectObj[intIdx].Startup(
          self.SoftConnectObj[0].Stringify(
            Object.assign(
              {
                action: "R",
              },
              subPots[intIdx]
            )
          )
        );
      }
    },
    FnSoftShutdown() {
      // 關閉
      for (var intIdx in this.SoftConnectObj) {
        if (isNaN(intIdx)) {
          continue;
        }
        this.SoftConnectObj[intIdx].Shutdown();
      }
      this.SoftConnectObj.length = 1;
    },
    FnDataArrived(Datas) {
      // 接受數據
      var _upds = {};
      for (var point in Datas) {
        if (this.SoftPoints[point]) {
          this.$set(this.SoftPoints[point], "value", Datas[point].value);
        }
        _upds[point] = Datas[point].value;
      }
      // callback to page_function
      if (typeof this.fnSoftPointUpdate == "function") {
        this.fnSoftPointUpdate && this.fnSoftPointUpdate(_upds);
      }
    },
    FnSoftCommand(Points, IsWrite, Callback) {
      /*// 棄用--不透過node_to_soft寫入
			if (!Object.keys(Points).length) {
				console.log("Points Is Empty.");
				return false;
			}
			// 預設是讀取
			IsWrite = typeof IsWrite == "boolean" ? IsWrite : false;
			this.SoftConnectObj[0].Command(this.SoftConnectObj[0].Stringify(Object.assign({ 
				"action" : IsWrite ? "W" : "R" 
			}, Points)), function (){
				if (typeof Callback == "function") {
					Callback && Callback();
				}
			});
			*/
      this.FnSoftAjaxCommand(Points, IsWrite).then(function (res) {
        Callback && Callback(res);
      });
    },
    FnSoftAjaxCommand(Points, IsWrite) {
      if (!Object.keys(Points).length) {
        console.log("Points Is Empty.");
        return false;
      }
      // 預設是讀取
      IsWrite = typeof IsWrite == "boolean" ? IsWrite : false;
      return axios.get(this.ActionUrl.hmitag, {
        params: {
          tag_cmd: this.SoftConnectObj[0].Stringify(
            Object.assign({ action: IsWrite ? "W" : "R" }, Points)
          ),
        },
      });
    },
    FnSoftValueModify(Points) {
      // for components.soft_item
      this.FnSoftCommand(Points, true);
    },
    // MQTT連線相關函式
    FnMQTTStartup() {
      var self = this;
      if (!Object.keys(self.MQTTPoints).length) {
        console.log("MQTTPoints Is Empty.");
        return false;
      }
      // 參數設置
      self.MQTTConnectObj.Options(
        Object.assign({}, self.MQTTConfig, {
          topic_pots: Object.keys(self.MQTTPoints),
        })
      );
      // 設置收值回調
      self.MQTTConnectObj.SetFnArrived(function (nodeValue, nodeName) {
        self.FnMQTTDataArrived(nodeValue, nodeName);
      });
      // 設置成功回調
      self.MQTTConnectObj.SetFnSucc(function () {
        if (typeof self.fnMQTTStartupSucc == "function") {
          self.fnMQTTStartupSucc && self.fnMQTTStartupSucc();
        }
      });
      // 設置失敗回調
      self.MQTTConnectObj.SetFnFail(function (err) {
        if (typeof self.fnSoftStartupFail == "function") {
          self.fnMQTTStartupFail && self.fnMQTTStartupFail(err);
        }
      });
      // 啟用通訊
      self.MQTTConnectObj.Startup();
    },
    FnMQTTShutdown() {
      this.MQTTConnectObj.Shutdown();
    },
    // 警報啟用
    FnMQTTALMStartup() {
      var self = this;
      // 參數設置
      self.MQTTConnectALM.Options(
        Object.assign({}, self.MQTTConfig, {
          client_id: self.MQTTConfig.client_id.replace("JS_", "ALM_"),
          topic_base: this.MQTTALMTopic,
          json_value: true,
        })
      );
      // 設置收值回調
      self.MQTTConnectALM.SetFnArrived(function (nodeValue, nodeName) {
        self.FnMQTTDataArrived(nodeValue, nodeName, true);
      });
      // 啟用通訊
      self.MQTTConnectALM.Startup();
    },
    FnMQTTALMShutdown() {
      this.MQTTConnectALM.Shutdown();
    },
    FnMQTTDataArrived(nodeValue, nodeName, isALM) {
      if (isALM) {
        /*
				this.$set(this.MQTTALMPoints, nodeName, nodeValue);
				clearTimeout(this.RealAlarm.Timer);
				clearTimeout(this.RealAlarm.TriggerTimer);
				var self = this;
				this.RealAlarm.Timer = setTimeout(function () {
					self.RealAlarmTrigger(true, self.RealAlarm.TriggerLoop);
				}, 100);
				return;
				*/
        if (
          !nodeValue.al_start_time ||
          !nodeValue.al_start_time.length ||
          nodeValue.al_norm_time
        ) {
          this.$delete(this.MQTTALMPoints, nodeName);
          return;
        }
        if (!this.IsVueComplete) {
          this.MQTTALMPoints[nodeName] = Object.assign({}, nodeValue, {
            node_name: nodeName,
          });
          //console.log(nodeName);
        } else {
          if (this.MQTTALMPoints[nodeName]) {
            const upds = [
              "al_start_time",
              "al_ack_time",
              "message",
              "al_tag_value",
              "al_group",
              "al_priority",
            ].filter(
              (col) => this.MQTTALMPoints[nodeName][col] != nodeValue[col]
            );
            upds.forEach((col, idx) => {
              if (idx != upds.length - 1) {
                this.MQTTALMPoints[nodeName][col] == nodeValue[col];
              }
              this.$set(this.MQTTALMPoints[nodeName], col, nodeValue[col]);
            });
            return;
          }
          this.$set(
            this.MQTTALMPoints,
            nodeName,
            Object.assign({}, nodeValue, { node_name: nodeName })
          );
        }
        return;
      }
      if (typeof this.MQTTPoints[nodeName] != "undefined") {
        try {
          if (isNaN(Number(nodeValue))) {
            throw new Error("Not a number");
          }
          this.$set(
            this.MQTTPoints[nodeName],
            "value",
            Number(nodeValue).toFixed(this.MQTTPoints[nodeName].decimal)
          );
        } catch (err) {
          this.$set(this.MQTTPoints[nodeName], "value", nodeValue);
        }
        // callback to page_function
        if (typeof this.fnMQTTPointUpdate == "function") {
          this.fnMQTTPointUpdate &&
            this.fnMQTTPointUpdate(
              this.MQTTPoints[nodeName].nodeValue,
              nodeName
            );
        }
      }
    },
    FnMQTTCommand(nodeValue, nodeName) {
      this.MQTTConnectObj.Publish(nodeValue, nodeName);
    },
    // 即時警報顯示
    async RealAlarmTrigger(opened, loop) {
      if (opened) {
        var self = this;
        var tags = [];
        Object.keys(this.MQTTALMPoints).forEach(function (tag) {
          if (self.MQTTALMPoints[tag] == "1") {
            tags.push(tag);
          }
        });
        if (tags.length > 0) {
          await axios
            .get(this.ActionUrl.alarm, {
              params: {
                tags: tags.join(","),
                _: new Date().getTime(),
              },
            })
            .then(function (res) {
              console.log(res);
              if (res.data.length != tags.length && loop > 0) {
                self.RealAlarm.TriggerTimer = setTimeout(function () {
                  self.RealAlarmTrigger(true, loop - 1);
                }, 200);
                return;
              }
              opened = false;
              self.$set(self.RealAlarm, "Datas", res.data);
              res.data.forEach(function (obj) {
                if (
                  typeof obj.Al_ACK_Time != "string" ||
                  !obj.Al_ACK_Time.length
                ) {
                  opened = true;
                }
              });
            })
            .catch(function (err) {
              opened = false;
              console.log(err.message);
            });
        } else {
          opened = false;
          //self.$set(self.RealAlarm, "Datas", []);
        }
      } else {
        this.RealAlarm.ManuShow = false;
      }
      this.RealAlarm.Display = opened == true ? true : false;
    },
    // 即時警報確認
    async RealAlarmAck() {
      var self = this;
      var tags = [];
      var isSelected = false;
      this.RealAlarm.Selected.forEach(function (obj) {
        isSelected = true;
        if (!obj["Al_ACK_Time"]) {
          tags.push(obj[self.RealAlarm.RowKey]);
        }
      });
      // 取得選擇目標
      if (!isSelected) {
        this.FnAlert("請先選擇警報");
        return false;
      }
      if (tags.length > 0) {
        // 更新警報確認時間
        await axios
          .get(this.ActionUrl.alarm, {
            params: {
              tags: tags.join(","),
              uid: self.LoginSetup.uid,
              ack: "t",
            },
          })
          .catch(function (err) {
            console.log(err.message);
          });
      }
      this.RealAlarm.ManuShow = false;
      this.RealAlarm.Display = false;
    },
    // Dialog相關函式
    async FnAlert(Content, SubText) {
      try {
        await this.$q.dialog({
          title: Content,
          message: SubText,
          ok: this.$t("sys.ok"),
          preventClose: true,
        });
        return true;
      } catch (err) {
        return false;
      }
    },
    async FnConfirm(Content, SubText) {
      try {
        await this.$q.dialog({
          title: Content,
          message: SubText,
          ok: this.$t("sys.ok"),
          cancel: this.$t("sys.cancel"),
          preventClose: true,
        });
        return true;
      } catch (err) {
        return false;
      }
    },
    async FnPrompt(Content, SubText) {
      try {
        return await this.$q.dialog({
          title: Content,
          message: SubText,
          prompt: {
            model: "",
            type: "text",
          },
          ok: this.$t("sys.ok"),
          cancel: this.$t("sys.cancel"),
          preventClose: true,
        });
      } catch (err) {
        return "";
      }
    },
    FnNotify(Content, Type) {
      Type = Type || "info"; // 預設info
      var styles = {
        warning: { color: "orange", icon: "warning", timeout: 3000 },
        success: { color: "positive", icon: "done", timeout: 3000 },
        error: { color: "negative", icon: "close", timeout: 3000 },
        info: { color: "info", icon: "priority_high", timeout: 3000 },
      };
      // 輸出訊息
      this.$q.notify(
        Object.assign(
          {
            color: "dark",
            icon: "message",
            textColor: "white",
            timeout: 2400,
          },
          styles[Type] || {},
          { message: Content, position: "top" }
        )
      );
    },
    FnLoadingShow(Content, timeout) {
      this.$q.loading.show({
        spinner: "q-spinner-facebook",
        spinnerSize: this.bodyFontSize * 5,
        spinnerColor: "primary",
        customClass: "sunware-loading bg-opacity-6",
        message: Content || " > " + this.$t("sys.loading_wait") + "...",
        messageColor: "white",
      });

      if (timeout && isNaN(Number(timeout))) {
        var self = this;
        setTimeout(function () {
          self.FnLoadingHide();
        }, Number(timeout) * 1000);
      }
    },
    FnLoadingHide() {
      this.$q.loading.hide();
    },
    // 報表匯出 目前支持 csv xlsx 格式
    // cols => [{ label: "AAA", field: "xx", format: function(){} }, { label: "BB", field: "yy" }]
    // rows => [{ xx: 123, yy: 6666 }, { xx: 9527, yy: 352.2 }]
    FnReportDownload(ext, rows, cols, setup) {
      if (!rows.length && !cols.length) {
        this.FnAlert("請先指定內容");
        return false;
      }
      var filename = "report";
      try {
        filename = setup.filename;
        delete setup.filename;
      } catch (err) {}
      var output_data = [];
      var row_data = [];
      // 處理欄位
      if (cols.length > 0) {
        cols.forEach(function (obj, index) {
          if (
            ext != "xlsx" ||
            (typeof setup == "object" && typeof setup.pass == "undefined") ||
            (ext == "xlsx" &&
              typeof setup == "object" &&
              !setup.pass.includes(index))
          ) {
            row_data.push(obj.label.replace("\n", " "));
          }
        });
        output_data.push(row_data);
      }
      // 處理資料
      if (ext == "xlsx" && typeof setup != "undefined" && setup.sheets) {
        var output_data = Array.from({ length: setup.sheets.length }, () =>
          output_data.slice(0)
        );
        var cols_row = output_data;
        setup.sheets.forEach(function (item, sheet) {
          var _tarr = rows[item.value || item];
          _tarr.forEach(function (data) {
            if (cols.length > 0) {
              var row_data = [];
              cols.forEach(function (obj, index) {
                if (!setup.pass || !setup.pass.includes(index)) {
                  if (typeof obj.field == "function") {
                    var val =
                      typeof obj.format == "function"
                        ? obj.format(obj.field(data))
                        : obj.field(data);
                  } else {
                    var val =
                      typeof obj.format == "function"
                        ? obj.format(data[obj.field])
                        : data[obj.field];
                  }
                  row_data.push(val);
                }
              });
              output_data[sheet].push(row_data);
            } else {
              output_data[sheet].push(data);
            }
          });
        });
      } else {
        rows.forEach(function (data) {
          if (cols.length > 0) {
            var row_data = [];
            cols.forEach(function (obj) {
              if (typeof obj.field == "function") {
                var val =
                  typeof obj.format == "function"
                    ? obj.format(obj.field(data))
                    : obj.field(data);
              } else {
                var val =
                  typeof obj.format == "function"
                    ? obj.format(data[obj.field])
                    : data[obj.field];
              }
              row_data.push(val);
            });
            output_data.push(ext == "csv" ? row_data.join(",") : row_data);
          } else {
            output_data.push(ext == "csv" ? data.join(",") : data);
          }
        });
      }
      // 匯出報表
      switch (ext) {
        case "csv":
          // 匯出utf8 格式的 csv
          saveAs(
            new Blob(["\ufeff" + output_data.join("\r\n") + "\r\n"], {
              type: "text/csv;charset=utf-8;",
            }),
            filename + ".csv"
          );
          break;
        case "xlsx":
          var wb = XLSX.utils.book_new();
          if (
            typeof setup != "undefined" &&
            typeof setup.sheets != "undefined"
          ) {
            setup.sheets.forEach(function (item, sheet) {
              var name = item.label || item;
              XLSX.utils.book_append_sheet(
                wb,
                XLSX.utils.aoa_to_sheet(output_data[sheet]),
                name
              );
            });
          } else {
            var ws = XLSX.utils.aoa_to_sheet(output_data);
            XLSX.utils.book_append_sheet(wb, ws, "RowDatas");
          }
          var wbout = XLSX.write(wb, {
            bookType: "xlsx",
            bookSST: false,
            type: "array",
          });
          saveAs(
            new Blob([wbout], { type: "application/octet-stream" }),
            filename + ".xlsx"
          );
          break;
        default:
          console.error("輸出類型錯誤");
      }
    },
    FnDownloadHTML(ext, el) {
      html2canvas(el, {
        onrendered: function (canvas) {
          switch (ext) {
            case "image":
              saveAs(canvas.toDataURL(), "report.png");
              break;
            case "pdf":
              var imgData = canvas.toDataURL("image/jpeg");
              var size = {
                p: { w: 600, h: 800 },
                l: { w: 800, h: 550 },
              };
              var orientation = "p";
              if (canvas.width > canvas.height) {
                // 螢幕橫放
                orientation = "l";
              }
              var doc = new jsPDF(orientation, "pt");
              if (
                (size[orientation]["w"] / canvas.width) * canvas.height >
                size[orientation]["h"]
              ) {
                // 若過高則變更寬
                var w = canvas.width * (canvas.height / size[orientation]["h"]);
                doc.addImage(
                  imgData,
                  5,
                  5,
                  w > size[orientation]["w"] ? size[orientation]["w"] : w,
                  size[orientation]["h"],
                  "img"
                );
              } else {
                var h = (size[orientation]["w"] / canvas.width) * canvas.height;
                doc.addImage(
                  imgData,
                  5,
                  5,
                  size[orientation]["w"],
                  h > size[orientation]["h"] ? size[orientation]["w"] : h,
                  "img"
                );
              }
              doc.save("report.pdf");
              break;
            default:
              console.error("輸出類型錯誤");
          }
        },
      });
    },
    FnGetOptions(data, labelKey, valueKey) {
      return _.map(data, (r) => {
        return { label: r[labelKey], value: r[valueKey] };
      });
    },
    receiveMessage(event) {
      var $url = new URL(location.href);
      if (["https://www.sunware.com.tw", $url.origin].includes(event.origin)) {
        try {
          var request = JSON.parse(event.data);
          if (request.method && typeof this[request.method] != "undefined") {
            this[request.method] && this[request.method](request.message);
          }
        } catch (err) {
          return;
        }
      }
    },
  },
  destroyed() {
    var self = this;
    window.removeEventListener("beforeunload", function (e) {
      self.FnPageBeforeunload(e);
    });
    window.removeEventListener("unload", function (e) {
      self.FnPageUnload(e);
    });
  },
};
