chore: 首次簽入 Thinkyu ASP.NET 專案

- 加入 Visual Studio / ASP.NET .gitignore
- 排除建置輸出、IDE 設定、NuGet packages、大型 MSI 安裝檔
This commit is contained in:
2026-09-10 09:42:37 +08:00
commit 577060bc78
2496 changed files with 501389 additions and 0 deletions
@@ -0,0 +1,185 @@
/**
* 使用 ApiMixin 的示例
* 这个文件展示了如何简化 領據處理.js 中的 API 调用
*/
// 原来的写法(冗长):
save() {
// ... 验证代码 ...
axios({
method: 'post',
url: `${API_BASE}/Save`,
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
data: JSON.stringify(data)
})
.then(response => {
const result = response.data.d || response.data;
if (result.Success) {
this.ShowSuccess(result.Message);
// ... 处理成功逻辑 ...
} else {
this.ShowError('儲存失敗: ' + result.Message);
}
})
.catch(error => {
console.error('Save error:', error);
this.ShowError('儲存發生錯誤: ' + (error.message || '請檢查網路連線'));
})
.finally(() => {
this.loading = false;
});
}
// ===================================================================
// 使用 ApiMixin 的新写法(简洁):
async save() {
if (!this.validate()) {
return;
}
this.loading = true;
try {
const data = {
isNew: this.mode === 'add',
mainItem: this.mainData,
laborItem: this.laborData,
travelItem: this.travelData
};
const result = await this.callApiWithErrorMsg('領據處理', 'Save', data, '儲存');
this.ShowSuccess(result.Message);
// 返回查看模式
this.mode = 'view';
// 更新编号和数据
if (this.mainData.編號 === '' && result.Data) {
this.mainData.編號 = result.Data;
this.編號 = result.Data;
}
this.backupData();
} catch (error) {
console.error('Save error:', error);
} finally {
this.loading = false;
}
}
// ===================================================================
// 其他 API 调用示例:
// 原来的 loadDropdownList:
async loadDropdownList(key) {
let value = null;
await axios({
method: 'post',
url: `${API_BASE}/GetDropdownLists`,
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
data: JSON.stringify({ key: key })
})
.then(response => {
const result = response.data.d || response.data;
if (result.Success) {
value = result.Data || [];
} else {
console.error('取得下拉選單資料失敗:', result.Message);
}
})
.catch(error => {
console.error('載入下拉選單失敗:', error);
});
return value;
}
// 使用 mixin 的新写法:
async loadDropdownList(key) {
try {
const result = await this.callApi('領據處理', 'GetDropdownLists', { key });
return result.Data || [];
} catch (error) {
console.error('載入下拉選單失敗:', error);
return [];
}
}
// ===================================================================
// searchRecipient 的对比:
// 原来的写法:
searchRecipient() {
const key = this.mainData.領款人姓名;
const identity = this.mainData.身分別;
if (key && key.length > 0) {
if (!identity) {
this.ShowWarning('請先選擇身分別');
this.recipientList = [];
this.showRecipientDropdown = false;
return;
}
axios({
method: 'post',
url: `${API_BASE}/SearchRecipient`,
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
data: JSON.stringify({ key, identity })
})
.then(response => {
const result = response.data.d || response.data;
if (result.Success) {
this.recipientList = result.Data || [];
this.showRecipientDropdown = this.recipientList.length > 0;
} else {
this.recipientList = [];
this.showRecipientDropdown = false;
this.ShowWarning(result.Message);
}
})
.catch(error => {
console.error('查詢領款人失敗:', error);
});
} else {
this.recipientList = [];
this.showRecipientDropdown = false;
}
}
// 使用 mixin 的新写法:
async searchRecipient() {
const key = this.mainData.領款人姓名;
const identity = this.mainData.身分別;
if (!key || key.length === 0) {
this.recipientList = [];
this.showRecipientDropdown = false;
return;
}
if (!identity) {
this.ShowWarning('請先選擇身分別');
this.recipientList = [];
this.showRecipientDropdown = false;
return;
}
try {
const result = await this.callApi('領據處理', 'SearchRecipient', { key, identity });
this.recipientList = result.Data || [];
this.showRecipientDropdown = this.recipientList.length > 0;
} catch (error) {
console.error('查詢領款人失敗:', error);
this.recipientList = [];
this.showRecipientDropdown = false;
this.ShowWarning(error.message);
}
}
+54
View File
@@ -0,0 +1,54 @@
/**
* API Mixin - 统一的 API 调用管理
* 用法: 在 Vue 组件中引入此 mixin,然后使用 this.callApi(endpoint, functionName, data)
*/
const ApiMixin = {
methods: {
/**
* 调用 API 的通用方法
* @param {string} endpoint - API 端点(.asmx 文件名,不需要扩展名),如 '領據處理'
* @param {string} functionName - 方法名称,如 'Save', 'GetOne'
* @param {object} data - 要传送的数据对象
* @returns {Promise} 返回 Promise,包含 API 响应数据
*/
callApi(endpoint, functionName, data = {}) {
console.log(`callApi [${endpoint}/${functionName}]`, data);
return axios({
method: 'post',
url: `${API_BASE}/${endpoint}.asmx/${functionName}`,
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
data: JSON.stringify(data)
})
.then(response => {
const result = response.data.d || response.data;
if (result.Success) {
return Promise.resolve(result);
} else {
return Promise.reject(new Error(result.Message || '操作失败'));
}
})
.catch(error => {
console.error(`API 調用失敗 [${endpoint}/${functionName}]:`, error);
return Promise.reject(error);
});
},
/**
* 调用 API 并显示错误消息的便捷方法
* @param {string} endpoint - API 端点
* @param {string} functionName - 方法名称
* @param {object} data - 数据对象
* @param {string} errorPrefix - 错误消息前缀
* @returns {Promise}
*/
callApiWithErrorMsg(endpoint, functionName, data = {}, errorPrefix = '操作') {
return this.callApi(endpoint, functionName, data)
.catch(error => {
this.ShowError(`${errorPrefix}失敗: ${error.message || '請檢查網路連線'}`);
return Promise.reject(error);
});
}
}
};
File diff suppressed because one or more lines are too long
+35
View File
@@ -0,0 +1,35 @@
// Vue Mixin - Base64 工具
// 提供 Base64 字串轉換為 Blob 的功能
var Base64Mixin = {
methods: {
/**
* 將 Base64 字串轉換為 Blob 物件
* @param {string} base64 - Base64 編碼字串
* @returns {Blob} Excel 格式的 Blob 物件
*/
Base64ToBlob: function(base64) {
try {
if (!base64 || typeof base64 !== 'string') {
throw new Error('Invalid input: Base64 string is required');
}
var cleanBase64 = base64.replace(/\s/g, '');
if (!cleanBase64 || !/^[A-Za-z0-9+/]*={0,2}$/.test(cleanBase64)) {
throw new Error('Invalid Base64 string format');
}
var binaryString = atob(cleanBase64);
var bytes = new Uint8Array(binaryString.length);
for (var i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
} catch (error) {
console.error('Base64ToBlob error:', error);
throw error;
}
}
}
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,198 @@
# Vue Date Mixin 使用說明
## 概述
`vue-date-mixin.js` 是一個 Vue.js mixin,用於處理 ASP.NET ASMX Web Service 回傳的日期格式 `/Date(timestamp)/`
## 功能
### 1. FormatDate(dateStr, separator)
將日期格式化為指定分隔符的格式。
**參數:**
- `dateStr`: 日期字串或 Date 物件
- `separator`: 日期分隔符,預設為 `"-"`
**回傳值:**
- 格式化後的日期字串
**範例:**
```javascript
this.FormatDate('/Date(1764622110737)/'); // 回傳: "2025-12-02" (使用預設 "-")
this.FormatDate('/Date(1764622110737)/', '/'); // 回傳: "2025/12/02"
this.FormatDate('/Date(1764622110737)/', '.'); // 回傳: "2025.12.02"
this.FormatDate(new Date(), '-'); // 回傳: "2025-01-15"
```
### 2. ConvertDatesInObject(obj, separator)
遍歷物件或陣列,自動轉換所有符合 ASP.NET ASMX 日期格式的屬性。
**參數:**
- `obj`: 要處理的物件或陣列
- `separator`: 日期分隔符,預設為 `"-"`
**回傳值:**
- 處理後的物件或陣列(所有日期欄位已轉換)
**範例:**
```javascript
// 使用預設分隔符 "-"
const item = {
編號: 1,
生效日期: '/Date(1764622110737)/',
備註: '測試'
};
const converted = this.ConvertDatesInObject(item);
// converted.生效日期 = "2025-12-02"
// 使用自訂分隔符 "/"
const converted2 = this.ConvertDatesInObject(item, '/');
// converted2.生效日期 = "2025/12/02"
// 處理陣列
const items = [
{ 編號: 1, 生效日期: '/Date(1764622110737)/' },
{ 編號: 2, 生效日期: '/Date(1764708510737)/' }
];
const converted = this.ConvertDatesInObject(items, '-');
// 所有項目的生效日期都會被轉換為 "YYYY-MM-DD" 格式
```
### 3. FormatDateTime(dateStr, separator)
將日期時間格式化為指定分隔符的格式。
**參數:**
- `dateStr`: 日期字串或 Date 物件
- `separator`: 日期分隔符,預設為 `"-"`
**回傳值:**
- 格式化後的日期時間字串
**範例:**
```javascript
this.FormatDateTime('/Date(1764622110737)/'); // 回傳: "2025-12-02 14:35:10"
this.FormatDateTime('/Date(1764622110737)/', '/'); // 回傳: "2025/12/02 14:35:10"
```
## 使用方式
### 1. 引入 Mixin
`.ascx.cs` 檔案中註冊腳本:
```csharp
Page.ClientScript.RegisterClientScriptInclude("DateMixin",
ParentPage.ResolveClientUrl("~/Scripts/vue-date-mixin.js"));
```
### 2. 在 Vue 實例中使用
```javascript
function CreateApp() {
app = new Vue({
el: "#APP",
mixins: [DateMixin], // 加入 mixin
data: {
Items: []
},
methods: {
SelectPage() {
axios.post('/Services/XXX.asmx/SelectPage', data)
.then(response => {
const result = response.data.d || response.data;
// 自動轉換所有日期欄位 (使用預設分隔符 "-")
const items = this.ConvertDatesInObject(result.data || []);
this.Items = items;
// 或使用自訂分隔符 "/"
// const items = this.ConvertDatesInObject(result.data || [], '/');
});
}
}
});
}
```
## 日期分隔符選項
### 常用分隔符
- `"-"` (預設): 2025-12-02
- `"/"`: 2025/12/02
- `"."`: 2025.12.02
- `""` (空字串): 20251202
### 使用建議
- **ISO 8601 標準**: 使用 `"-"` (例: 2025-12-02)
- **台灣慣用**: 使用 `"/"` (例: 2025/12/02)
- **歐洲慣用**: 使用 `"."` (例: 2025.12.02)
- **HTML5 date input**: 使用 `"-"` (必須是 YYYY-MM-DD 格式)
## 支援的日期格式
### 輸入格式
- ASP.NET ASMX 格式: `/Date(1764622110737)/`
- 標準 JavaScript Date 物件
- 任何可被 `new Date()` 解析的字串
### 輸出格式
- FormatDate: `YYYY{sep}MM{sep}DD` (例: 2025-12-02, 2025/12/02)
- FormatDateTime: `YYYY{sep}MM{sep}DD HH:mm:ss` (例: 2025-12-02 14:35:10)
## 注意事項
1. **預設分隔符**: 如果不提供 `separator` 參數,預設使用 `"-"`
2. **自動遞迴處理**: `ConvertDatesInObject` 會自動處理巢狀物件和陣列
3. **不改變原物件**: 方法會回傳新的物件,不會修改原始資料
4. **安全性**: 包含空值檢查和錯誤處理
5. **效能**: 只處理符合 `/Date(` 格式的字串,避免不必要的轉換
## 完整範例
```javascript
let app = null;
function CreateApp() {
app = new Vue({
el: "#APP",
mixins: [DateMixin],
data: {
Items: []
},
methods: {
LoadData() {
axios.post('/Services/MyService.asmx/GetData', {})
.then(response => {
const result = response.data.d;
if (result.success) {
// 使用預設分隔符 "-" (適用於 HTML5 date input)
this.Items = this.ConvertDatesInObject(result.data);
// 或使用 "/" 分隔符 (適用於顯示)
// this.Items = this.ConvertDatesInObject(result.data, '/');
}
});
},
FormatSingleDate() {
// 格式化單一日期
const dateStr = '/Date(1764622110737)/';
console.log(this.FormatDate(dateStr)); // "2025-12-02"
console.log(this.FormatDate(dateStr, '/')); // "2025/12/02"
}
}
});
}
```
## HTML5 Date Input 整合
當使用 HTML5 的 `<input type="date">` 時,**必須**使用 `"-"` 分隔符:
```html
<input type="date" v-model="EditingItem.生效日期"/>
```
```javascript
// 載入時使用 "-" 分隔符
this.Items = this.ConvertDatesInObject(result.data, '-');
// 或省略參數使用預設值 "-"
this.Items = this.ConvertDatesInObject(result.data);
+127
View File
@@ -0,0 +1,127 @@
// Vue Mixin - 日期轉換工具
// 用於處理 ASP.NET ASMX Web Service 回傳的日期格式
var DateMixin = {
methods: {
/**
* 格式化日期
* @param {string|Date} dateStr - 日期字串或 Date 物件
* @param {string} separator - 日期分隔符預設為 "-"
* @returns {string} 格式化後的日期字串
*/
FormatDate: function(dateStr, separator) {
if (!dateStr) return '';
// 預設分隔符為 "-"
if (separator === undefined) {
separator = '-';
}
// 處理 ASP.NET ASMX 的日期格式 /Date(1764622110737)/
if (typeof dateStr === 'string' && dateStr.indexOf('/Date(') === 0) {
var timestamp = parseInt(dateStr.match(/-?\d+/)[0]);
var date = new Date(timestamp);
var year = String(date.getFullYear()).padStart(4, '0');
var month = String(date.getMonth() + 1).padStart(2, '0');
var day = String(date.getDate()).padStart(2, '0');
return year + separator + month + separator + day;
}
// 如果已經是一般的 Date 物件或其他格式
var date = new Date(dateStr);
if (!isNaN(date.getTime())) {
var year = String(date.getFullYear()).padStart(4, '0');
var month = String(date.getMonth() + 1).padStart(2, '0');
var day = String(date.getDate()).padStart(2, '0');
return year + separator + month + separator + day;
}
return dateStr;
},
/**
* 遍歷物件或陣列自動轉換所有符合 ASP.NET ASMX 日期格式的屬性
* @param {Object|Array} obj - 要處理的物件或陣列
* @param {string} separator - 日期分隔符預設為 "-"
* @returns {Object|Array} 處理後的物件或陣列
*/
ConvertDatesInObject: function(obj, separator) {
if (!obj) return obj;
// 預設分隔符為 "-"
if (separator === undefined) {
separator = '-';
}
// 處理陣列
if (Array.isArray(obj)) {
return obj.map(function(item) {
return this.ConvertDatesInObject(item, separator);
}.bind(this));
}
// 處理物件
if (typeof obj === 'object') {
var result = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var value = obj[key];
// 檢查是否為 ASP.NET ASMX 日期格式
if (typeof value === 'string' && value.indexOf('/Date(') === 0) {
result[key] = this.FormatDate(value, separator);
}
// 遞迴處理巢狀物件或陣列
else if (value !== null && typeof value === 'object') {
result[key] = this.ConvertDatesInObject(value, separator);
}
else {
result[key] = value;
}
}
}
return result;
}
return obj;
},
/**
* 格式化日期時間
* @param {string|Date} dateStr - 日期字串或 Date 物件
* @param {string} separator - 日期分隔符預設為 "-"
* @returns {string} 格式化後的日期時間字串
*/
FormatDateTime: function(dateStr, separator) {
if (!dateStr) return '';
// 預設分隔符為 "-"
if (separator === undefined) {
separator = '-';
}
var date;
// 處理 ASP.NET ASMX 的日期格式 /Date(1764622110737)/
if (typeof dateStr === 'string' && dateStr.indexOf('/Date(') === 0) {
var timestamp = parseInt(dateStr.match(/-?\d+/)[0]);
date = new Date(timestamp);
} else {
date = new Date(dateStr);
}
if (isNaN(date.getTime())) {
return dateStr;
}
var year = String(date.getFullYear()).padStart(4, '0');
var month = String(date.getMonth() + 1).padStart(2, '0');
var day = String(date.getDate()).padStart(2, '0');
var hours = String(date.getHours()).padStart(2, '0');
var minutes = String(date.getMinutes()).padStart(2, '0');
var seconds = String(date.getSeconds()).padStart(2, '0');
return year + separator + month + separator + day + ' ' + hours + ':' + minutes + ':' + seconds;
}
}
};
@@ -0,0 +1,98 @@
// Vue Mixin - 訊息提示功能
// 提供浮動訊息提示 (Toast) 功能
// 訊息類型常數
var ToastType = {
SUCCESS: 'success',
ERROR: 'error',
WARNING: 'warning',
INFO: 'info'
};
var ToastMixin = {
data: function() {
return {
// 訊息提示相關
ShowMessage: false,
MessageText: "",
MessageType: ToastType.SUCCESS,
// 預設顯示時長(ms)
defaultDuration: 5000,
// 將常數暴露給 Vue 實例使用
ToastType: ToastType
};
},
methods: {
/**
* 顯示訊息提示
* @param {string} message - 訊息內容
* @param {string} type - 訊息類型: ToastType.SUCCESS, ToastType.ERROR, ToastType.WARNING, ToastType.INFO
* @param {number} duration - 顯示時長毫秒預設 3000ms
*/
ShowToast: function(message, type, duration) {
if (type === undefined) {
type = ToastType.SUCCESS;
}
if (duration === undefined) {
duration = this.defaultDuration;
}
this.MessageText = message;
this.MessageType = type;
this.ShowMessage = true;
var self = this;
setTimeout(function() {
self.ShowMessage = false;
}, duration);
},
/**
* 顯示成功訊息
* @param {string} message - 訊息內容
* @param {number} duration - 顯示時長毫秒預設 3000ms
*/
ShowSuccess: function(message, duration) {
this.ShowToast(message, ToastType.SUCCESS, duration);
},
/**
* 顯示錯誤訊息
* @param {string} message - 訊息內容
* @param {number} duration - 顯示時長毫秒預設 3000ms
*/
ShowError: function(message, duration) {
this.ShowToast(message, ToastType.ERROR, duration);
},
/**
* 顯示警告訊息
* @param {string} message - 訊息內容
* @param {number} duration - 顯示時長毫秒預設 4000ms
*/
ShowWarning: function(message, duration) {
if (duration === undefined) {
duration = this.defaultDuration;
}
this.ShowToast(message, ToastType.WARNING, duration);
},
/**
* 顯示資訊訊息
* @param {string} message - 訊息內容
* @param {number} duration - 顯示時長毫秒預設 3000ms
*/
ShowInfo: function(message, duration) {
this.ShowToast(message, ToastType.INFO, duration);
},
/**
* 隱藏訊息提示
*/
HideToast: function() {
this.ShowMessage = false;
}
}
};
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
if (typeof(Sys.Browser.WebKit) == "undefined") {
Sys.Browser.WebKit = {};
}
if (navigator.userAgent.indexOf("WebKit/") > -1) {
Sys.Browser.agent = Sys.Browser.WebKit;
Sys.Browser.version = parseFloat(navigator.userAgent.match(/Webkit\/(\d+(\.\d+)?)/)[1]);
Sys.Browser.name = "WebKit";
}
+245
View File
@@ -0,0 +1,245 @@
let app = null;
let V人員類別 = null;
let V講師編號 = null;
let V憑證類別 = [];
let V扣繳類別 = [];
let V銀行分行 = [];
function CreatePaymentSettingApp() {
const loadMarker = document.getElementById('PaymentSettingAPP');
if (loadMarker) {
DoCreatePaymentSettingApp();
}
}
function DoCreatePaymentSettingApp() {
app = new Vue({
el: "#PaymentSettingAPP",
mixins: [DateMixin, ToastMixin, ApiMixin],
data: {
// 分頁相關
PageIndex: 1,
PageSize: 50,
Total: 0,
Items: [],
// 輸入參數
人員類別: "",
講師編號: "",
// 下拉選單資料
憑證類別Options: [],
扣繳類別Options: [],
// 新增/編輯狀態
Appending: false,
Editing: false,
// 銀行分行浮動選單相關
銀行分行: [],
ShowBankDropdown: false,
FilteredBankBranches: [],
// 編輯用空白物件範本
EmptyItem: {
編號: 0,
講師_非講師編號: "",
生效日期: "",
憑證類別: "",
扣繳類別: "",
匯款戶名: "",
匯款帳號: "",
銀行分行: "",
銀行分行代碼: "",
備註: ""
},
// 目前正在編輯的項目
EditingItem: {},
// 編輯表單錯誤訊息
EditErrorMessages: {
講師_非講師編號: "",
生效日期: "",
憑證類別: "",
扣繳類別: "",
匯款戶名: "",
匯款帳號: "",
銀行分行: "",
銀行分行代碼: "",
備註: "",
Clear() {
this.講師_非講師編號 = "";
this.生效日期 = "";
this.憑證類別 = "";
this.扣繳類別 = "";
this.匯款戶名 = "";
this.匯款帳號 = "";
this.銀行分行 = "";
this.銀行分行代碼 = "";
this.備註 = "";
}
}
},
created() {
// 從全域變數取得初始資料
this.人員類別 = V人員類別;
this.講師編號 = V講師編號;
this.講師姓名 = V講師姓名;
this.憑證類別Options = V憑證類別;
this.扣繳類別Options = V扣繳類別;
this.銀行分行 = V銀行分行;
},
mounted() {
this.SelectPage();
},
methods: {
// === 資料載入方法 ===
async SelectPage() {
try {
// 使用 ASMX Web Service
const result = await this.callApiWithErrorMsg('付款設定', 'SelectPage', {
人員類別: this.人員類別,
講師編號: this.講師編號,
PageIndex: this.PageIndex,
PageSize: this.PageSize
}, '載入資料');
const items = this.ConvertDatesInObject(result.Data || [], '-');
this.Items = items;
this.Total = result.count || 0;
} catch (error) {
console.error('SelectPage error:', error);
}
},
// === 新增/編輯/刪除方法 ===
Append() {
this.EditingItem = JSON.parse(JSON.stringify(this.EmptyItem));
this.EditingItem.講師_非講師編號 = this.講師編號;
this.EditingItem.備註 = '更新內容:'
this.Appending = true;
},
Edit(item, index) {
this.EditingItem = JSON.parse(JSON.stringify(item));
this.EditingItem.Index = index;
this.Editing = true;
},
async Save() {
this.EditErrorMessages.Clear();
let messages = [];
// 驗證資料
if (!this.EditingItem.生效日期) {
this.EditErrorMessages.生效日期 = '請填寫';
messages.push('請填寫生效日期');
}
if (!this.EditingItem.憑證類別) {
this.EditErrorMessages.憑證類別 = '請選擇';
messages.push('請選擇憑證類別');
}
if (!this.EditingItem.扣繳類別) {
this.EditErrorMessages.扣繳類別 = '請選擇';
messages.push('請選擇扣繳類別');
}
if (!this.EditingItem.備註) {
this.EditErrorMessages.備註 = '請填寫';
messages.push('請填寫備註');
}
if (this.EditingItem.備註.length > 100) {
this.EditErrorMessages.備註 = '最多 100 個字';
messages.push('備註最長 100 個字');
}
if (messages.length > 0) {
this.ShowWarning(messages.join('、'));
return;
}
const isNew = this.Appending;
try {
await this.callApiWithErrorMsg('付款設定', 'Save',
{
itemStr: JSON.stringify(this.EditingItem),
isNew: isNew
},
'儲存'
);
this.ShowSuccess('儲存成功');
this.Appending = false;
this.Editing = false;
this.EditingItem = {};
this.SelectPage(); // 重新載入資料
} catch (error) {
console.error('付款設定 Save error:', error);
this.ShowError('儲存時發生錯誤: ' + (error.message || '請檢查網路連線'));
}
},
Cancel() {
this.EditingItem = {};
this.Appending = false;
this.Editing = false;
},
async Delete(item, index) {
if (!confirm('確定要刪除此筆資料?')) return;
try {
await this.callApiWithErrorMsg('付款設定', 'Delete',
{
編號: item.編號,
DB_APPNO: item.DB_APPNO
},
'儲存'
);
this.ShowSuccess('刪除成功');
this.SelectPage(); // 重新載入資料
} catch (error) {
console.error('付款設定 Delete error:', error);
this.ShowError('刪除時發生錯誤: ' + (error.message || '請檢查網路連線'));
}
},
GetOptionName(options, value) {
const option = options.find(o => o.Value === value);
return option ? option.Name : '';
},
// === 銀行分行浮動選單方法 ===
FilterBankBranch() {
const keyword = this.EditingItem.銀行分行代碼;
if (!keyword || keyword.trim() === '') {
this.FilteredBankBranches = this.銀行分行.slice(0, 20);
return;
}
const lowerKeyword = keyword.toLowerCase();
this.FilteredBankBranches = this.銀行分行.filter(branch => {
return branch.Value.toLowerCase().includes(lowerKeyword) ||
branch.Name.toLowerCase().includes(lowerKeyword);
}).slice(0, 20);
},
ShowBankBranchList() {
this.FilterBankBranch();
this.ShowBankDropdown = true;
},
HideBankBranchList() {
setTimeout(() => {
this.ShowBankDropdown = false;
}, 200);
},
SelectBankBranch(branch) {
this.EditingItem.銀行分行代碼 = branch.Value;
this.EditingItem.銀行分行 = branch.Name;
this.ShowBankDropdown = false;
}
}
});
}
@@ -0,0 +1,455 @@
function CreateTrainFareApp() {
const loadMarker = document.getElementById('TrainFareAPP');
if (loadMarker) {
DoCreateTrainFareApp();
}
}
function DoCreateTrainFareApp() {
trainFareApp = new Vue({
el: "#TrainFareAPP",
mixins: [DateMixin, ToastMixin, ApiMixin],
data: {
versions: [],
selectedVersionId: '',
isEditMode: false,
originStations: [],
destStations: [],
fareData: {},
editFareData: {},
loading: false
},
mounted() {
this.LoadVersions();
},
methods: {
async LoadVersions() {
try {
const result = await this.callApiWithErrorMsg('火車票價維護', 'GetVersions', {}, '載入版本清單');
this.versions = result.Data || [];
if (!this.selectedVersionId && this.versions.length > 0) {
this.selectedVersionId = this.versions[0].Value;
this.LoadFareTable();
}
} catch (error) {
console.error('LoadVersions error:', error);
}
},
async LoadFareTable() {
if (!this.selectedVersionId) {
this.originStations = [];
this.destStations = [];
this.fareData = {};
return;
}
try {
this.loading = true;
const result = await this.callApiWithErrorMsg(
'火車票價維護',
'GetFareTable',
{ versionId: this.selectedVersionId },
'載入票價表'
);
const data = result.Data || {};
this.originStations = data.originStations || [];
this.destStations = data.destStations || [];
this.fareData = data.fares || {};
this.editFareData = JSON.parse(JSON.stringify(this.fareData));
this.isEditMode = false;
} catch (error) {
console.error('LoadFareTable error:', error);
} finally {
this.loading = false;
}
},
EnterEditMode() {
this.editFareData = JSON.parse(JSON.stringify(this.fareData));
this.isEditMode = true;
},
CancelEdit() {
this.editFareData = {};
this.isEditMode = false;
},
async SaveChanges() {
if (!this.selectedVersionId) {
this.ShowWarning('請先選擇版本');
return;
}
try {
this.loading = true;
await this.callApiWithErrorMsg(
'火車票價維護',
'SaveFareTable',
{
versionId: this.selectedVersionId,
fares: JSON.stringify(this.editFareData)
},
'儲存'
);
this.ShowSuccess('儲存成功');
this.isEditMode = false;
this.LoadVersions();
this.LoadFareTable();
} catch (error) {
console.error('SaveChanges error:', error);
} finally {
this.loading = false;
}
},
async DeleteVersion() {
if (!this.selectedVersionId) {
this.ShowWarning('請先選擇版本');
return;
}
if (!confirm(`確定要刪除版本 ${this.selectedVersionId} 嗎?`)) {
return;
}
try {
this.loading = true;
await this.callApiWithErrorMsg(
'火車票價維護',
'DeleteVersion',
{ versionId: this.selectedVersionId },
'刪除'
);
this.ShowSuccess('版本刪除成功');
this.selectedVersionId = '';
this.LoadVersions();
this.LoadFareTable();
} catch (error) {
console.error('DeleteVersion error:', error);
} finally {
this.loading = false;
}
},
GetFare(origin, destination) {
const key = `${origin}_${destination}`;
return this.fareData[key] || '';
},
GetFareInput(origin, destination) {
if (origin === destination) return null;
const key = `${origin}_${destination}`;
return this.editFareData[key] || null;
},
SetFareInput(origin, destination, value) {
if (origin === destination) return;
const key = `${origin}_${destination}`;
// 轉換為整數,空值或非數字時設為 0
const numValue = parseInt(value) || 0;
this.$set(this.editFareData, key, numValue);
}
}
});
}
function CreateTrainFareImportExportApp() {
const loadMarker = document.getElementById('TrainFareImportExportAPP');
if (loadMarker) {
DoCreateTrainFareImportExportApp();
}
}
function DoCreateTrainFareImportExportApp() {
const importExportApp = new Vue({
el: "#TrainFareImportExportAPP",
mixins: [DateMixin, ToastMixin, ApiMixin, Base64Mixin],
data: {
activeTab: 'export',
versions: [],
exportVersionId: '',
exportResponse: null,
selectedFile: null,
uploadedFilePath: null,
importPreviewData: {},
importOriginStations: [],
importDestStations: [],
checkResult: null,
loading: false
},
computed: {
isImportButtonDisabled() {
// 匯入按鈕禁用條件:
// 1. 沒有上傳檔案
// 2. 沒有驗證結果
// 3. 驗證失敗
// 4. 驗證未完成(success 不為 true
return !this.uploadedFilePath ||
!this.checkResult ||
!this.checkResult.success;
},
exportOriginStations() {
return this.exportResponse?.Data?.originStations || [];
},
exportDestStations() {
return this.exportResponse?.Data?.destStations || [];
},
exportData() {
return this.exportResponse?.Data?.fares || {};
}
},
mounted() {
this.LoadVersions();
},
watch: {
exportVersionId() {
// 版本選擇改變時,重置匯出數據
this.exportResponse = null;
}
},
methods: {
async LoadVersions() {
try {
const result = await this.callApiWithErrorMsg('火車票價維護', 'GetVersions', {}, '載入版本清單');
this.versions = result.Data || [];
} catch (error) {
console.error('LoadVersions error:', error);
}
},
async DownloadTemplate() {
try {
const result = await this.callApiWithErrorMsg(
'火車票價維護',
'GetExcelTemplate',
{},
'下載範本'
);
// 下載 Excel 檔案
const excelData = result.Data;
if (!excelData) {
this.ShowError('下載失敗:未取得範本檔案資料');
return;
}
const blob = this.Base64ToBlob(excelData);
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = '火車票價_匯入範本.xlsx';
link.click();
this.ShowSuccess('範本下載成功');
} catch (error) {
console.error('DownloadTemplate error:', error);
}
},
async ExportData() {
if (!this.exportVersionId) {
this.ShowWarning('請選擇版本');
return;
}
try {
this.loading = true;
const result = await this.callApiWithErrorMsg(
'火車票價維護',
'ExportFareData',
{ versionId: this.exportVersionId },
'匯出'
);
// 儲存完整響應以供計算屬性使用
this.exportResponse = result;
// 下載 Excel 檔案
const excelData = result.ExcelData;
if (!excelData) {
this.ShowError('匯出失敗:未取得 Excel 檔案資料');
return;
}
const blob = this.Base64ToBlob(excelData);
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `火車票價_版本${this.exportVersionId}_${new Date().getTime()}.xlsx`;
link.click();
this.ShowSuccess('匯出成功');
} catch (error) {
console.error('ExportData error:', error);
} finally {
this.loading = false;
}
},
OnFileSelected(event) {
const file = event.target.files[0];
if (!file) return;
this.selectedFile = file;
this.checkResult = null;
this.importPreviewData = {};
this.importOriginStations = [];
this.importDestStations = [];
this.UploadFile(file);
},
UploadFile(file) {
if (!file) {
this.ShowError('請選擇檔案');
return;
}
try {
const formData = new FormData();
formData.append('file', file);
// 使用 .ashx Handler 上傳檔案
const uploadUrl = `${API_BASE}/TrainFareUploadHandler.ashx`;
fetch(uploadUrl, {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.text();
})
.then(text => {
try {
const result = JSON.parse(text);
if (result && result.Success) {
const uploadedFilePath = result.Data;
this.ShowSuccess('檔案上傳成功');
// 立即驗證上傳的檔案
this.ValidateUploadedFile(uploadedFilePath);
} else {
const errorMsg = result ? result.Message : '上傳失敗';
this.ShowError('上傳失敗:' + errorMsg);
}
} catch (e) {
console.error('Parse response error:', e);
console.error('Response text:', text);
this.ShowError('檔案上傳失敗:回應格式錯誤');
}
})
.catch(error => {
console.error('Upload error:', error);
this.ShowError('檔案上傳失敗:' + error.message);
});
} catch (error) {
console.error('UploadFile error:', error);
this.ShowError('檔案上傳出錯:' + error.message);
}
},
async ValidateUploadedFile(filePath) {
try {
const result = await this.callApiWithErrorMsg(
'火車票價維護',
'ValidateImportFile',
{ filePath: filePath },
'驗證檔案'
);
this.checkResult = {
success: result.Success,
errors: result.Data?.Errors || []
};
if (this.checkResult.success) {
// 解析預覽資料
this.uploadedFilePath = filePath;
this.importPreviewData = result.Data?.Fares || {};
this.importOriginStations = result.Data?.OriginStations || [];
this.importDestStations = result.Data?.DestStations || [];
this.ShowSuccess('檔案驗證成功');
} else {
this.ShowError('檔案驗證失敗');
this.uploadedFilePath = null;
}
} catch (error) {
console.error('ValidateUploadedFile error:', error);
this.uploadedFilePath = null;
}
},
CancelImport() {
this.selectedFile = null;
this.uploadedFilePath = null;
this.checkResult = null;
this.importPreviewData = {};
this.importOriginStations = [];
this.importDestStations = [];
const fileInput = document.querySelector('input[type="file"]');
if (fileInput) {
fileInput.value = '';
}
},
async ImportData() {
// 匯入按鈕已被禁用,不應該執行到這裡
// 但仍保留安全檢查作為雙重保障
if (!this.uploadedFilePath || !this.checkResult || !this.checkResult.success) {
this.ShowWarning('請先上傳並驗證檔案');
return;
}
try {
this.loading = true;
const result = await this.callApiWithErrorMsg(
'火車票價維護',
'ImportFareData',
{
mode: 'new',
versionId: null,
filePath: this.uploadedFilePath
},
'匯入'
);
this.ShowSuccess('匯入成功');
this.CancelImport();
// 更新版本清單
if (trainFareApp) {
trainFareApp.LoadVersions();
}
this.LoadVersions();
} catch (error) {
console.error('ImportData error:', error);
} finally {
this.loading = false;
}
},
GetExportFare(origin, destination) {
const key = `${origin}_${destination}`;
return this.exportData[key] || '';
},
GetImportFare(origin, destination) {
const key = `${origin}_${destination}`;
return this.importPreviewData[key] || '';
}
}
});
}
//// 頁面載入時建立 Vue 應用
//document.addEventListener('DOMContentLoaded', function () {
// CreateTrainFareApp();
// CreateTrainFareImportExportApp();
//});
@@ -0,0 +1,641 @@
let taxReceiptApp = null;
function CreateTaxReceiptApp() {
const loadMarker = document.getElementById('taxReceiptApp');
if (loadMarker) {
DoCreateTaxReceiptApp();
}
}
function DoCreateTaxReceiptApp() {
taxReceiptApp = new Vue({
el: '#taxReceiptApp',
mixins: [ToastMixin, ApiMixin],
data: {
activeTab: 0,
currentYear: '',
currentClosingMonth: '',
newClosingMonth: '',
loading: false,
message: '',
messageType: 'info',
summaryTable: {
startDate: '',
endDate: '',
identity: '',
paymentMethod: '',
identityOptions: [],
paymentMethodOptions: [],
dataList: [],
loading: false,
message: '',
messageType: 'info'
},
detailTable: {
startDate: '',
endDate: '',
identity: '',
certificateType: '',
identityOptions: [],
certificateTypeOptions: [],
dataList: [],
selectAll: false,
senderEmail: '',
emailSubject: '',
emailBody: '',
loading: false,
message: '',
messageType: 'info',
emailMessage: '',
emailMessageType: 'info',
previewData: [],
showEmailPreview: false
}
},
created() {
this.initializeData();
this.loadSummaryTableDropdowns();
this.loadDetailTableDropdowns();
// 確保預覽窗口初始為關閉狀態
this.detailTable.showEmailPreview = false;
this.detailTable.previewData = [];
},
mounted() {
this.loadClosingMonthData();
// 再次確保預覽窗口初始為關閉狀態
this.$nextTick(() => {
this.detailTable.showEmailPreview = false;
this.detailTable.previewData = [];
});
},
methods: {
initializeData() {
// 取得現行年度
const now = new Date();
this.currentYear = now.getFullYear().toString();
},
async loadClosingMonthData() {
try {
const result = await this.callApiWithErrorMsg(
'領據處理',
'GetClosingMonth',
{},
'載入關帳年月資料'
);
if (result && result.Data) {
this.currentClosingMonth = result.Data.currentClosingMonth || '';
// 初始化新關帳年月為目前月份
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
this.newClosingMonth = `${year}-${month}`;
}
} catch (error) {
console.error('載入關帳年月失敗:', error);
this.showMessage('載入關帳年月失敗: ' + (error.message || '請檢查網路連線'), 'danger');
}
},
validateMonth() {
if (!this.newClosingMonth) {
return;
}
// 驗證格式 YYYY-MM
const pattern = /^\d{4}-\d{2}$/;
if (!pattern.test(this.newClosingMonth)) {
this.showMessage('年月格式不正確,請使用 YYYY-MM 格式', 'danger');
this.newClosingMonth = '';
return;
}
// 驗證年份
const [year, month] = this.newClosingMonth.split('-');
const yearNum = parseInt(year);
const monthNum = parseInt(month);
// 月份範圍驗證
if (monthNum < 1 || monthNum > 12) {
this.showMessage('月份必須介於 01 到 12 之間', 'danger');
this.newClosingMonth = '';
return;
}
// 年份不能超過現在
const currentYear = new Date().getFullYear();
if (yearNum > currentYear) {
this.showMessage('年份不能超過現在', 'danger');
this.newClosingMonth = '';
return;
}
},
async saveClosingMonth() {
if (!this.newClosingMonth) {
this.showMessage('請輸入調整的關帳年月', 'warning');
return;
}
// 再次驗證
this.validateMonth();
if (!this.newClosingMonth) {
return;
}
// 確認操作
if (!confirm(`確認要將領據關帳年月調整為 ${this.newClosingMonth} 嗎?\n此操作將影響領據的修改權限。`)) {
return;
}
this.loading = true;
try {
const params = {
closingMonth: this.newClosingMonth
};
const result = await this.callApiWithErrorMsg(
'領據處理',
'UpdateClosingMonth',
params,
'保存關帳年月'
);
if (result && result.Success) {
this.showMessage('關帳年月已成功更新', 'success');
this.currentClosingMonth = this.newClosingMonth;
// 清除輸入欄位
setTimeout(() => {
this.clearForm();
}, 1500);
} else {
this.showMessage(result.Message || '保存失敗,請稍後重試', 'danger');
}
} catch (error) {
console.error('保存失敗:', error);
this.showMessage('保存失敗: ' + (error.message || '請檢查網路連線'), 'danger');
} finally {
this.loading = false;
}
},
clearForm() {
this.newClosingMonth = '';
this.message = '';
},
showMessage(text, type = 'info') {
this.message = text;
this.messageType = type;
// 3 秒後自動隱藏訊息
if (type === 'success' || type === 'info') {
setTimeout(() => {
this.message = '';
}, 3000);
}
},
async loadSummaryTableDropdowns() {
try {
// 加載身分別選項
const identityResult = await this.callApiWithErrorMsg(
'領據處理',
'GetDropdownLists',
{ key: '身分別' },
'載入身分別選項'
);
if (identityResult && identityResult.Data) {
this.summaryTable.identityOptions = identityResult.Data.filter(item => item.Value !== '');
}
// 加載付款方式選項
const paymentResult = await this.callApiWithErrorMsg(
'領據處理',
'GetDropdownLists',
{ key: '付款方式' },
'載入付款方式選項'
);
if (paymentResult && paymentResult.Data) {
this.summaryTable.paymentMethodOptions = paymentResult.Data.filter(item => item.Value !== '');
}
} catch (error) {
console.error('加載下拉清單失敗:', error);
}
},
async searchSummaryTable() {
this.summaryTable.loading = true;
this.summaryTable.message = '';
this.summaryTable.dataList = [];
try {
const params = {
起日: this.summaryTable.startDate ? this.parseDate(this.summaryTable.startDate) : null,
迄日: this.summaryTable.endDate ? this.parseDate(this.summaryTable.endDate) : null,
身分別: this.summaryTable.identity || null,
付款方式: this.summaryTable.paymentMethod || null,
輸出方式: 'DATA'
};
const result = await this.callApiWithErrorMsg(
'領據處理',
'SearchSummaryTable',
params,
'查詢個人費用彙整表'
);
if (result && result.Success) {
this.summaryTable.dataList = result.Data || [];
if (this.summaryTable.dataList.length === 0) {
this.summaryTable.message = '查詢完成,但未找到符合條件的資料';
this.summaryTable.messageType = 'info';
} else {
this.summaryTable.message = `查詢完成,共找到 ${this.summaryTable.dataList.length} 筆資料`;
this.summaryTable.messageType = 'success';
}
} else {
this.summaryTable.message = result.Message || '查詢失敗,請稍後重試';
this.summaryTable.messageType = 'danger';
}
} catch (error) {
console.error('查詢失敗:', error);
this.summaryTable.message = '查詢失敗: ' + (error.message || '請檢查網路連線');
this.summaryTable.messageType = 'danger';
} finally {
this.summaryTable.loading = false;
}
},
async exportSummaryTable() {
if (this.summaryTable.dataList.length === 0) {
this.summaryTable.message = '沒有資料可以匯出,請先執行查詢';
this.summaryTable.messageType = 'warning';
return;
}
this.summaryTable.loading = true;
this.summaryTable.message = '';
try {
const params = {
起日: this.summaryTable.startDate ? this.parseDate(this.summaryTable.startDate) : null,
迄日: this.summaryTable.endDate ? this.parseDate(this.summaryTable.endDate) : null,
身分別: this.summaryTable.identity || null,
付款方式: this.summaryTable.paymentMethod || null,
輸出方式: 'EXPORT'
};
const result = await this.callApiWithErrorMsg(
'領據處理',
'SearchSummaryTable',
params,
'匯出個人費用彙整表'
);
if (result && result.Success && result.Data) {
// 下載 Excel 檔案
const excelData = result.Data;
if (excelData.excelData && excelData.fileName) {
this.downloadExcel(excelData.excelData, excelData.fileName);
this.summaryTable.message = '匯出成功';
this.summaryTable.messageType = 'success';
} else {
this.summaryTable.message = '匯出失敗:無法取得檔案';
this.summaryTable.messageType = 'danger';
}
} else {
this.summaryTable.message = result.Message || '匯出失敗,請稍後重試';
this.summaryTable.messageType = 'danger';
}
} catch (error) {
console.error('匯出失敗:', error);
this.summaryTable.message = '匯出失敗: ' + (error.message || '請檢查網路連線');
this.summaryTable.messageType = 'danger';
} finally {
this.summaryTable.loading = false;
}
},
parseDate(dateString) {
if (!dateString) return null;
const date = new Date(dateString);
return date.toISOString().split('T')[0];
},
formatCurrency(value) {
if (!value) return '0';
return new Intl.NumberFormat('zh-TW', {
style: 'currency',
currency: 'TWD',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(value);
},
downloadExcel(excelBase64, fileName) {
const binaryString = atob(excelBase64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
const blob = new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(link);
},
async loadDetailTableDropdowns() {
try {
// 加載身分別選項
const identityResult = await this.callApiWithErrorMsg(
'領據處理',
'GetDropdownLists',
{ key: '身分別' },
'載入身分別選項'
);
if (identityResult && identityResult.Data) {
this.detailTable.identityOptions = identityResult.Data.filter(item => item.Value !== '');
}
// 加載憑證類別選項
const certResult = await this.callApiWithErrorMsg(
'領據處理',
'GetDropdownLists',
{ key: '憑證類別' },
'載入憑證類別選項'
);
if (certResult && certResult.Data) {
this.detailTable.certificateTypeOptions = certResult.Data.filter(item => item.Value !== '');
}
} catch (error) {
console.error('加載下拉清單失敗:', error);
}
},
async searchDetailTable() {
this.detailTable.loading = true;
this.detailTable.message = '';
this.detailTable.dataList = [];
this.detailTable.selectAll = false;
try {
const params = {
起日: this.detailTable.startDate ? this.parseDate(this.detailTable.startDate) : null,
迄日: this.detailTable.endDate ? this.parseDate(this.detailTable.endDate) : null,
身分別: this.detailTable.identity || null,
憑證類別: this.detailTable.certificateType || null,
輸出方式: 'DATA'
};
const result = await this.callApiWithErrorMsg(
'領據處理',
'SearchDetailTable',
params,
'查詢扣繳明細'
);
if (result && result.Success) {
var resultData = (result.Data || []).map(item => ({
...item,
selected: false,
showDetail: false
}));
this.detailTable.dataList = resultData;
if (this.detailTable.dataList.length === 0) {
this.detailTable.message = '查詢完成,但未找到符合條件的資料';
this.detailTable.messageType = 'info';
} else {
this.detailTable.message = `查詢完成,共找到 ${this.detailTable.dataList.length} 筆資料`;
this.detailTable.messageType = 'success';
}
} else {
this.detailTable.message = result.Message || '查詢失敗,請稍後重試';
this.detailTable.messageType = 'danger';
}
} catch (error) {
console.error('查詢失敗:', error);
this.detailTable.message = '查詢失敗: ' + (error.message || '請檢查網路連線');
this.detailTable.messageType = 'danger';
} finally {
this.detailTable.loading = false;
}
},
async exportDetailTable(kind) {
if (this.detailTable.dataList.length === 0) {
this.detailTable.message = '沒有資料可以匯出,請先執行查詢';
this.detailTable.messageType = 'warning';
return;
}
this.detailTable.loading = true;
this.detailTable.message = '';
try {
const params = {
起日: this.detailTable.startDate ? this.parseDate(this.detailTable.startDate) : null,
迄日: this.detailTable.endDate ? this.parseDate(this.detailTable.endDate) : null,
身分別: this.detailTable.identity || null,
憑證類別: this.detailTable.certificateType || null,
輸出方式: `EXPORT_${kind}`,
};
const result = await this.callApiWithErrorMsg(
'領據處理',
'SearchDetailTable',
params,
'匯出扣繳明細'
);
if (result && result.Success && result.Data) {
const excelData = result.Data;
if (excelData.excelData && excelData.fileName) {
this.downloadExcel(excelData.excelData, excelData.fileName);
this.detailTable.message = '匯出成功';
this.detailTable.messageType = 'success';
} else {
this.detailTable.message = '匯出失敗:無法取得檔案';
this.detailTable.messageType = 'danger';
}
} else {
this.detailTable.message = result.Message || '匯出失敗,請稍後重試';
this.detailTable.messageType = 'danger';
}
} catch (error) {
console.error('匯出失敗:', error);
this.detailTable.message = '匯出失敗: ' + (error.message || '請檢查網路連線');
this.detailTable.messageType = 'danger';
} finally {
this.detailTable.loading = false;
}
},
toggleSelectAll() {
this.detailTable.dataList.forEach(item => {
item.selected = this.detailTable.selectAll;
});
},
async previewEmail() {
// 檢查是否有選中的項目
const selectedItems = this.detailTable.dataList.filter(item => item.selected);
if (selectedItems.length === 0) {
this.detailTable.emailMessage = '請先勾選要發送的項目';
this.detailTable.emailMessageType = 'warning';
return;
}
// 檢查郵件設定
if (!this.detailTable.senderEmail) {
this.detailTable.emailMessage = '請輸入寄件人郵箱';
this.detailTable.emailMessageType = 'warning';
return;
}
if (!this.detailTable.emailSubject) {
this.detailTable.emailMessage = '請輸入郵件主旨';
this.detailTable.emailMessageType = 'warning';
return;
}
if (!this.detailTable.emailBody) {
this.detailTable.emailMessage = '請輸入郵件內文';
this.detailTable.emailMessageType = 'warning';
return;
}
this.detailTable.loading = true;
this.detailTable.emailMessage = '';
try {
// 提取選中項目的編號作為講師非講師編號
const 講師非講師編號 = selectedItems.map(item => item.講師_非講師編號);
const params = {
起日: this.detailTable.startDate ? this.parseDate(this.detailTable.startDate) : null,
迄日: this.detailTable.endDate ? this.parseDate(this.detailTable.endDate) : null,
身分別: this.detailTable.identity || null,
憑證類別: this.detailTable.certificateType || null,
輸出方式: 'PREVIEW',
講師非講師編號: 講師非講師編號,
寄件人郵箱: this.detailTable.senderEmail,
郵件主旨: this.detailTable.emailSubject,
郵件內文: this.detailTable.emailBody
};
const result = await this.callApiWithErrorMsg(
'領據處理',
'SendEmail',
params,
'預覽郵件'
);
if (result && result.Success && result.Data) {
this.detailTable.previewData = result.Data || [];
this.detailTable.showEmailPreview = true;
this.detailTable.emailMessage = '';
} else {
this.detailTable.emailMessage = result.Message || '預覽失敗,請稍後重試';
this.detailTable.emailMessageType = 'danger';
}
} catch (error) {
console.error('預覽失敗:', error);
this.detailTable.emailMessage = '預覽失敗: ' + (error.message || '請檢查網路連線');
this.detailTable.emailMessageType = 'danger';
} finally {
this.detailTable.loading = false;
}
},
closeEmailPreview() {
this.detailTable.showEmailPreview = false;
this.detailTable.previewData = [];
},
async sendEmail() {
// 檢查是否有選中的項目
const selectedItems = this.detailTable.dataList.filter(item => item.selected);
if (selectedItems.length === 0) {
this.detailTable.emailMessage = '請先勾選要發送的項目';
this.detailTable.emailMessageType = 'warning';
return;
}
if (!confirm(`確認要發送郵件給 ${selectedItems.length} 位收件者嗎?`)) {
return;
}
this.detailTable.loading = true;
this.detailTable.emailMessage = '';
try {
// 提取選中項目的編號作為講師非講師編號
const 講師非講師編號 = selectedItems.map(item => item.講師_非講師編號);
const params = {
起日: this.detailTable.startDate ? this.parseDate(this.detailTable.startDate) : null,
迄日: this.detailTable.endDate ? this.parseDate(this.detailTable.endDate) : null,
身分別: this.detailTable.identity || null,
憑證類別: this.detailTable.certificateType || null,
輸出方式: 'SENDMAIL',
講師非講師編號: 講師非講師編號,
寄件人郵箱: this.detailTable.senderEmail,
郵件主旨: this.detailTable.emailSubject,
郵件內文: this.detailTable.emailBody
};
const result = await this.callApiWithErrorMsg(
'領據處理',
'SendEmail',
params,
'發送郵件'
);
if (result && result.Success) {
this.detailTable.emailMessage = `郵件已排定發送給 ${selectedItems.length} 位收件者`;
this.detailTable.emailMessageType = 'success';
// 關閉預覽小窗
this.detailTable.showEmailPreview = false;
this.detailTable.previewData = [];
// 清空勾選
this.detailTable.dataList.forEach(item => {
item.selected = false;
});
this.detailTable.selectAll = false;
} else {
this.detailTable.emailMessage = result.Message || '發送失敗,請稍後重試';
this.detailTable.emailMessageType = 'danger';
}
} catch (error) {
console.error('發送失敗:', error);
this.detailTable.emailMessage = '發送失敗: ' + (error.message || '請檢查網路連線');
this.detailTable.emailMessageType = 'danger';
} finally {
this.detailTable.loading = false;
}
}
},
computed: {
canSendEmail() {
const hasSelected = this.detailTable.dataList.some(item => item.selected);
const hasEmail = this.detailTable.senderEmail && this.detailTable.senderEmail.trim().length > 0;
const hasSubject = this.detailTable.emailSubject && this.detailTable.emailSubject.trim().length > 0;
const hasBody = this.detailTable.emailBody && this.detailTable.emailBody.trim().length > 0;
return hasSelected && hasEmail && hasSubject && hasBody && !this.detailTable.loading;
}
}
});
}
+207
View File
@@ -0,0 +1,207 @@
function CreateReceiptImportApp() {
const loadMarker = document.getElementById('receiptImportApp');
if (loadMarker) {
DoCreateReceiptImportApp();
}
}
function DoCreateReceiptImportApp() {
new Vue({
el: '#receiptImportApp',
mixins: [DateMixin, ToastMixin, ApiMixin, Base64Mixin],
data: {
selectedFile: null,
isProcessing: false,
validationMessages: [],
validationSuccess: false,
validatedRowCount: 0,
importMessages: [],
importSuccess: false,
importSuccessCount: 0,
importFailCount: 0,
importDone: false
},
methods: {
async downloadTemplate() {
try {
this.isProcessing = true;
const result = await this.callApiWithErrorMsg('領據處理', 'DownloadImportTemplate', {}, '下載範本');
const excelData = result.Data && result.Data.excelData;
if (!excelData) {
this.ShowError('下載失敗:未取得範本檔案資料');
return;
}
const blob = this.Base64ToBlob(excelData);
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = (result.Data && result.Data.fileName) || '領據資料匯入格式.xlsx';
link.click();
this.ShowSuccess('範本下載成功');
} catch (error) {
console.error('downloadTemplate error:', error);
} finally {
this.isProcessing = false;
}
},
onFileSelected(event) {
const file = event.target.files[0];
if (!file) return;
this.selectedFile = file;
this.validationMessages = [];
this.validationSuccess = false;
this.validatedRowCount = 0;
this.importMessages = [];
this.importSuccess = false;
this.importSuccessCount = 0;
this.importFailCount = 0;
this.importDone = false;
},
uploadFile() {
if (!this.selectedFile) {
this.ShowWarning('請先選擇檔案');
return;
}
this.isProcessing = true;
this.validationMessages = [];
this.validationSuccess = false;
this.validatedRowCount = 0;
const formData = new FormData();
formData.append('file', this.selectedFile);
const uploadUrl = API_BASE + '/ReceiptUploadHandler.ashx';
fetch(uploadUrl, {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error('HTTP error! status: ' + response.status);
}
return response.text();
})
.then(text => {
try {
const result = JSON.parse(text);
if (result && result.Success) {
const uploadedFilePath = result.Data;
this.ShowSuccess('檔案上傳成功');
this.ValidateUploadedFile(uploadedFilePath);
} else {
const errorMsg = result ? result.Message : '上傳失敗';
this.ShowError('上傳失敗:' + errorMsg);
this.isProcessing = false;
}
} catch (e) {
console.error('Parse response error:', e, 'Response text:', text);
this.ShowError('檔案上傳失敗:回應格式錯誤');
this.isProcessing = false;
}
})
.catch(error => {
console.error('Upload error:', error);
this.ShowError('檔案上傳失敗:' + error.message);
this.isProcessing = false;
});
},
async ValidateUploadedFile(filePath) {
try {
const result = await this.callApiWithErrorMsg('領據處理', 'ValidateImportFile', { filePath: filePath }, '驗證檔案');
const data = result.Data || {};
if (data.Success) {
this.validationSuccess = true;
this.validatedRowCount = data.RowCount || 0;
const warnings = data.Warnings || [];
if (warnings.length > 0) {
this.validationMessages = ['驗證通過,但有以下重複資料警告:'].concat(warnings);
this.ShowWarning('驗證通過,但有重複資料,請確認後再匯入');
} else {
this.validationMessages = ['驗證通過'];
this.ShowSuccess('檔案驗證成功');
}
} else {
this.validationSuccess = false;
this.validationMessages = data.Errors || ['驗證失敗'];
this.ShowError('檔案驗證失敗');
}
} catch (error) {
console.error('ValidateUploadedFile error:', error);
this.validationSuccess = false;
} finally {
this.isProcessing = false;
}
},
async importData() {
if (!this.validationSuccess) {
this.ShowWarning('請先上傳並驗證檔案');
return;
}
if (this.importDone) {
this.ShowWarning('匯入已完成,請點擊「重新匯入」開始新的匯入');
return;
}
try {
this.isProcessing = true;
const result = await this.callApiWithErrorMsg('領據處理', 'ImportReceiptData', {}, '匯入');
const data = result.Data || {};
const successCount = data.successCount || 0;
const failCount = data.failCount || 0;
const failedRows = data.failedRows || [];
this.importSuccessCount = successCount;
this.importFailCount = failCount;
this.importMessages = failedRows;
this.importSuccess = (failCount === 0);
if (this.importSuccess) {
this.ShowSuccess('匯入成功,共匯入 ' + successCount + ' 筆');
} else {
this.ShowWarning('匯入完成,成功 ' + successCount + ' 筆,失敗 ' + failCount + ' 筆');
}
this.importDone = true;
} catch (error) {
console.error('importData error:', error);
this.importSuccess = false;
} finally {
this.isProcessing = false;
}
},
resetImport() {
this.selectedFile = null;
this.validationMessages = [];
this.validationSuccess = false;
this.validatedRowCount = 0;
this.importMessages = [];
this.importSuccess = false;
this.importSuccessCount = 0;
this.importFailCount = 0;
this.importDone = false;
const fileInput = document.querySelector('#receiptImportApp input[type="file"]');
if (fileInput) {
fileInput.value = '';
}
},
}
});
}
@@ -0,0 +1,351 @@
function CreateReceiptSearchApp() {
const loadMarker = document.getElementById('receiptSearchApp');
if (loadMarker) {
DoCreateReceiptSearchApp();
}
}
function DoCreateReceiptSearchApp() {
window.__vms = window.__vms || {}
if (window.__vms.receiptSearchApp)
return;
console.log('Create receiptSearchApp');
window.__vms.receiptSearchApp = new Vue({
el: '#receiptSearchApp',
mixins: [DateMixin, ToastMixin, ApiMixin],
data: {
searchParams: {
計畫代號: '',
計畫名稱: '',
活動日期_1: '',
活動日期_2: '',
身分別: '',
講師_非講師編號: '',
憑證類別: '',
活動類別: '',
付款方式: ''
},
dropdown: {
身分別: [],
憑證類別: [],
付款方式: []
},
dataList: [],
selectedItems: [],
selectAll: false,
currentPage: 1,
pageSize: 20,
totalCount: 0,
totalPages: 0,
loading: false
},
created() {
// 從 AJAX 取得查詢用下拉選單資料
this.loadSearchDropdownLists();
// 取回保存的查詢條件
this.searchParams.計畫代號 = document.getElementById('ctl00_ContentPlaceHolder1_hid計畫代號').value;
this.searchParams.計畫名稱 = document.getElementById('ctl00_ContentPlaceHolder1_hid計畫名稱').value;
this.searchParams.活動日期_1 = document.getElementById('ctl00_ContentPlaceHolder1_hid活動日期_1').value;
this.searchParams.活動日期_2 = document.getElementById('ctl00_ContentPlaceHolder1_hid活動日期_2').value;
this.searchParams.身分別 = document.getElementById('ctl00_ContentPlaceHolder1_hid身分別').value;
this.searchParams.憑證類別 = document.getElementById('ctl00_ContentPlaceHolder1_hid憑證類別').value;
this.searchParams.活動類別 = document.getElementById('ctl00_ContentPlaceHolder1_hid活動類別').value;
this.searchParams.付款方式 = document.getElementById('ctl00_ContentPlaceHolder1_hid付款方式').value;
this.currentPage = 1 * document.getElementById('ctl00_ContentPlaceHolder1_hidCurrentPage').value || 1;
this.pageSize = 1 * document.getElementById('ctl00_ContentPlaceHolder1_hidPageSize').value || 20;
},
computed: {
displayPages() {
const pages = [];
const start = Math.max(1, this.currentPage - 2);
const end = Math.min(this.totalPages, this.currentPage + 2);
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
}
},
mounted() {
this.search();
},
methods: {
async loadSearchDropdownLists() {
this.dropdown.身分別 = await this.loadSearchDropdownList("身分別");
this.dropdown.憑證類別 = await this.loadSearchDropdownList("憑證類別");
this.dropdown.付款方式 = await this.loadSearchDropdownList("付款方式");
},
async loadSearchDropdownList(key) {
try {
const result = await this.callApiWithErrorMsg(
'領據處理',
'GetSearchDropdownLists',
{ key: key },
'載入下拉選單'
);
return result.Data || [];
}
catch(error) {
console.error('載入下拉選單失敗:', error);
return [];
}
},
clearSearch() {
this.searchParams.計畫代號 = '';
this.searchParams.計畫名稱 = '';
this.searchParams.活動日期_1 = '';
this.searchParams.活動日期_2 = '';
this.searchParams.身分別 = '';
this.searchParams.憑證類別 = '';
this.searchParams.活動類別 = '';
this.searchParams.付款方式 = '';
this.search();
},
async search() {
this.loading = true;
const params = {
pageIndex: this.currentPage,
pageSize: parseInt(this.pageSize),
活動日期_1: this.searchParams.活動日期_1 || null,
活動日期_2: this.searchParams.活動日期_2 || null,
身分別: this.searchParams.身分別 || null,
講師_非講師編號: this.searchParams.講師_非講師編號 || null,
憑證類別: this.searchParams.憑證類別 || null,
計畫代號: this.searchParams.計畫代號 || null,
計畫名稱: this.searchParams.計畫名稱 || null,
活動類別: this.searchParams.活動類別 || null,
付款方式: this.searchParams.付款方式 || null,
orderBy: ''
};
// 保存查詢條件
document.getElementById('ctl00_ContentPlaceHolder1_hid計畫代號').value = params.計畫代號;
document.getElementById('ctl00_ContentPlaceHolder1_hid計畫名稱').value = params.計畫名稱;
document.getElementById('ctl00_ContentPlaceHolder1_hid活動日期_1').value = params.活動日期_1;
document.getElementById('ctl00_ContentPlaceHolder1_hid活動日期_2').value = params.活動日期_2;
document.getElementById('ctl00_ContentPlaceHolder1_hid身分別').value = params.身分別;
document.getElementById('ctl00_ContentPlaceHolder1_hid憑證類別').value = params.憑證類別;
document.getElementById('ctl00_ContentPlaceHolder1_hid活動類別').value = params.活動類別;
document.getElementById('ctl00_ContentPlaceHolder1_hid付款方式').value = params.付款方式;
document.getElementById('ctl00_ContentPlaceHolder1_hidCurrentPage').value = this.currentPage;
document.getElementById('ctl00_ContentPlaceHolder1_hidPageSize').value = this.pageSize;
try {
let value = null;
const result = await this.callApiWithErrorMsg(
'領據處理',
'SelectPage',
params,
'查詢'
);
// 使用 ConvertDatesInObject 自動轉換日期
this.dataList = this.ConvertDatesInObject(result.Data || [], '-');
this.totalCount = result.TotalCount;
this.totalPages = Math.ceil(this.totalCount / this.pageSize);
this.selectedItems = [];
this.selectAll = false;
}
catch (error) {
console.error('查詢失敗:', error);
}
finally {
this.loading = false;
};
},
searchButtonClick()
{
this.currentPage = 1;
this.search();
},
changePage(page) {
if (page >= 1 && page <= this.totalPages) {
this.currentPage = page;
this.search();
}
},
toggleSelectAll() {
if (this.selectAll) {
this.selectedItems = [...this.dataList];
} else {
this.selectedItems = [];
}
},
addNew() {
// 切換到領據登錄與修改分頁
document.getElementById('ctl00_ContentPlaceHolder1_hid編號').value = '';
window.__doPostBack('ctl00$ContentPlaceHolder1$btnTab2', '');
},
edit(編號) {
// 切換到領據登錄與修改分頁,並進入該行資料的修改
document.getElementById('ctl00_ContentPlaceHolder1_hid編號').value = 編號;
window.__doPostBack('ctl00$ContentPlaceHolder1$btnTab2', '');
},
async printSelected() {
if (this.selectedItems.length === 0) {
this.ShowWarning('請選擇要列印的領據');
return;
}
this.loading = true;
try {
const idList = this.selectedItems.map(item => item.編號);
const result = await this.callApiWithErrorMsg(
'領據處理',
'Print',
{ idList: idList },
'列印領據'
);
if (result.Success) {
// 下載 Excel 檔案
if (result.Data && result.Data.fileName) {
const link = document.createElement('a');
link.href = `data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,${result.Data.excelData}`;
link.download = result.Data.fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
this.ShowSuccess(result.Message);
} else {
this.ShowWarning(result.Message);
}
} else
{
this.ShowError('列印失敗: ' + (error.message || '請稍後重試'));
}
// 列印完成後,刷新列表
this.search();
} catch (error) {
this.ShowError('列印失敗: ' + (error.message || '請檢查網路連線'));
} finally {
this.loading = false;
}
},
async doExport() {
if (this.dataList.length === 0) {
this.ShowWarning('請先查詢再匯出');
return;
}
this.loading = true;
try {
const idList = this.selectedItems.map(item => item.編號);
const params = {
活動日期_1: this.searchParams.活動日期_1 || null,
活動日期_2: this.searchParams.活動日期_2 || null,
身分別: this.searchParams.身分別 || null,
講師_非講師編號: this.searchParams.講師_非講師編號 || null,
憑證類別: this.searchParams.憑證類別 || null,
計畫代號: this.searchParams.計畫代號 || null,
計畫名稱: this.searchParams.計畫名稱 || null,
活動類別: this.searchParams.活動類別 || null,
付款方式: this.searchParams.付款方式 || null,
orderBy: ''
};
const result = await this.callApiWithErrorMsg(
'領據處理',
'Export',
params,
'領據匯出'
);
// 下載 Excel 檔案
if (result.Data && result.Data.fileName) {
const link = document.createElement('a');
link.href = `data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,${result.Data.excelData}`;
link.download = result.Data.fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
this.ShowSuccess('匯出完成');
} else {
this.ShowError('匯出失敗,沒有檔案內容');
}
} catch (error) {
this.ShowError('匯出失敗: ' + (error.message || '請檢查網路連線'));
} finally {
this.loading = false;
}
},
//async voidSelected() {
// if (this.selectedItems.length === 0) {
// this.ShowWarning('請選擇要作廢的領據');
// return;
// }
// if (!confirm('確定要作廢/取消作廢選取的領據嗎?')) {
// return;
// }
// this.loading = true;
// try {
// // 並行調用多個 API
// const promises = this.selectedItems.map(item => {
// return this.callApi('領據處理', 'Void', {
// no: item.編號,
// dbAppNo: item.DB_APPNO
// });
// });
// await Promise.all(promises);
// this.ShowSuccess('作廢操作完成');
// this.search();
// } catch (error) {
// console.error('Void error:', error);
// this.ShowError('作廢操作失敗: ' + (error.message || '請檢查網路連線'));
// } finally {
// this.loading = false;
// }
//},
//async deleteSelected() {
// if (this.selectedItems.length === 0) {
// this.ShowWarning('請選擇要刪除的領據');
// return;
// }
// if (!confirm('確定要刪除選取的領據嗎?此操作無法復原!')) {
// return;
// }
// this.loading = true;
// try {
// // 並行調用多個 API
// const promises = this.selectedItems.map(item => {
// return this.callApi('領據處理', 'Delete', {
// no: item.編號,
// dbAppNo: item.DB_APPNO
// });
// });
// await Promise.all(promises);
// this.ShowSuccess('刪除完成');
// this.search();
// } catch (error) {
// console.error('Delete error:', error);
// this.ShowError('刪除失敗: ' + (error.message || '請檢查網路連線'));
// } finally {
// this.loading = false;
// }
//}
}
});
}
+363
View File
@@ -0,0 +1,363 @@
function CreateReceiptSearchApp() {
const loadMarker = document.getElementById('receiptSearchApp');
if (loadMarker) {
DoCreateReceiptSearchApp();
}
}
function DoCreateReceiptSearchApp() {
window.__vms = window.__vms || {}
if (window.__vms.receiptSearchApp)
return;
console.log('Create receiptSearchApp');
window.__vms.receiptSearchApp = new Vue({
el: '#receiptSearchApp',
mixins: [DateMixin, ToastMixin, ApiMixin],
data: {
searchParams: {
計畫代號: '',
計畫名稱: '',
活動日期_1: '',
活動日期_2: '',
身分別: '',
講師_非講師編號: '',
憑證類別: '',
活動類別: '',
付款方式: '',
領款人姓名: ''
},
dropdown: {
身分別: [],
憑證類別: [],
付款方式: []
},
dataList: [],
selectedItems: [],
selectAll: false,
currentPage: 1,
pageSize: 20,
totalCount: 0,
totalPages: 0,
loading: false
},
created() {
// 從 AJAX 取得查詢用下拉選單資料
this.loadSearchDropdownLists();
// 取回保存的查詢條件
this.searchParams.計畫代號 = document.getElementById('ctl00_ContentPlaceHolder1_hid計畫代號').value;
this.searchParams.計畫名稱 = document.getElementById('ctl00_ContentPlaceHolder1_hid計畫名稱').value;
this.searchParams.活動日期_1 = document.getElementById('ctl00_ContentPlaceHolder1_hid活動日期_1').value;
this.searchParams.活動日期_2 = document.getElementById('ctl00_ContentPlaceHolder1_hid活動日期_2').value;
this.searchParams.身分別 = document.getElementById('ctl00_ContentPlaceHolder1_hid身分別').value;
this.searchParams.憑證類別 = document.getElementById('ctl00_ContentPlaceHolder1_hid憑證類別').value;
this.searchParams.活動類別 = document.getElementById('ctl00_ContentPlaceHolder1_hid活動類別').value;
this.searchParams.付款方式 = document.getElementById('ctl00_ContentPlaceHolder1_hid付款方式').value;
this.searchParams.領款人姓名 = document.getElementById('ctl00_ContentPlaceHolder1_hid領款人姓名').value;
this.currentPage = 1 * document.getElementById('ctl00_ContentPlaceHolder1_hidCurrentPage').value || 1;
this.pageSize = 1 * document.getElementById('ctl00_ContentPlaceHolder1_hidPageSize').value || 20;
},
computed: {
displayPages() {
const pages = [];
const start = Math.max(1, this.currentPage - 2);
const end = Math.min(this.totalPages, this.currentPage + 2);
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
}
},
mounted() {
this.search();
},
methods: {
async loadSearchDropdownLists() {
this.dropdown.身分別 = await this.loadSearchDropdownList("身分別");
this.dropdown.憑證類別 = await this.loadSearchDropdownList("憑證類別");
this.dropdown.付款方式 = await this.loadSearchDropdownList("付款方式");
},
async loadSearchDropdownList(key) {
try {
const result = await this.callApiWithErrorMsg(
'領據處理',
'GetSearchDropdownLists',
{ key: key },
'載入下拉選單'
);
return result.Data || [];
}
catch(error) {
console.error('載入下拉選單失敗:', error);
return [];
}
},
clearSearch() {
this.searchParams.計畫代號 = '';
this.searchParams.計畫名稱 = '';
this.searchParams.活動日期_1 = '';
this.searchParams.活動日期_2 = '';
this.searchParams.身分別 = '';
this.searchParams.憑證類別 = '';
this.searchParams.活動類別 = '';
this.searchParams.付款方式 = '';
this.searchParams.領款人姓名 = '';
this.currentPage = 1;
this.search();
},
async search() {
this.loading = true;
const params = {
pageIndex: this.currentPage,
pageSize: parseInt(this.pageSize),
活動日期_1: this.searchParams.活動日期_1 || null,
活動日期_2: this.searchParams.活動日期_2 || null,
身分別: this.searchParams.身分別 || null,
講師_非講師編號: this.searchParams.講師_非講師編號 || null,
憑證類別: this.searchParams.憑證類別 || null,
計畫代號: this.searchParams.計畫代號 || null,
計畫名稱: this.searchParams.計畫名稱 || null,
活動類別: this.searchParams.活動類別 || null,
付款方式: this.searchParams.付款方式 || null,
領款人姓名: this.searchParams.領款人姓名 || null,
orderBy: ''
};
// 保存查詢條件
document.getElementById('ctl00_ContentPlaceHolder1_hid計畫代號').value = params.計畫代號;
document.getElementById('ctl00_ContentPlaceHolder1_hid計畫名稱').value = params.計畫名稱;
document.getElementById('ctl00_ContentPlaceHolder1_hid活動日期_1').value = params.活動日期_1;
document.getElementById('ctl00_ContentPlaceHolder1_hid活動日期_2').value = params.活動日期_2;
document.getElementById('ctl00_ContentPlaceHolder1_hid身分別').value = params.身分別;
document.getElementById('ctl00_ContentPlaceHolder1_hid憑證類別').value = params.憑證類別;
document.getElementById('ctl00_ContentPlaceHolder1_hid活動類別').value = params.活動類別;
document.getElementById('ctl00_ContentPlaceHolder1_hid付款方式').value = params.付款方式;
document.getElementById('ctl00_ContentPlaceHolder1_hid領款人姓名').value = params.領款人姓名;
document.getElementById('ctl00_ContentPlaceHolder1_hidCurrentPage').value = this.currentPage;
document.getElementById('ctl00_ContentPlaceHolder1_hidPageSize').value = this.pageSize;
try {
let value = null;
const result = await this.callApiWithErrorMsg(
'領據處理',
'SelectPage',
params,
'查詢'
);
// 使用 ConvertDatesInObject 自動轉換日期
this.dataList = this.ConvertDatesInObject(result.Data || [], '-');
this.totalCount = result.TotalCount;
this.totalPages = Math.ceil(this.totalCount / this.pageSize);
this.selectedItems = [];
this.selectAll = false;
// 花花: 儲存當前頁編號清單及總頁數,供領據處理上下筆導航使用
document.getElementById('ctl00_ContentPlaceHolder1_hidIdList').value =
JSON.stringify(this.dataList.map(item => item.編號));
document.getElementById('ctl00_ContentPlaceHolder1_hidNavTotalPages').value = this.totalPages;
}
catch (error) {
console.error('查詢失敗:', error);
}
finally {
this.loading = false;
};
},
searchButtonClick()
{
this.currentPage = 1;
this.search();
},
changePage(page) {
if (page >= 1 && page <= this.totalPages) {
this.currentPage = page;
this.search();
}
},
toggleSelectAll() {
if (this.selectAll) {
this.selectedItems = [...this.dataList];
} else {
this.selectedItems = [];
}
},
addNew() {
// 切換到領據登錄與修改分頁
document.getElementById('ctl00_ContentPlaceHolder1_hid編號').value = '';
window.__doPostBack('ctl00$ContentPlaceHolder1$btnTab2', '');
},
edit(編號) {
// 切換到領據登錄與修改分頁,並進入該行資料的修改
document.getElementById('ctl00_ContentPlaceHolder1_hid編號').value = 編號;
window.__doPostBack('ctl00$ContentPlaceHolder1$btnTab2', '');
},
async printSelected() {
if (this.selectedItems.length === 0) {
this.ShowWarning('請選擇要列印的領據');
return;
}
this.loading = true;
try {
const idList = this.selectedItems.map(item => item.編號);
const result = await this.callApiWithErrorMsg(
'領據處理',
'Print',
{ idList: idList },
'列印領據'
);
if (result.Success) {
// 下載 Excel 檔案
if (result.Data && result.Data.fileName) {
const link = document.createElement('a');
link.href = `data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,${result.Data.excelData}`;
link.download = result.Data.fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
this.ShowSuccess(result.Message);
} else {
this.ShowWarning(result.Message);
}
} else
{
this.ShowError('列印失敗: ' + (error.message || '請稍後重試'));
}
// 列印完成後,刷新列表
this.search();
} catch (error) {
this.ShowError('列印失敗: ' + (error.message || '請檢查網路連線'));
} finally {
this.loading = false;
}
},
async doExport() {
if (this.dataList.length === 0) {
this.ShowWarning('請先查詢再匯出');
return;
}
this.loading = true;
try {
const idList = this.selectedItems.map(item => item.編號);
const params = {
活動日期_1: this.searchParams.活動日期_1 || null,
活動日期_2: this.searchParams.活動日期_2 || null,
身分別: this.searchParams.身分別 || null,
講師_非講師編號: this.searchParams.講師_非講師編號 || null,
憑證類別: this.searchParams.憑證類別 || null,
計畫代號: this.searchParams.計畫代號 || null,
計畫名稱: this.searchParams.計畫名稱 || null,
活動類別: this.searchParams.活動類別 || null,
付款方式: this.searchParams.付款方式 || null,
領款人姓名: this.searchParams.領款人姓名 || null,
orderBy: ''
};
const result = await this.callApiWithErrorMsg(
'領據處理',
'Export',
params,
'領據匯出'
);
// 下載 Excel 檔案
if (result.Data && result.Data.fileName) {
const link = document.createElement('a');
link.href = `data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,${result.Data.excelData}`;
link.download = result.Data.fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
this.ShowSuccess('匯出完成');
} else {
this.ShowError('匯出失敗,沒有檔案內容');
}
} catch (error) {
this.ShowError('匯出失敗: ' + (error.message || '請檢查網路連線'));
} finally {
this.loading = false;
}
},
//async voidSelected() {
// if (this.selectedItems.length === 0) {
// this.ShowWarning('請選擇要作廢的領據');
// return;
// }
// if (!confirm('確定要作廢/取消作廢選取的領據嗎?')) {
// return;
// }
// this.loading = true;
// try {
// // 並行調用多個 API
// const promises = this.selectedItems.map(item => {
// return this.callApi('領據處理', 'Void', {
// no: item.編號,
// dbAppNo: item.DB_APPNO
// });
// });
// await Promise.all(promises);
// this.ShowSuccess('作廢操作完成');
// this.search();
// } catch (error) {
// console.error('Void error:', error);
// this.ShowError('作廢操作失敗: ' + (error.message || '請檢查網路連線'));
// } finally {
// this.loading = false;
// }
//},
//async deleteSelected() {
// if (this.selectedItems.length === 0) {
// this.ShowWarning('請選擇要刪除的領據');
// return;
// }
// if (!confirm('確定要刪除選取的領據嗎?此操作無法復原!')) {
// return;
// }
// this.loading = true;
// try {
// // 並行調用多個 API
// const promises = this.selectedItems.map(item => {
// return this.callApi('領據處理', 'Delete', {
// no: item.編號,
// dbAppNo: item.DB_APPNO
// });
// });
// await Promise.all(promises);
// this.ShowSuccess('刪除完成');
// this.search();
// } catch (error) {
// console.error('Delete error:', error);
// this.ShowError('刪除失敗: ' + (error.message || '請檢查網路連線'));
// } finally {
// this.loading = false;
// }
//}
}
});
}
@@ -0,0 +1,926 @@
function CreateReceiptEditApp() {
const loadMarker = document.getElementById('receiptEditApp');
if (loadMarker) {
DoCreateReceiptEditApp();
}
}
function DoCreateReceiptEditApp() {
window.__vms = window.__vms || {}
if (window.__vms.receiptEditApp)
return;
window.__vms.receiptEditApp = new Vue({
el: '#receiptEditApp',
mixins: [DateMixin, ToastMixin, ApiMixin],
data: {
mode: 'view', // view, add, edit
編號: 0,
originalMainData: null,
originalLaborData: null,
originalTravelData: null,
mainData: {
編號: 0,
表單編號: '',
活動日期: '',
身分別: '',
講師_非講師編號: '',
領款人姓名: '',
身分證字號: '',
憑證類別: '',
計畫代號: '',
計畫名稱: '',
活動地點: '',
活動類別: '',
活動名稱事由: '',
製單人員: '',
付款方式: '',
作廢: 0,
列印次數: 0,
已關帳: false,
DB_APPNO: 1,
DB_CRDAT: '',
DB_CRUSR: '',
DB_CRUSR_NAME: '',
DB_TRDAT: '',
DB_TRUSR: '',
DB_TRUSR_NAME: ''
},
laborData: {
編號: '',
領據編號: 0,
費用別: '',
單價: 0,
數量: 0,
單位: '',
扣繳類別: '',
合計金額: 0,
應扣繳稅額: 0,
服務費: 0,
作廢: 0,
DB_APPNO: 1
},
travelData: {
編號: '',
領據編號: 0,
票價資訊: '',
費用別: '',
起點: '',
訖點: '',
捷運公車: 0,
火車: 0,
計程車: 0,
高鐵飛機: 0,
自行開車: 0,
過路費停車費: 0,
住宿費: 0,
合計: 0,
作廢: 0,
DB_APPNO: 1
},
dropdown: {
身分別: [],
憑證類別: [],
付款方式: [],
勞務費_費用別: [],
差旅費_費用別: [],
扣繳類別: [],
單位別: []
},
recipientList: [],
showRecipientDropdown: false,
planList: [],
showPlanDropdown: false,
originStations: [],
showOriginDropdown: false,
destStations: [],
showDestDropdown: false,
stations: [],
showActivityLocationPhrase: false,
activityLocationPhrases: [],
showActivityCategoryPhrase: false,
activityCategoryPhrases: [],
showTicketPricePhrase: false,
ticketPricePhrases: [],
loading: false,
variables: {
領據執行業務所得課稅門檻: 0,
領據薪資課稅門檻: 0,
領據基本工資: 0,
領據執行業務所得稅率: 0,
領據薪資稅率: 0
}
},
created() {
// 從 AJAX 取得下拉選單資料
this.loading = true;
this.loadVariables();
this.loadDropdownLists();
this.loadPhraseLists();
this.loading = false;
this.mainData.製單人員 = V製單人員;
this.編號 = 0;
if (document.getElementById('ctl00_ContentPlaceHolder1_hid編號').value !== '') {
this.編號 = parseInt(document.getElementById('ctl00_ContentPlaceHolder1_hid編號').value) || 0;
}
},
mounted() {
if (this.編號 !== 0) {
this.mode = 'view';
this.loadData(this.編號)
} else {
this.mode = 'add';
this.reset();
this.backupData();
this.setDefault();
}
},
watch: {
'laborData.單價'() {
this.calculateLaborTotal();
},
'laborData.數量'() {
this.calculateLaborTotal();
},
'laborData.扣繳類別'() {
this.calculateTax();
},
'mainData.身分別'() {
if (this.mode === 'add' || this.mode === 'edit') {
this.mainData.講師_非講師編號 = '';
this.mainData.領款人姓名 = '';
this.mainData.身分證字號 = '';
this.recipientList = [];
}
},
'travelData.捷運公車'() { this.calculateTravelTotal(); },
'travelData.火車'() { this.calculateTravelTotal(); },
'travelData.計程車'() { this.calculateTravelTotal(); },
'travelData.高鐵飛機'() { this.calculateTravelTotal(); },
'travelData.自行開車'() { this.calculateTravelTotal(); },
'travelData.過路費停車費'() { this.calculateTravelTotal(); },
'travelData.住宿費'() { this.calculateTravelTotal(); }
},
computed: {
isViewMode() {
return this.mode === 'view';
},
isEditMode() {
return this.mode === 'edit' || this.mode === 'add';
},
canAddNew() {
return this.mode === 'view';
},
canEdit() {
return this.mode === 'view' && this.編號 !== 0;
},
canCancel() {
return this.mode === 'add' || this.mode === 'edit';
},
canSave() {
return this.mode === 'add' || this.mode === 'edit';
},
canReturnToList() {
return this.mode === 'view';
},
closed() {
return this.mainData.已關帳;
},
laborWarnings() {
const warnings = [];
const 扣繳類別 = this.laborData.扣繳類別 || '';
// 費用別警告
if (['演講費', '撰稿費'].indexOf(this.laborData.費用別) >= 0 && 扣繳類別 === '50') {
warnings.push('扣繳類別只能是執行業務所得');
}
// 合計金額警告
if (扣繳類別 === '50' && this.laborData.合計金額 >= this.variables.領據基本工資) {
warnings.push(`薪資不得超過基本工資${this.variables.領據基本工資}(含),超過請拆成不同日期的領據`);
}
if ((扣繳類別.startsWith('9A') || 扣繳類別.startsWith('9B')) && this.laborData.合計金額 >= this.variables.領據執行業務所得課稅門檻) {
warnings.push(`執行業務所得不得超過${this.variables.領據執行業務所得課稅門檻}(含),超過請拆成不同日期的領據`);
}
return warnings;
}
},
methods: {
async loadVariables() {
try {
const result = await this.callApi('領據處理', 'GetVariables', {});
if (result.Data) {
this.variables = result.Data;
}
} catch (error) {
console.error('載入片語清單失敗:', error);
}
},
async loadDropdownLists() {
this.dropdown.身分別 = await this.loadDropdownList("身分別");
this.dropdown.憑證類別 = await this.loadDropdownList("憑證類別");
this.dropdown.付款方式 = await this.loadDropdownList("付款方式");
this.dropdown.勞務費_費用別 = await this.loadDropdownList("勞務費_費用別");
this.dropdown.差旅費_費用別 = await this.loadDropdownList("差旅費_費用別");
this.dropdown.扣繳類別 = await this.loadDropdownList("扣繳類別");
this.dropdown.單位別 = await this.loadDropdownList("單位別");
},
async loadDropdownList(key) {
try {
const result = await this.callApi('領據處理', 'GetDropdownLists', { key });
return result.Data || [];
} catch (error) {
console.error('載入下拉選單失敗:', error);
return [];
}
},
async loadPhraseLists() {
try {
const result = await this.callApi('領據處理', 'GetPhraseLists', {});
if (result.Data) {
this.activityLocationPhrases = result.Data.activityLocation || [];
this.activityCategoryPhrases = result.Data.activityCategory || [];
this.ticketPricePhrases = result.Data.ticketPrice || [];
}
} catch (error) {
console.error('載入公用變數失敗:', error);
}
},
toggleActivityLocationPhrase() {
if (this.isViewMode) return;
this.showActivityLocationPhrase = !this.showActivityLocationPhrase;
this.showActivityCategoryPhrase = false;
this.showTicketPricePhrase = false;
},
toggleActivityCategoryPhrase() {
if (this.isViewMode) return;
this.showActivityCategoryPhrase = !this.showActivityCategoryPhrase;
this.showActivityLocationPhrase = false;
this.showTicketPricePhrase = false;
},
toggleTicketPricePhrase() {
if (this.isViewMode) return;
this.showTicketPricePhrase = !this.showTicketPricePhrase;
this.showActivityLocationPhrase = false;
this.showActivityCategoryPhrase = false;
},
insertPhrase(fieldName, phrase) {
let input;
if (fieldName === '活動地點') {
input = this.$refs.活動地點Input;
this.showActivityLocationPhrase = false;
} else if (fieldName === '活動類別') {
input = this.$refs.活動類別Input;
this.showActivityCategoryPhrase = false;
} else if (fieldName === '票價資訊') {
input = this.$refs.票價資訊Input;
this.showTicketPricePhrase = false;
}
if (input) {
const startPos = input.selectionStart || input.value.length;
const endPos = input.selectionEnd || input.value.length;
const currentValue = input.value;
const newValue = currentValue.substring(0, startPos) + phrase + currentValue.substring(endPos);
if (fieldName === '活動地點') {
this.mainData.活動地點 = newValue;
} else if (fieldName === '活動類別') {
this.mainData.活動類別 = newValue;
} else if (fieldName === '票價資訊') {
this.travelData.票價資訊 = newValue;
}
// 更新游標位置
this.$nextTick(() => {
input.focus();
input.setSelectionRange(startPos + phrase.length, startPos + phrase.length);
});
}
},
calculateLaborTotal() {
const price = parseFloat(this.laborData.單價) || 0;
const qty = parseFloat(this.laborData.數量) || 0;
this.laborData.合計金額 = Math.round(price * qty);
this.calculateTax();
},
async calculateTax() {
const laborFee = parseInt(this.laborData.合計金額) || 0;
const category = this.laborData.扣繳類別 || '';
if (laborFee > 0 && category) {
this.loading = true;
try {
const result = await this.callApi('領據處理', 'CalcTax', {
laborFee,
category
});
this.laborData.應扣繳稅額 = result.Data;
} catch (error) {
console.error('計算稅額失敗:', error);
} finally {
this.loading = false;
}
} else {
this.laborData.應扣繳稅額 = 0;
}
},
calculateTravelTotal() {
this.travelData.合計 =
(parseInt(this.travelData.捷運公車) || 0) +
(parseInt(this.travelData.火車) || 0) +
(parseInt(this.travelData.計程車) || 0) +
(parseInt(this.travelData.高鐵飛機) || 0) +
(parseInt(this.travelData.自行開車) || 0) +
(parseInt(this.travelData.過路費停車費) || 0) +
(parseInt(this.travelData.住宿費) || 0);
},
searchStation(field) {
const key = this.travelData[field];
if (key && key.length > 0) {
axios({
method: 'post',
url: `${API_TRAIN_FARE}/SelectStation`,
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
data: JSON.stringify({ key })
})
.then(response => {
const result = response.data.d || response.data;
if (result.Success) {
this.stations = result.Data || [];
} else {
this.stations = [];
this.ShowWarning(result.Message);
}
})
.catch(error => {
console.error('查詢車站失敗:', error);
});
}
},
async searchRecipient() {
const key = this.mainData.領款人姓名;
const identity = this.mainData.身分別;
const activationDate = this.mainData.活動日期;
if (!key || key.length === 0) {
this.recipientList = [];
this.showRecipientDropdown = false;
return;
}
if (!identity) {
this.ShowWarning('請先選擇身分別');
this.recipientList = [];
this.showRecipientDropdown = false;
return;
}
if (!activationDate) {
this.ShowWarning('請輸入活動日期');
this.recipientList = [];
this.showRecipientDropdown = false;
return;
}
this.loading = true;
try {
const result = await this.callApi('領據處理', 'SearchRecipient', { key, identity, activationDate });
this.recipientList = result.Data || [];
this.showRecipientDropdown = this.recipientList.length > 0;
} catch (error) {
console.error('查詢領款人失敗:', error);
this.recipientList = [];
this.showRecipientDropdown = false;
this.ShowWarning(error.message);
} finally {
this.loading = false;
}
},
hideRecipientDropdown() {
setTimeout(() => {
this.showRecipientDropdown = false;
this.recipientList = [];
}, 200);
},
selectRecipient(recipient) {
this.mainData.講師_非講師編號 = recipient.Value;
this.mainData.領款人姓名 = recipient.Name;
this.mainData.身分證字號 = recipient.IdNumber;
this.mainData.憑證類別 = recipient.VoucherType;
this.laborData.扣繳類別 = recipient.ConductType;
this.recipientList = [];
this.showRecipientDropdown = false;
},
async searchPlan() {
const key = this.mainData.計畫代號;
const year = this.mainData.活動日期 ? new Date(this.mainData.活動日期).getFullYear() - 1911 : null;
if (year === null) {
this.ShowWarning("請輸入活動日期");
return;
}
if (!key || key.length === 0) {
this.planList = [];
this.showPlanDropdown = false;
return;
}
this.loading = true;
try {
const result = await this.callApi('領據處理', 'SearchPlan', { key, year });
this.planList = result.Data || [];
this.showPlanDropdown = this.planList.length > 0;
} catch (error) {
console.error('查詢計畫失敗:', error);
this.planList = [];
this.showPlanDropdown = false;
this.ShowWarning(error.message);
} finally {
this.loading = false;
}
},
selectPlan(plan) {
this.mainData.計畫代號 = plan.Code;
this.mainData.計畫名稱 = plan.Name;
this.planList = [];
this.showPlanDropdown = false;
},
hidePlanDropdown() {
setTimeout(() => {
this.showPlanDropdown = false;
this.planList = [];
}, 200);
},
async searchOriginStation() {
const key = this.travelData.起點;
if (!key || key.length === 0) {
this.originStations = [];
this.showOriginDropdown = false;
return;
}
this.loading = true;
try {
const result = await this.callApi('火車票價', 'SelectOriginStation', { key });
this.originStations = result.Data || [];
this.showOriginDropdown = this.originStations.length > 0;
} catch (error) {
console.error('查詢起點失敗:', error);
this.originStations = [];
this.showOriginDropdown = false;
this.ShowWarning(error.message);
} finally {
this.loading = false;
}
},
selectOriginStation(station) {
this.travelData.起點 = station.Name;
this.originStations = [];
this.showOriginDropdown = false;
},
hideOriginDropdown() {
setTimeout(() => {
this.showOriginDropdown = false;
this.originStations = [];
}, 200);
this.calculateFare();
},
async searchDestStation() {
const key = this.travelData.訖點;
if (!key || key.length === 0) {
this.destStations = [];
this.showDestDropdown = false;
return;
}
this.loading = true;
try {
const result = await this.callApi('火車票價', 'SelectDestStation', { key });
this.destStations = result.Data || [];
this.showDestDropdown = this.destStations.length > 0;
} catch (error) {
console.error('查詢訖點失敗:', error);
this.destStations = [];
this.showDestDropdown = false;
this.ShowWarning(error.message);
} finally {
this.loading = false;
}
},
selectDestStation(station) {
this.travelData.訖點 = station.Name;
this.destStations = [];
this.showDestDropdown = false;
},
hideDestDropdown() {
setTimeout(() => {
this.showDestDropdown = false;
this.destStations = [];
}, 200);
this.calculateFare();
},
async calculateFare() {
const 起站 = this.travelData.起點;
const 迄站 = this.travelData.訖點;
if (!起站 || !迄站) {
return;
}
this.loading = true;
try {
const result = await this.callApi('火車票價', 'SelectFare', { 起站, 迄站 });
this.travelData.火車 = result.Data;
this.calculateTravelTotal();
} catch (error) {
console.error('查詢票價失敗:', error);
this.ShowWarning(error.message);
} finally {
this.loading = false;
}
},
async save() {
if (!this.validate() || (this.laborWarnings || []).length > 0) {
return;
}
this.loading = true;
try {
const data = {
isNew: this.mode === 'add',
mainItem: this.mainData,
laborItem: this.laborData,
travelItem: this.travelData
};
const result = await this.callApiWithErrorMsg('領據處理', 'Save', data, '儲存');
this.ShowSuccess(result.Message);
// 新增存檔更新編號變數
if (this.mode === 'add') {
this.編號 = result.Data.編號;
}
// 返回查看模式
this.mode = 'view';
this.loadData(this.編號);
this.backupData();
} catch (error) {
console.error('Save error:', error);
} finally {
this.loading = false;
}
},
validate() {
if (!this.mainData.身分別) {
this.ShowWarning('請選擇身分別');
return false;
}
if (!this.mainData.領款人姓名) {
this.ShowWarning('請輸入領款人姓名');
return false;
}
if (!this.mainData.計畫代號) {
this.ShowWarning('請輸入計畫代號');
return false;
}
return true;
},
returnToList() {
if ((this.mode === 'add' || this.mode === 'edit') && !confirm("是否放棄" + (this.mode === 'add' ? '新增' : '修改')))
return;
window.__doPostBack('ctl00$ContentPlaceHolder1$btnTab1', '');
},
backupData() {
this.originalMainData = JSON.parse(JSON.stringify(this.mainData));
this.originalLaborData = JSON.parse(JSON.stringify(this.laborData));
this.originalTravelData = JSON.parse(JSON.stringify(this.travelData));
},
restoreData() {
if (this.originalMainData) {
this.mainData = JSON.parse(JSON.stringify(this.originalMainData));
}
if (this.originalLaborData) {
this.laborData = JSON.parse(JSON.stringify(this.originalLaborData));
}
if (this.originalTravelData) {
this.travelData = JSON.parse(JSON.stringify(this.originalTravelData));
}
},
addNew() {
if (this.mode !== 'view') {
return;
}
this.mode = 'add';
this.reset();
this.backupData();
this.setDefault();
},
edit() {
if (this.mode !== 'view') {
return;
}
this.mode = 'edit';
this.backupData();
},
cancel() {
if (this.mode === 'view') {
return;
}
if (!confirm("是否放棄" + (this.mode === 'add' ? '新增' : '修改') + ",資料將復原?")) {
return;
}
this.restoreData();
this.mode = 'view';
},
reset() {
this.mainData = {
編號: 0,
表單編號: '',
活動日期: '',
身分別: '',
講師_非講師編號: '',
領款人姓名: '',
身分證字號: '',
憑證類別: '',
計畫代號: '',
計畫名稱: '',
活動地點: '',
活動類別: '',
活動名稱事由: '',
付款方式: '',
作廢: 0,
列印次數: 0,
DB_APPNO: 1,
DB_CRDAT: '',
DB_CRUSR: '',
DB_CRUSR_NAME: '',
DB_TRDAT: '',
DB_TRUSR: '',
DB_TRUSR_NAME: '',
};
this.laborData = {
編號: '',
領據編號: 0,
費用別: '',
單價: 0,
數量: 0,
單位: '',
扣繳類別: '',
合計金額: 0,
應扣繳稅額: 0,
服務費: 0,
作廢: 0,
DB_APPNO: 1
};
this.travelData = {
編號: '',
領據編號: 0,
票價資訊: '',
費用別: '',
起點: '',
訖點: '',
捷運公車: 0,
火車: 0,
計程車: 0,
高鐵飛機: 0,
自行開車: 0,
過路費停車費: 0,
住宿費: 0,
合計: 0,
作廢: 0,
DB_APPNO: 1
};
},
setDefault()
{
this.mainData.DB_CRDAT = this.FormatDate(new Date());
this.mainData.DB_CRUSR = document.getElementById('ctl00_hidCurrentUser').value;
this.mainData.DB_CRUSR_NAME = document.getElementById('ctl00_hidCurrentUserName').value;
},
async loadData(no) {
this.loading = true;
try {
const result = await this.callApiWithErrorMsg('領據處理', 'GetOne', { no }, '載入');
const data = result.Data;
this.mainData = {
編號: data.編號,
表單編號: data.表單編號,
活動日期: this.FormatDate(data.活動日期),
身分別: data.身分別,
講師_非講師編號: data.講師_非講師編號,
領款人姓名: data.領款人姓名,
身分證字號: data.身分證字號,
憑證類別: data.憑證類別,
計畫代號: data.計畫代號,
計畫名稱: data.計畫名稱,
活動地點: data.活動地點,
活動類別: data.活動類別,
活動名稱事由: data.活動名稱事由,
付款方式: data.付款方式,
作廢: data.作廢,
列印次數: data.列印次數,
已關帳: data.已關帳,
DB_APPNO: data.DB_APPNO,
DB_CRDAT: this.FormatDate(data.DB_CRDAT),
DB_CRUSR: data.DB_CRUSR,
DB_CRUSR_NAME: data.DB_CRUSR_NAME,
DB_TRDAT: data.DB_TRUSR !== null ? this.FormatDate(data.DB_TRDAT) : '',
DB_TRUSR: data.DB_TRUSR !== null ? data.DB_TRUSR : '',
DB_TRUSR_NAME: data.DB_TRUSR != null ? data.DB_TRUSR_NAME: ''
};
this.laborData = {
編號: data.勞務費_編號,
領據編號: data.編號,
費用別: data.勞務費_費用別,
單價: data.勞務費_單價,
數量: data.勞務費_數量,
單位: data.勞務費_單位,
扣繳類別: data.勞務費_扣繳類別,
合計金額: data.勞務費_合計金額,
應扣繳稅額: data.勞務費_應扣繳稅額,
服務費: data.勞務費_服務費,
作廢: data.勞務費_作廢
};
this.travelData = {
編號: data.差旅費_編號,
領據編號: data.編號,
票價資訊: data.差旅費_票價資訊,
費用別: data.差旅費_費用別,
起點: data.差旅費_起點,
訖點: data.差旅費_訖點,
捷運公車: data.差旅費_捷運公車,
火車: data.差旅費_火車,
計程車: data.差旅費_計程車,
高鐵飛機: data.差旅費_高鐵飛機,
自行開車: data.差旅費_自行開車,
過路費停車費: data.差旅費_過路費停車費,
住宿費: data.差旅費_住宿費,
合計: data.差旅費_合計,
作廢: data.差旅費_作廢
};
if (data.main) {
this.mainData = data.main;
}
if (data.labor) {
this.laborData = data.labor;
}
if (data.travel) {
this.travelData = data.travel;
}
this.backupData();
} catch (error) {
console.error('LoadData error:', error);
} finally {
this.loading = false;
}
},
async print() {
if (this.mainData.作廢 !== 0) {
this.ShowError('已作廢不可列印');
}
this.loading = true;
try {
const result = await this.callApiWithErrorMsg(
'領據處理',
'Print',
{ idList: [this.mainData.編號 ] },
'列印領據'
);
if (result.Success) {
// 下載 Excel 檔案
if (result.Data && result.Data.fileName) {
const link = document.createElement('a');
link.href = `data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,${result.Data.excelData}`;
link.download = result.Data.fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
this.ShowSuccess(result.Message);
} else {
this.ShowWarning(result.Message);
}
} else {
this.ShowError('列印失敗: ' + (error.message || '請稍後重試'));
}
} catch (error) {
this.ShowError('列印失敗: ' + (error.message || '請檢查網路連線'));
} finally {
this.loadData(this.編號);
this.loading = false;
}
},
async voidVoucher() {
if (this.mainData.作廢 === 0 && !confirm("是否確定作廢?") || this.mainData.作廢 !== 0 && !confirm("是否確定取消作廢?")) {
return;
}
this.loading = true;
try {
const result = await this.callApi('領據處理', 'Void', {
no: this.mainData.編號
});
this.loadData(this.mainData.編號);
this.ShowSuccess('作廢操作完成');
} catch (error) {
console.error('Void error:', error);
this.ShowError('作廢操作失敗: ' + (error.message || '請檢查網路連線'));
} finally {
this.loading = false;
}
},
async voidLaborFee() {
if (this.laborData.作廢 === 0 && !confirm("是否確定作廢勞務費?") || this.laborData.作廢 !== 0 && !confirm("是否確定取消作廢勞務費?")) {
return;
}
this.loading = true;
try {
const result = await this.callApi('領據處理', 'VoidLaborFee', {
receiptNo: this.mainData.編號
});
this.loadData(this.mainData.編號);
this.ShowSuccess('勞務費作廢操作完成');
} catch (error) {
console.error('VoidLaborFee error:', error);
this.ShowError('勞務費作廢操作失敗: ' + (error.message || '請檢查網路連線'));
} finally {
this.loading = false;
}
},
async voidTravelFee() {
if (this.travelData.作廢 === 0 && !confirm("是否確定作廢差旅費?") || this.travelData.作廢 !== 0 && !confirm("是否確定取消作廢差旅費?")) {
return;
}
this.loading = true;
try {
const result = await this.callApi('領據處理', 'VoidTravelFee', {
receiptNo: this.mainData.編號
});
this.loadData(this.mainData.編號);
this.ShowSuccess('差旅費作廢操作完成');
} catch (error) {
console.error('VoidTravelFee error:', error);
this.ShowError('差旅費作廢操作失敗: ' + (error.message || '請檢查網路連線'));
} finally {
this.loading = false;
}
},
}
});
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
let V身分別 = [];
let V憑證類別 = [];
let V付款方式 = [];
let V勞務費_費用別 = [];
let V差旅費_費用別 = [];
let V扣繳類別 = [];
let V單位別 = [];
let V製單人員 = "";