var base_function = {
	data: () => {
		return {
			Version: "0.10.0",
			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_gold_seal.svg",
				// logo網址
				homeHref: "index.php",
				caption: "index",
				pdfName : null,
			},
			// Soft監控啟動
			IsSoftStartup: false,
			// MQTT監控啟動
			IsMQTTStartup: false,
			SoftConfig: {
				host: document.domain,
				port: window.location.protocol == "https:" ? "9999" : "8888",
				useSSL: window.location.protocol == "https:",
				autoConnect: true,
				reconnect: true
			},
			SoftConnectObj: [new OPCTechs_Soft()],
			MQTTConfig: {
				host: document.domain,
				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: true,
				delay_sec: 10,
				json_value: true,
				qos: 0,
				retained: true
			},
			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]/ig, "") || "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;
		},
		pageDateTableBtn (){
			if (this.LoginSetup.roles.includes('admin') || this.LoginSetup.roles.includes('director') ) {
				return true;
			} else {
				return 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) {
			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) {
				console.log(err.message);
			}).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) {
				console.log(err.message);
			}).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, {
				useCORS: true,
				scale: 2,
				background: '#FFFFFF',
				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 },
							// 	"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");

							var contentWidth = canvas.width;
	                        var contentHeight = canvas.height;

	                        //一頁pdf顯示html頁面生成的canvas高度;
	                        var pageHeight = contentWidth / 592.28 * 841.89;
	                        //未生成pdf的html頁面高度
	                        var leftHeight = contentHeight;
	                        //pdf頁面偏移
	                        var position = 0;
	                        //html頁面生成的canvas在pdf中圖片的寬高（a4紙的尺寸[595.28,841.89]）
	                        var imgWidth = 595.28;
	                        var imgHeight = 592.28 / contentWidth * contentHeight;
	                        var pageData = canvas.toDataURL('image/jpeg', 1.0);
	                        var pdf = new jsPDF('', 'pt', 'a4');

	                        // 有兩個高度需要區分，一個是html頁面的實際高度，和生成pdf的頁面高度(841.89)
	                        // 當內容未超過pdf一頁顯示的範圍，無需分頁
	                        if (leftHeight < pageHeight) {
	                           	pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight);
	                        } else {
	                            while (leftHeight > 0) {
	                                pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
	                                leftHeight -= pageHeight;
	                                position -= 841.89;
	                                //避免添加空白頁
	                                if (leftHeight > 0) {
	                                    pdf.addPage();
	                                }
	                            }
	                        }
	                        var temp_name = vm.WebSetup.pdfName+".pdf";
	                        pdf.save(temp_name);

							break;
						default:
							console.error("輸出類型錯誤");
					}
				}
			});
		},
		FnGetOptions (data, labelKey, valueKey) {
			return _.map(data, (r) => {
				return { label: r[labelKey], value: r[valueKey] };
			});
		},
		receiveMessage (event) {
			console.log(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); });
	}
}