chore: 首次簽入 Thinkyu ASP.NET 專案
- 加入 Visual Studio / ASP.NET .gitignore - 排除建置輸出、IDE 設定、NuGet packages、大型 MSI 安裝檔
This commit is contained in:
@@ -0,0 +1,682 @@
|
||||
// ========================================
|
||||
// 報到系統 - 共用 JavaScript 函式庫
|
||||
// 檔案位置:Scripts/shared.js
|
||||
// ========================================
|
||||
//
|
||||
// 提供的全域服務:
|
||||
// - $api : API 呼叫服務 (定義於 line 21)
|
||||
// - $message : 訊息提示服務 (定義於 line 101)
|
||||
// - $format : 顯示格式化服務 (定義於 line 223)
|
||||
// - $dataConvert : 資料轉換服務 (處理 /Date()/ 等資料來源格式)
|
||||
// - $navigate : 頁面導航服務
|
||||
//
|
||||
// 使用方式:
|
||||
// 在 Vue 實例內:this.$api.login(...), this.$format.date(...), this.$dataConvert.formatDate(...)
|
||||
// 在 Vue 實例外:window.$api.login(...), window.$format.date(...), window.$dataConvert.formatDate(...)
|
||||
//
|
||||
// 向後相容舊 API:apiCall.login(...), message.success(...), navigate.to(...)
|
||||
//
|
||||
// ========================================
|
||||
|
||||
(() => {
|
||||
'use strict';
|
||||
|
||||
// ==========================================
|
||||
// Loading 遮罩服務
|
||||
// 定義位置:Scripts/shared.js (line 21-70)
|
||||
// ==========================================
|
||||
const LoadingService = {
|
||||
overlay: null,
|
||||
counter: 0,
|
||||
|
||||
/**
|
||||
* 顯示 Loading 遮罩
|
||||
* @定義位置 Scripts/shared.js line 29
|
||||
*/
|
||||
show() {
|
||||
this.counter++;
|
||||
|
||||
if (!this.overlay) {
|
||||
this.overlay = document.createElement('div');
|
||||
this.overlay.id = 'api-loading-overlay';
|
||||
this.overlay.innerHTML = `
|
||||
<div class="spinner-container">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(this.overlay);
|
||||
}
|
||||
|
||||
this.overlay.style.display = 'flex';
|
||||
},
|
||||
|
||||
/**
|
||||
* 隱藏 Loading 遮罩
|
||||
* @定義位置 Scripts/shared.js line 51
|
||||
*/
|
||||
hide() {
|
||||
this.counter--;
|
||||
|
||||
if (this.counter <= 0) {
|
||||
this.counter = 0;
|
||||
if (this.overlay) {
|
||||
this.overlay.style.display = 'none';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 強制隱藏 Loading 遮罩
|
||||
* @定義位置 Scripts/shared.js line 66
|
||||
*/
|
||||
forceHide() {
|
||||
this.counter = 0;
|
||||
if (this.overlay) {
|
||||
this.overlay.style.display = 'none';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// API 服務
|
||||
// 定義位置:Scripts/shared.js (line 76-162)
|
||||
// ==========================================
|
||||
const ApiService = {
|
||||
// API 基礎路徑
|
||||
baseUrl: API_BASE_URL,
|
||||
homeUrl: HOME_URL,
|
||||
|
||||
/**
|
||||
* 呼叫 ASMX WebMethod
|
||||
* @定義位置 Scripts/shared.js line 33
|
||||
* @param {string} serviceName - 服務名稱 (如 'LoginService')
|
||||
* @param {string} methodName - 方法名稱 (如 'Login')
|
||||
* @param {object} data - 傳送的資料
|
||||
* @returns {Promise} 回傳 Promise 物件
|
||||
*
|
||||
* @example
|
||||
* $api.call('LoginService', 'Login', { username: 'admin', password: '123' })
|
||||
* .then(response => console.log(response))
|
||||
* .catch(error => console.error(error));
|
||||
*/
|
||||
call(serviceName, methodName, data = {}) {
|
||||
LoadingService.show();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
const url = `${this.baseUrl}${serviceName}.asmx/${methodName}`;
|
||||
|
||||
xhr.open('POST', url, true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
|
||||
// ✨ 關鍵修正: 確保傳送 Cookie
|
||||
xhr.withCredentials = true;
|
||||
|
||||
xhr.onload = () => {
|
||||
LoadingService.hide();
|
||||
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
resolve(response.d || response);
|
||||
} catch (e) {
|
||||
reject({ Success: false, Message: '解析回應失敗' });
|
||||
}
|
||||
} else if (xhr.status === 401) {
|
||||
// ✨ 處理 401 錯誤
|
||||
reject({ Success: false, Message: '未授權,請重新登入' });
|
||||
// 可選: 自動導向登入頁
|
||||
// window.location.href = '/Login.aspx';
|
||||
} else {
|
||||
reject({ Success: false, Message: `HTTP Error: ${xhr.status}` });
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () => {
|
||||
LoadingService.hide();
|
||||
reject({ Success: false, Message: '網路錯誤' });
|
||||
};
|
||||
|
||||
xhr.send(JSON.stringify(data));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 登入
|
||||
* @定義位置 Scripts/shared.js line 78
|
||||
* @param {string} username - 使用者帳號
|
||||
* @param {string} password - 密碼
|
||||
* @param {boolean} rememberMe - 是否記住我
|
||||
* @returns {Promise<LoginResponse>}
|
||||
*
|
||||
* @example
|
||||
* $api.login('admin', 'admin123', true)
|
||||
* .then(response => {
|
||||
* if (response.Success) {
|
||||
* console.log('登入成功:', response.UserName);
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
login(username, password, rememberMe = false) {
|
||||
return this.call('LoginService', 'Login', {
|
||||
username,
|
||||
password,
|
||||
rememberMe
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 登出
|
||||
* @定義位置 Scripts/shared.js line 101
|
||||
* @returns {Promise<LogoutResponse>}
|
||||
*
|
||||
* @example
|
||||
* $api.logout().then(response => console.log('已登出'));
|
||||
*/
|
||||
logout() {
|
||||
return this.call('LoginService', 'Logout');
|
||||
},
|
||||
|
||||
/**
|
||||
* 檢查 Session 狀態
|
||||
* @定義位置 Scripts/shared.js line 112
|
||||
* @returns {Promise<CheckSessionResponse>}
|
||||
*
|
||||
* @example
|
||||
* $api.checkSession().then(response => {
|
||||
* if (response.IsLoggedIn) {
|
||||
* console.log('已登入:', response.UserName);
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
checkSession() {
|
||||
return this.call('LoginService', 'CheckSession');
|
||||
}
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// 訊息服務
|
||||
// 定義位置:Scripts/shared.js (line 133-208)
|
||||
// ==========================================
|
||||
const MessageService = {
|
||||
container: null,
|
||||
|
||||
/**
|
||||
* 初始化訊息容器
|
||||
* @定義位置 Scripts/shared.js line 141
|
||||
*/
|
||||
initContainer() {
|
||||
if (!this.container) {
|
||||
this.container = document.createElement('div');
|
||||
this.container.id = 'message-container';
|
||||
this.container.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 1050; pointer-events: none; display: flex; flex-direction: column; gap: 10px; max-width: 400px;';
|
||||
document.body.appendChild(this.container);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 顯示訊息提示
|
||||
* @定義位置 Scripts/shared.js line 153
|
||||
* @param {string} text - 訊息文字
|
||||
* @param {string} type - 訊息類型 (success/danger/warning/info)
|
||||
* @param {number} duration - 顯示時間 (毫秒,預設 3000)
|
||||
*
|
||||
* @example
|
||||
* $message.show('操作成功', 'success', 3000);
|
||||
*/
|
||||
show(text, type, duration = 3000) {
|
||||
this.initContainer();
|
||||
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = `alert alert-${type}`;
|
||||
alertDiv.style.cssText = 'position: relative; min-width: 300px; animation: slideIn 0.3s; box-shadow: 0 4px 12px rgba(0,0,0,0.15); margin: 0; pointer-events: auto; padding-right: 40px; display: flex; align-items: center;';
|
||||
|
||||
const textSpan = document.createElement('span');
|
||||
textSpan.textContent = text;
|
||||
textSpan.style.cssText = 'flex: 1;';
|
||||
alertDiv.appendChild(textSpan);
|
||||
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.innerHTML = '×';
|
||||
closeBtn.style.cssText = 'position: absolute; right: 10px; top: 50%; transform: translateY(-50%); background: none; border: none; font-size: 24px; line-height: 1; cursor: pointer; opacity: 0.5; padding: 0; width: 20px; height: 20px;';
|
||||
closeBtn.onmouseover = () => closeBtn.style.opacity = '1';
|
||||
closeBtn.onmouseout = () => closeBtn.style.opacity = '0.5';
|
||||
closeBtn.onclick = () => this.removeMessage(alertDiv);
|
||||
alertDiv.appendChild(closeBtn);
|
||||
|
||||
this.container.appendChild(alertDiv);
|
||||
|
||||
if (duration > 0) {
|
||||
setTimeout(() => {
|
||||
this.removeMessage(alertDiv);
|
||||
}, duration);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 移除訊息
|
||||
* @定義位置 Scripts/shared.js line 193
|
||||
* @param {HTMLElement} alertDiv - 要移除的訊息元素
|
||||
*/
|
||||
removeMessage(alertDiv) {
|
||||
if (!alertDiv || !alertDiv.parentNode) return;
|
||||
|
||||
alertDiv.style.animation = 'slideOut 0.3s';
|
||||
setTimeout(() => {
|
||||
if (alertDiv.parentNode) {
|
||||
alertDiv.parentNode.removeChild(alertDiv);
|
||||
}
|
||||
}, 300);
|
||||
},
|
||||
|
||||
/**
|
||||
* 顯示成功訊息 (綠色)
|
||||
* @定義位置 Scripts/shared.js line 171
|
||||
* @param {string} text - 訊息文字
|
||||
* @param {number} duration - 顯示時間 (毫秒)
|
||||
*
|
||||
* @example
|
||||
* $message.success('儲存成功');
|
||||
*/
|
||||
success(text, duration) {
|
||||
this.show(text, 'success', duration);
|
||||
},
|
||||
|
||||
/**
|
||||
* 顯示錯誤訊息 (紅色)
|
||||
* @定義位置 Scripts/shared.js line 183
|
||||
* @param {string} text - 訊息文字
|
||||
* @param {number} duration - 顯示時間 (毫秒)
|
||||
*
|
||||
* @example
|
||||
* $message.error('操作失敗');
|
||||
*/
|
||||
error(text, duration) {
|
||||
this.show(text, 'danger', duration);
|
||||
},
|
||||
|
||||
/**
|
||||
* 顯示警告訊息 (黃色)
|
||||
* @定義位置 Scripts/shared.js line 195
|
||||
* @param {string} text - 訊息文字
|
||||
* @param {number} duration - 顯示時間 (毫秒)
|
||||
*
|
||||
* @example
|
||||
* $message.warning('請注意');
|
||||
*/
|
||||
warning(text, duration) {
|
||||
this.show(text, 'warning', duration);
|
||||
},
|
||||
|
||||
/**
|
||||
* 顯示資訊訊息 (藍色)
|
||||
* @定義位置 Scripts/shared.js line 207
|
||||
* @param {string} text - 訊息文字
|
||||
* @param {number} duration - 顯示時間 (毫秒)
|
||||
*
|
||||
* @example
|
||||
* $message.info('系統提示');
|
||||
*/
|
||||
info(text, duration) {
|
||||
this.show(text, 'info', duration);
|
||||
}
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// 顯示格式化服務(用於 UI 顯示,不處理資料來源格式轉換)
|
||||
// 定義位置:Scripts/shared.js
|
||||
// ==========================================
|
||||
const FormatService = {
|
||||
/**
|
||||
* 格式化日期(顯示用)
|
||||
* @param {string|Date} dateString - 日期字串或 Date 物件
|
||||
* @returns {string} "YYYY-MM-DD"
|
||||
*
|
||||
* @example
|
||||
* this.$format.date('2025-06-15T08:30:00'); // "2025-06-15"
|
||||
*/
|
||||
date(dateString) {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
if (isNaN(date.getTime())) return '';
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化時間(顯示用,只取 HH:mm)
|
||||
* @param {string|Date} dateString - 日期字串或 Date 物件
|
||||
* @returns {string} "HH:mm"
|
||||
*
|
||||
* @example
|
||||
* this.$format.time('2025-06-15T08:30:00'); // "08:30"
|
||||
*/
|
||||
time(dateString) {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
if (isNaN(date.getTime())) return '';
|
||||
const hour = String(date.getHours()).padStart(2, '0');
|
||||
const minute = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${hour}:${minute}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期時間(顯示用)
|
||||
* @param {string|Date} dateString - 日期字串或 Date 物件
|
||||
* @returns {string} "YYYY-MM-DD HH:mm:ss"
|
||||
*
|
||||
* @example
|
||||
* this.$format.dateTime('2025-06-15T08:30:00'); // "2025-06-15 08:30:00"
|
||||
*/
|
||||
dateTime(dateString) {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
if (isNaN(date.getTime())) return '';
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hour = String(date.getHours()).padStart(2, '0');
|
||||
const minute = String(date.getMinutes()).padStart(2, '0');
|
||||
const second = String(date.getSeconds()).padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期時間供 input[type="datetime-local"] 使用
|
||||
* @param {string|Date} dateString - 日期字串或 Date 物件
|
||||
* @returns {string} "YYYY-MM-DDTHH:mm"
|
||||
*
|
||||
* @example
|
||||
* this.$format.dateTimeForInput('2025-06-15T08:30:00'); // "2025-06-15T08:30"
|
||||
*/
|
||||
dateTimeForInput(dateString) {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
if (isNaN(date.getTime())) return '';
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hour = String(date.getHours()).padStart(2, '0');
|
||||
const minute = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}T${hour}:${minute}`;
|
||||
}
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// 資料轉換服務(用於處理 /Date(...)/ 等資料來源格式)
|
||||
// 定義位置:Scripts/shared.js
|
||||
// ==========================================
|
||||
const DataConvertionService = {
|
||||
/**
|
||||
* 格式化日期
|
||||
* @定義位置 Scripts/shared.js line 231
|
||||
* @param {string|Date} dateStr - 日期字串或 Date 物件
|
||||
* @param {string} separator - 日期分隔符,預設為 "-"
|
||||
* @returns {string} 格式化後的日期字串
|
||||
*
|
||||
* @example
|
||||
* $dataConvert.formatDate('/Date(1764622110737)/', '-'); // "2025-12-31"
|
||||
* $dataConvert.formatDate(new Date(), '/'); // "2025/12/31"
|
||||
*/
|
||||
formatDate(dateStr, separator = '-') {
|
||||
if (!dateStr) return '';
|
||||
|
||||
// 處理 ASP.NET ASMX 的日期格式 /Date(1764622110737)/
|
||||
if (typeof dateStr === 'string' && dateStr.indexOf('/Date(') === 0) {
|
||||
const timestamp = parseInt(dateStr.match(/-?\d+/)[0]);
|
||||
const date = new Date(timestamp);
|
||||
const year = String(date.getFullYear()).padStart(4, '0');
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return year + separator + month + separator + day;
|
||||
}
|
||||
|
||||
// 如果已經是一般的 Date 物件或其他格式
|
||||
const date = new Date(dateStr);
|
||||
if (!isNaN(date.getTime())) {
|
||||
const year = String(date.getFullYear()).padStart(4, '0');
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return year + separator + month + separator + day;
|
||||
}
|
||||
|
||||
return dateStr;
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期時間
|
||||
* @定義位置 Scripts/shared.js line 269
|
||||
* @param {string|Date} dateStr - 日期字串或 Date 物件
|
||||
* @param {string} separator - 日期分隔符,預設為 "-"
|
||||
* @returns {string} 格式化後的日期時間字串
|
||||
*
|
||||
* @example
|
||||
* $dataConvert.formatDateTime('/Date(1764622110737)/'); // "2025-12-31 14:35:10"
|
||||
*/
|
||||
formatDateTime(dateStr, separator = '-') {
|
||||
if (!dateStr) return '';
|
||||
|
||||
let date;
|
||||
|
||||
// 處理 ASP.NET ASMX 的日期格式 /Date(1764622110737)/
|
||||
if (typeof dateStr === 'string' && dateStr.indexOf('/Date(') === 0) {
|
||||
const timestamp = parseInt(dateStr.match(/-?\d+/)[0]);
|
||||
date = new Date(timestamp);
|
||||
} else {
|
||||
date = new Date(dateStr);
|
||||
}
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
return dateStr;
|
||||
}
|
||||
|
||||
const year = String(date.getFullYear()).padStart(4, '0');
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
|
||||
return year + separator + month + separator + day + ' ' + hours + ':' + minutes + ':' + seconds;
|
||||
},
|
||||
|
||||
/**
|
||||
* 遍歷物件或陣列,自動轉換所有符合 ASP.NET ASMX 日期格式的屬性
|
||||
* @定義位置 Scripts/shared.js line 308
|
||||
* @param {Object|Array} obj - 要處理的物件或陣列
|
||||
* @param {string} separator - 日期分隔符,預設為 "-"
|
||||
* @returns {Object|Array} 處理後的物件或陣列
|
||||
*
|
||||
* @example
|
||||
* const data = { createDate: '/Date(1764622110737)/', items: [{date: '/Date(1764622110737)/'}] };
|
||||
* $dataConvert.convertDatesInObject(data); // 自動轉換所有日期欄位
|
||||
*/
|
||||
convertDatesInObject(obj, separator = '-') {
|
||||
if (!obj) return obj;
|
||||
|
||||
// 處理陣列
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(item => this.convertDatesInObject(item, separator));
|
||||
}
|
||||
|
||||
// 處理物件
|
||||
if (typeof obj === 'object') {
|
||||
const result = {};
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
const value = obj[key];
|
||||
|
||||
// 檢查是否為 ASP.NET ASMX 日期格式
|
||||
if (typeof value === 'string' && value.indexOf('/Date(') === 0) {
|
||||
result[key] = this.formatDateTime(value, separator);
|
||||
}
|
||||
// 遞迴處理巢狀物件或陣列
|
||||
else if (value !== null && typeof value === 'object') {
|
||||
result[key] = this.convertDatesInObject(value, separator);
|
||||
}
|
||||
else {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// 導航服務
|
||||
// 定義位置:Scripts/shared.js (line 347-379)
|
||||
// ==========================================
|
||||
const NavigateService = {
|
||||
/**
|
||||
* 導航到指定 URL
|
||||
* @定義位置 Scripts/shared.js line 230
|
||||
* @param {string} url - 目標 URL
|
||||
*
|
||||
* @example
|
||||
* $navigate.to('/Forms/ActivityList.aspx');
|
||||
*/
|
||||
to(url) {
|
||||
window.location.href = url;
|
||||
},
|
||||
|
||||
/**
|
||||
* 導航到登入頁
|
||||
* @定義位置 Scripts/shared.js line 241
|
||||
*
|
||||
* @example
|
||||
* $navigate.toLogin();
|
||||
*/
|
||||
toLogin() {
|
||||
this.to(`${HOME_URL}Login.aspx`);
|
||||
},
|
||||
|
||||
/**
|
||||
* 導航到首頁
|
||||
* @定義位置 Scripts/shared.js line 251
|
||||
*
|
||||
* @example
|
||||
* $navigate.toHome();
|
||||
*/
|
||||
toHome() {
|
||||
this.to(`${HOME_URL}Forms/Home.aspx`);
|
||||
}
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// 初始化動畫樣式
|
||||
// 定義位置:Scripts/shared.js (line 386-454)
|
||||
// ==========================================
|
||||
(() => {
|
||||
if (!document.getElementById('shared-styles')) {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'shared-styles';
|
||||
style.textContent = `
|
||||
/* Message 動畫 */
|
||||
@keyframes slideIn {
|
||||
from { transform: translateX(400px); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
@keyframes slideOut {
|
||||
from { transform: translateX(0); opacity: 1; }
|
||||
to { transform: translateX(400px); opacity: 0; }
|
||||
}
|
||||
|
||||
/* Loading 遮罩樣式 */
|
||||
#api-loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
#api-loading-overlay .spinner-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#api-loading-overlay .spinner {
|
||||
border: 4px solid rgba(255, 255, 255, 0.3);
|
||||
border-left-color: #c41e3a;
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
|
||||
// ==========================================
|
||||
// 匯出到全域
|
||||
// 定義位置:Scripts/shared.js (line 409-430)
|
||||
// ==========================================
|
||||
|
||||
// 新版 API(建議使用,符合 Vue 慣例)
|
||||
window.$api = ApiService;
|
||||
window.$message = MessageService;
|
||||
window.$format = FormatService;
|
||||
window.$dataConvert = DataConvertionService;
|
||||
window.$navigate = NavigateService;
|
||||
window.$loading = LoadingService;
|
||||
|
||||
// 舊版 API(向後相容,未來版本可能移除)
|
||||
window.apiCall = ApiService;
|
||||
window.message = MessageService;
|
||||
window.navigate = NavigateService;
|
||||
|
||||
// ==========================================
|
||||
// Vue Plugin (自動安裝)
|
||||
// 定義位置:Scripts/shared.js (line 433-445)
|
||||
// ==========================================
|
||||
|
||||
// 延遲註冊 Vue plugin,確保 Vue 已載入
|
||||
function installVuePlugin() {
|
||||
if (typeof window.Vue !== 'undefined' && window.Vue.prototype) {
|
||||
window.Vue.prototype.$api = ApiService;
|
||||
window.Vue.prototype.$message = MessageService;
|
||||
window.Vue.prototype.$format = FormatService;
|
||||
window.Vue.prototype.$dataConvert = DataConvertionService;
|
||||
window.Vue.prototype.$navigate = NavigateService;
|
||||
window.Vue.prototype.$loading = LoadingService;
|
||||
|
||||
console.log('[Shared.js] ? Vue plugin installed');
|
||||
console.log(' Use in Vue: this.$api, this.$message, this.$format, this.$dataConvert, this.$navigate, this.$loading');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 立即嘗試安裝
|
||||
if (!installVuePlugin()) {
|
||||
// 如果失敗,等待 DOM 載入後再試
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', installVuePlugin);
|
||||
} else {
|
||||
// DOM 已載入,等待一下再試
|
||||
setTimeout(installVuePlugin, 0);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[Shared.js] ? Services loaded from Scripts/shared.js');
|
||||
console.log(' Global API: window.$api, window.$message, window.$format, window.$dataConvert, window.$navigate, window.$loading');
|
||||
console.log(' Legacy API: window.apiCall, window.message, window.navigate');
|
||||
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user