# 法會報名統計 ## 相關檢視VIEW ### 法會統計 ```SQL CREATE VIEW [dbo].[法會統計] AS SELECT TOP (100) PERCENT dbo.activity.num, dbo.activity.category_kind, dbo.activity_category_kind.kind AS 活動主類型, dbo.activity.kind, dbo.activity_kind.kind AS 活動詳細分類, dbo.activity.subject AS 活動名稱, dbo.activity.startDate_solar AS 開始日期, dbo.activity.endDate_solar AS 結束日期, dbo.activity.dueDate AS 報名截止日, SUM(dbo.pro_order_detail.qty) AS 功德數量, SUM(dbo.pro_order_detail.price) AS 合計 FROM dbo.pro_order_detail LEFT OUTER JOIN dbo.pro_order ON dbo.pro_order_detail.order_no = dbo.pro_order.order_no RIGHT OUTER JOIN dbo.activity ON dbo.pro_order.activity_num = dbo.activity.num LEFT OUTER JOIN dbo.activity_kind ON dbo.activity.kind = dbo.activity_kind.num LEFT OUTER JOIN dbo.activity_category_kind ON dbo.activity.category_kind = dbo.activity_category_kind.num GROUP BY dbo.activity.num, dbo.activity.category_kind, dbo.activity_category_kind.kind, dbo.activity.kind, dbo.activity_kind.kind, dbo.activity.subject, dbo.activity.startDate_solar, dbo.activity.endDate_solar, dbo.activity.dueDate GO ``` ### 報名明細查詢 ```SQL CREATE VIEW [dbo].[報名明細查詢] AS SELECT dbo.activity.num AS 法會ID, dbo.activity.subject AS 法會名稱, dbo.activity.startDate_solar AS 開始日期, dbo.activity.endDate_solar AS 結束日期, dbo.followers.f_number AS 信眾編號, dbo.followers.u_name AS 信眾姓名, dbo.pro_order.order_no AS 報名編號, dbo.pro_order.up_time AS 報名日期, CASE WHEN parent_num IS NOT NULL THEN '是' ELSE '否' END AS 功德主, dbo.actItem_kind.kind AS 功德類型, dbo.actItem.subject AS 功德名稱, dbo.pro_order_detail.qty AS 數量, dbo.pro_order_detail.price AS 金額, 0 AS 已收, dbo.pro_order_detail.price * dbo.pro_order_detail.qty - 0 AS 未收 FROM dbo.pro_order_detail INNER JOIN dbo.pro_order ON dbo.pro_order_detail.order_no = dbo.pro_order.order_no INNER JOIN dbo.actItem ON dbo.pro_order_detail.actItem_num = dbo.actItem.num INNER JOIN dbo.activity ON dbo.pro_order.activity_num = dbo.activity.num INNER JOIN dbo.followers ON dbo.pro_order.f_num = dbo.followers.num INNER JOIN dbo.actItem_kind ON dbo.actItem.kind = dbo.actItem_kind.num GO ``` #### 欄位分類 ``` // 🟠 橘色欄位 - 法會基礎資料 (法會相關的直接來源) 法會ID = act.num, 法會名稱 = act.subject, 開始日期 = act.startDate_solar, 結束日期 = act.endDate_solar, // 🔵 藍色欄位 - 信眾基礎資料 (信眾相關的直接來源) 信眾編號 = f.f_number, 信眾姓名 = f.u_name, 報名編號 = po.order_no, 報名日期 = po.up_time, // 🟢綠色欄位 - 功德資訊欄位 (功德相關資訊) 功德主 = pod.parent_num != null ? "是" : "否", 功德類型 = aik.kind, 功德名稱 = ai.subject, // 🟣 紫色欄位 - 計算欄位 (需要計算) 數量 = pod.qty, 金額 = pod.price, 已收 = 0, // 業務邏輯:固定為 0 未收 = pod.price * pod.qty - 0, // 計算未收金額 ``` ## API設計 - pivot01Controller - 查詢: 法會統計, 條件參數: 年份 - 查詢: 報名明細查詢, 條件參數: 法會ID - 因為: 希望能隨時加上新的檢視, 不需要動到EF-MODEL - 所以: 直接用SQL SELECT指令查詢, VIEW名稱也以字串命名 - 各檢視的查詢, 都用很固定的模式. - 例: SELECT * FROM {VIEW名稱} WHERE {條件欄位} = {條件值(參數傳入)} { AND/OR 其他固定條件} ORDER BY {欄位, 欄位, ...} - 傳回欄位: 都以中文名稱為主, 故不用定義對照表 - 其它統計, 都以VUE/JS, 在瀏覽器端操作 ## 頁面設計需求 - 檔名: pivot/pivot01.aspx - .cs只是個空架子, 參考: D:\dev\ez\17168erp\git_17888\web\admin\pivot\query.aspx.cs - 頁籤式操作介面 - Tab1 : 法會選擇頁面 - 相關檢視: 法會統計 - 一開始只有Tab1, 其它隱藏 - 年份選擇, 預設今年, 切換年份時重新查詢 - 表格顯示資料條件: - 當年度(日期範圍涵蓋), 及無日期範圍(NULL) - 排序: 日期 - 表格按鈕: "選擇", 點選後, 以該場法會活動, 為查詢條件 - 查詢: 報名明細查詢, where: 指定法會的所有資料 - 按"選擇"後, 才出現Tab2~5 - Tab2 : 牌位分析 - PIVOT TABLE, 詳見說明 - Tab3 : 信眾報名分析 - PIVOT TABLE, 詳見說明 - Tab4 : 功德主查詢分析 - PIVOT TABLE, 詳見說明 - Tab5 : 報名明細資料 - 相關檢視: 報名明細查詢 - 以表格呈現資料 - Tab1所選擇法會的所有明細資料 ### Tab1 : 法會選擇頁面 - 查詢列: - 下拉:年份選擇, - 今年起前後5年, 預設今年 - 可自行輸入, 也可輸入前後5年以外的年份 - "查看"按鈕, - 按下即查詢所選年份的所有活動 - 更新下方表格 - 法會表格: - 頁面載入時, 依下拉預設查詢當年度所有法會(包含) - 直接列出所有欄位 ### Tab2 : 牌位分析 - 參考的EXCEL樞紐分析表 - 篩選: 法會名稱(不用處理, 因為我們的查詢, 已經只限一場法會了) - 列: 功德類型, 功德名稱 - 欄: 無 - 值: 數量:加總, 金額加總 - UI呈現: - 以vuetify Table呈現樞紐分析表 - 用JS把該法會明細資料變成PIVOT TABLE的型式 - 可以2層收合表格列(功德類型, 功德名稱) - 分別顯示: 該類型, 或該功德項目名稱的: 加總數量, 加總金額 - 若無法2層, 就直接呈現4個欄位: 功德類型, 功德名稱, 數量, 金額 - 上下層, 樣式要有明顯區隔 - 最後一列, 要有全部總計, 若vuetify table有footer也可以 ### Tab3 : 信眾報名分析 - 搜尋欄位: 關鍵字: 姓名過濾 - 也是先做PIVOT運算, 欄位如下: - 信眾編號 - 信眾姓名 - 功德主(功德主=是)數量 - 功德主(功德主=是)金額 - 功德數量(功德主=否) - 功德金額(功德主=否) ### Tab4 : 功德主查詢分析 - 搜尋欄位: - 關鍵字: 姓名過濾 - 功德類型下拉: 全部或單一分類 - RADIO : 切換值: 金額, 或:數量 - CHECK : 功德主: 是/否 - PIVOT TABLE - 第一欄: 信眾姓名, FIXED - 第二欄: 總計(金額或數量) - 第三欄以後: - 標題: 所選功德類型(或全部)的功德項目 - 資料列: (依選取) 金額或數量 (該信眾/該功德) ### Tab5 : 報名明細資料 (細節待補充) - 表格: 欄位: 報名明細查詢的所有欄位 - 各欄位表頭顏色標示為前述分組 ## 實作計劃 ### Phase 1 : implement controller #### 目標 創建 `pivot01Controller.cs`,提供簡單直觀的 API 端點 #### 設計原則 - **VIEW 導向**: 直接使用 SQL 查詢 VIEW,不依賴 EF Model - **動態查詢**: VIEW 名稱以字串參數傳遞,便於新增 VIEW 而不需改程式 - **固定查詢模式**: `SELECT * FROM {VIEW名稱} WHERE {條件欄位} = {參數值} ORDER BY {排序欄位}` - **中文欄位名**: 查詢結果保留 VIEW 的中文欄位名,前端直接使用 #### API 端點設計 ##### 1. GET /api/pivot01/activity_stats **功能**: 查詢法會統計(對應「法會統計」VIEW) **參數**: - `year` (int, required): 查詢年份 - `includeNoDate` (bool, optional, default=true): 是否包含無日期的法會 **查詢邏輯**: ```sql SELECT * FROM [法會統計] WHERE (YEAR(開始日期) = @year OR YEAR(結束日期) = @year) OR (@includeNoDate = 1 AND (開始日期 IS NULL OR 結束日期 IS NULL)) ORDER BY 開始日期 DESC, 結束日期 DESC ``` **回應格式**: ```json { "success": true, "data": [ { "num": 123, "category_kind": 1, "活動主類型": "法會", "kind": 5, "活動詳細分類": "法會-梁皇寶懺", "活動名稱": "2025年梁皇寶懺", "開始日期": "2025-03-01", "結束日期": "2025-03-10", "報名截止日": "2025-02-25", "功德數量": 150, "合計": 500000 } ], "message": "查詢成功", "rowCount": 10 } ``` ##### 2. GET /api/pivot01/registration_details **功能**: 查詢報名明細(對應「報名明細查詢」VIEW) **參數**: - `activityNum` (int, required): 法會編號(法會ID) - `page` (int, optional, default=1): 頁碼 - `pageSize` (int, optional, default=50): 每頁筆數(最大 10000) **查詢邏輯**: ```sql SELECT * FROM [報名明細查詢] WHERE 法會ID = @activityNum ORDER BY 報名日期 DESC, 報名編號 DESC OFFSET (@page - 1) * @pageSize ROWS FETCH NEXT @pageSize ROWS ONLY ``` **回應格式**: ```json { "success": true, "data": { "list": [ { "法會ID": 123, "法會名稱": "2025年梁皇寶懺", "開始日期": "2025-03-01", "結束日期": "2025-03-10", "信眾編號": "F12345", "信眾姓名": "王大明", "報名編號": "ORD20250101001", "報名日期": "2025-01-15T10:30:00", "功德主": "是", "功德類型": "牌位", "功德名稱": "消災大牌位", "數量": 1, "金額": 3000, "已收": 0, "未收": 3000 } ], "pagination": { "currentPage": 1, "pageSize": 50, "totalRows": 523, "totalPages": 11 } }, "message": "查詢成功" } ``` #### 實作檔案 ``` D:\dev\ez\17168erp\git_17888\web\App_Code\api\pivot01Controller.cs ``` #### 程式架構 ```csharp using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Linq; using System.Web.Http; using System.Configuration; [ezAuthorize] public class pivot01Controller : ApiController { private string _connectionString = ConfigurationManager.ConnectionStrings["ezEntities"].ConnectionString; // 通用 VIEW 查詢方法 private DataTable ExecuteSqlQuery(string sql, SqlParameter[] parameters = null); private List> DataTableToDictionary(DataTable dt); // API 端點 [HttpGet] [Route("api/pivot01/activity_stats")] public IHttpActionResult GetActivityStats(int year, bool includeNoDate = true); [HttpGet] [Route("api/pivot01/registration_details")] public IHttpActionResult GetRegistrationDetails(int activityNum, int page = 1, int pageSize = 50); } ``` #### 開發步驟 - [x] 確認資料庫中 VIEW 已建立 - [x] 建立 `pivot01Controller.cs` - [x] 實作通用 SQL 查詢方法 - [x] 實作 `GetActivityStats` 端點 - [x] 實作 `GetRegistrationDetails` 端點 - [ ] 使用 Postman/瀏覽器測試 API - [ ] 調整錯誤處理和回應格式 --- ### Phase 2 : implement aspx/cs, basic #### 目標 建立前端頁面 `pivot-01.aspx`,實作 Tab1(法會選擇)和 Tab5(報名明細資料) #### 實作檔案 ``` D:\dev\ez\17168erp\git_17888\web\admin\pivot\pivot-01.aspx D:\dev\ez\17168erp\git_17888\web\admin\pivot\pivot-01.aspx.cs ``` #### 設計原則 - **頁籤式介面**: 使用 Vuetify Tabs 元件 - **動態顯示**: 只在選擇法會後才顯示 Tab2-56 - **響應式設計**: 適配桌面和平板 - **資料快取**: 選擇法會後一次載入全部明細,供所有 Tab 使用 #### Tab1: 法會選擇頁面 ##### UI 結構 ``` ┌─────────────────────────────────────────────────┐ │ 📊 法會報名統計分析 │ ├─────────────────────────────────────────────────┤ │ Tab1: 法會選擇 │ Tab2 │ Tab3 │ Tab4 │ Tab5 │ │ ├─────────────────────────────────────────────────┤ │ 查詢條件 │ │ ┌──────────┐ ┌─────────┐ │ │ │ 年份: 2025 ▼│ │ [查看] │ │ │ └──────────┘ └─────────┘ │ │ │ │ 法會清單 (共 15 場) │ │ ┌─────────────────────────────────────────────┐│ │ │編號│法會名稱│日期│報名數│金額│動作│ ││ │ ├─────────────────────────────────────────────┤│ │ │123│2025梁皇...│3/1-3/10│150│50萬│[選擇]│ ││ │ │124│2025地藏...│4/5-4/12│200│60萬│[選擇]│ ││ │ └─────────────────────────────────────────────┘│ └─────────────────────────────────────────────────┘ ``` ##### 功能規格 1. **年份選擇器** - 下拉選單,範圍:今年 ± 5 年 - 預設值:當前年份(2025) - 可手動輸入其他年份 2. **查看按鈕** - 點擊後呼叫 API: `GET /api/pivot01/activity_stats?year={year}` - 顯示 Loading 動畫 - 更新法會清單表格 3. **法會清單表格** - 欄位:編號、法會名稱、日期範圍、報名數、總金額、操作按鈕 - 排序:日期降序 - 可搜尋、可排序 4. **選擇按鈕** - 點擊後呼叫 API: `GET /api/pivot01/registration_details?activityNum={num}&pageSize=10000` - 儲存完整資料到 Vue data (`rawData`) - 顯示 Tab2-6 - 自動切換到 Tab5 #### Tab5: 報名明細資料 ##### UI 結構 ``` ┌─────────────────────────────────────────────────┐ │ Tab1 │ Tab2 │ Tab3 │ Tab4 │ Tab5: 報名明細資料 │ │ ├─────────────────────────────────────────────────┤ │ 📌 當前法會: 2025年梁皇寶懺 (3/1-3/10) │ │ 📊 總筆數: 523 筆 │ 總金額: NT$ 1,569,000 │ │ │ │ 🔍 搜尋: [____________________] [匯出Excel] │ │ │ │ 報名明細表格 │ │ ┌─────────────────────────────────────────────┐│ │ │法會│信眾│姓名│日期│類型│名稱│主│量│金額│收│ ││ │ ├─────────────────────────────────────────────┤│ │ │梁皇│F001│王大│1/15│牌位│消災│是│1│3000│3K│ ││ │ │梁皇│F002│李小│1/16│供養│供僧│否│2│1000│1K│ ││ │ └─────────────────────────────────────────────┘│ │ 顯示 1-50 / 共 523 筆 [1][2][3]...[11] │ └─────────────────────────────────────────────────┘ ``` ##### 功能規格 1. **資訊列** - 顯示當前法會名稱和日期 - 顯示總筆數和總金額(從 `rawData` 計算) 2. **搜尋框** - 可搜尋:信眾編號、信眾姓名、功德類型、功德名稱 - 即時過濾(前端) 3. **匯出 Excel 按鈕** - 匯出當前過濾後的資料 - 使用 CSV 格式(UTF-8 BOM) - 檔名:`報名明細_{法會名稱}_{時間戳記}.csv` 4. **報名明細表格** - 欄位顏色標示: - 🟠 橘色:法會相關(法會名稱、日期) - 🔵 藍色:信眾相關(編號、姓名、報名日期) - 🟢 綠色:功德相關(類型、名稱、功德主) - 🟣 紫色:計算欄位(數量、金額、已收、未收) - 可排序所有欄位 - 分頁顯示(前端分頁) ##### CSS 樣式 ```css /* 欄位色彩標記 */ .field-activity { border-left: 4px solid #ff9800; } /* 橘色 */ .field-follower { border-left: 4px solid #2196f3; } /* 藍色 */ .field-merit { border-left: 4px solid #4caf50; } /* 綠色 */ .field-calculated { border-left: 4px solid #9c27b0; } /* 紫色 */ ``` #### Vue 資料結構 ```javascript data() { return { activeTab: 0, // 當前頁籤 // Tab1: 法會選擇 tab1: { year: 2025, // 預設今年 yearOptions: [2020, 2021, ..., 2030], loading: false, activities: [], // 法會清單 selectedActivity: null }, // 全域資料(所有 Tab 共用) rawData: [], // 完整報名明細資料 // Tab5: 報名明細 tab5: { headers: [ { text: '信眾姓名', value: '信眾姓名', class: 'field-follower' }, { text: '報名日期', value: '報名日期', class: 'field-follower' }, { text: '功德類型', value: '功德類型', class: 'field-merit' }, { text: '功德名稱', value: '功德名稱', class: 'field-merit' }, { text: '功德主', value: '功德主', class: 'field-merit' }, { text: '數量', value: '數量', class: 'field-calculated' }, { text: '金額', value: '金額', class: 'field-calculated' }, { text: '已收', value: '已收', class: 'field-calculated' }, { text: '未收', value: '未收', class: 'field-calculated' } ], search: '' } } }, computed: { hasData() { return this.rawData && this.rawData.length > 0; }, filteredData() { /* 搜尋過濾邏輯 */ }, totalAmount() { /* 總金額計算 */ } }, methods: { async loadActivities() { /* 載入法會清單 */ }, async selectActivity(activity) { /* 選擇法會並載入明細 */ }, exportToExcel() { /* 匯出 CSV */ } } ``` #### 開發步驟 - [x] 建立 `pivot-01.aspx.cs`(空架子,參考 query.aspx.cs) - [x] 建立 `pivot-01.aspx` 基本框架(MasterPage、Content) - [x] 實作 Tab1 UI(年份選擇、法會表格) - [x] 實作 Tab1 邏輯(API 呼叫、資料綁定) - [x] 實作 Tab5 UI(資訊列、搜尋框、明細表格) - [x] 實作 Tab5 邏輯(過濾、排序、分頁) - [x] 實作匯出 Excel 功能 - [x] 調整 CSS 樣式(欄位顏色標示) - [ ] 測試所有功能 #### 資料流程 ``` 使用者選擇年份 → API查詢法會列表 → 顯示法會表格 ↓ 使用者點選法會 → API載入完整明細 → 儲存到rawData ↓ 切換到Tab5 → 顯示表格 → 可搜尋/排序/匯出 ``` --- ### Phase 3 : Tab2 牌位分析實作計劃 ##### 目標 實作 Tab2 牌位分析功能,將 `rawData` 轉換為樞紐分析表格式,提供功德類型和功德名稱的分層統計 ##### 設計規格 ###### 資料結構分析 - **來源資料**: `rawData`(從 Tab1 選擇法會後載入的完整報名明細) - **分組維度**: - 第一層:功德類型 (`功德類型` 欄位) - 第二層:功德名稱 (`功德名稱` 欄位) - **統計值**: - 數量總計:sum(`數量`) - 金額總計:sum(`金額`) ###### Vue 資料結構設計 ```javascript // Tab2: 牌位分析 tab2: { expanded: [], // 展開的功德類型 ID 陣列 pivotData: [], // 處理後的樞紐資料 loading: false, headers: [ { text: '功德項目', value: 'name', width: 300 }, { text: '數量總計', value: 'totalQty', width: 120, align: 'end' }, { text: '金額總計', value: 'totalAmount', width: 150, align: 'end' }, { text: '', value: 'data-table-expand', width: 50 } // 展開按鈕欄位 ] } ``` ###### 樞紐資料轉換演算法 ```javascript computed: { tab2PivotData() { if (!this.rawData || this.rawData.length === 0) return []; // Step 1: 按功德類型分組 const typeGroups = {}; this.rawData.forEach(row => { const type = row.功德類型 || '未分類'; const name = row.功德名稱 || '未命名'; const qty = parseInt(row.數量) || 0; const amount = parseFloat(row.金額) || 0; if (!typeGroups[type]) { typeGroups[type] = { name: type, isGroup: true, totalQty: 0, totalAmount: 0, children: {} }; } // Step 2: 按功德名稱分組 if (!typeGroups[type].children[name]) { typeGroups[type].children[name] = { name: name, isGroup: false, totalQty: 0, totalAmount: 0, parentType: type }; } // Step 3: 累計統計 typeGroups[type].children[name].totalQty += qty; typeGroups[type].children[name].totalAmount += amount; typeGroups[type].totalQty += qty; typeGroups[type].totalAmount += amount; }); // Step 4: 轉換為表格資料 const result = []; Object.values(typeGroups).forEach(group => { result.push(group); Object.values(group.children).forEach(child => { result.push(child); }); }); return result; } } ``` ###### UI 實作規格 **表格結構**: ```html ``` **樣式設計**: ```css /* Tab2 樞紐分析表樣式 */ .pivot-group-row { background-color: #f5f5f5 !important; border-left: 4px solid #ff9800; } .pivot-detail-row { border-left: 4px solid #4caf50; } .pivot-total-row { background-color: #e3f2fd !important; border-left: 4px solid #2196f3; font-weight: bold !important; } ``` ##### 實作步驟 1. **需求分析與設計** (1 小時) - 確認樞紐分析表的確切需求 - 設計資料轉換演算法 - 規劃 UI 元件結構 2. **資料處理邏輯** (2 小時) - 實作 `tab2PivotData` computed 屬性 - 實作資料分組和統計演算法 - 添加總計計算邏輯 3. **UI 元件實作** (2 小時) - 在 `pivot-01.aspx` 中添加 Tab2 內容 - 實作樞紐分析表格元件 - 添加展開/收合功能 4. **樣式與視覺化** (1 小時) - 設計分層樣式(群組 vs 明細) - 添加圖示區分功德類型和名稱 - 實作總計列樣式 5. **測試與優化** (1 小時) - 測試不同資料量的效能 - 驗證統計數字正確性 - 調整使用者體驗 ##### 預期成果 **功能完成標準**: - ✅ 可正確按功德類型和功德名稱分組顯示 - ✅ 統計數字正確(數量總計、金額總計) - ✅ 支援展開/收合功德類型 - ✅ 有明顯的視覺層級區分 - ✅ 表格底部顯示全部總計 - ✅ 響應式設計適配不同螢幕 **資料範例展示**: ``` ┌─────────────────────────────────────────────────┐ │ 📊 牌位分析 │ ├─────────────────────────────────────────────────┤ │ ▼ 🏷️ 牌位 │ 150 │ NT$ 450,000 │ │ │ ● 消災大牌位 │ 80 │ NT$ 240,000 │ │ │ ● 祈福小牌位 │ 70 │ NT$ 210,000 │ │ │ ▼ 🏷️ 供養 │ 95 │ NT$ 285,000 │ │ │ ● 供僧 │ 45 │ NT$ 135,000 │ │ │ ● 供燈 │ 50 │ NT$ 150,000 │ │ ├─────────────────────────────────────────────────┤ │ 總計 │ 245 │ NT$ 735,000 │ │ └─────────────────────────────────────────────────┘ ``` ### Phase 4 : Tab3 信眾報名分析實作計劃 ##### 目標 實作 Tab3 信眾報名分析功能,統計每位信眾的報名情況,區分功德主和一般信眾的數量與金額 ##### 設計規格 ###### 需求分析 根據文件說明: - **搜尋欄位**: 關鍵字(姓名過濾) - **PIVOT 運算欄位**: - 信眾編號 - 信眾姓名 - 功德主數量(功德主=是) - 功德主金額(功德主=是) - 功德數量(功德主=否) - 功德金額(功德主=否) ###### 資料結構分析 - **來源資料**: `rawData` - **分組維度**: 信眾(`信眾編號` + `信眾姓名`) - **統計維度**: - 按 `功德主` 欄位分類("是" / "否") - 計算數量和金額 ###### Vue 資料結構設計 ```javascript // Tab3: 信眾報名分析 tab3: { search: '', // 搜尋關鍵字 headers: [ { text: '信眾編號', value: '信眾編號', width: 120, class: 'field-follower' }, { text: '信眾姓名', value: '信眾姓名', width: 120, class: 'field-follower' }, { text: '功德主數量', value: '功德主數量', width: 120, align: 'end', class: 'field-calculated' }, { text: '功德主金額', value: '功德主金額', width: 150, align: 'end', class: 'field-calculated' }, { text: '功德數量', value: '功德數量', width: 120, align: 'end', class: 'field-calculated' }, { text: '功德金額', value: '功德金額', width: 150, align: 'end', class: 'field-calculated' } ] } ``` ###### 樞紐資料轉換演算法 ```javascript computed: { tab3PivotData() { if (!this.rawData || this.rawData.length === 0) return []; // Step 1: 按信眾分組 const followerGroups = {}; this.rawData.forEach(row => { const fNumber = row.信眾編號 || ''; const fName = row.信眾姓名 || ''; const key = fNumber + '_' + fName; const qty = parseInt(row.數量) || 0; const amount = parseFloat(row.金額) || 0; const isMaster = row.功德主 === '是'; if (!followerGroups[key]) { followerGroups[key] = { 信眾編號: fNumber, 信眾姓名: fName, 功德主數量: 0, 功德主金額: 0, 功德數量: 0, 功德金額: 0 }; } // Step 2: 累計統計 if (isMaster) { followerGroups[key].功德主數量 += qty; followerGroups[key].功德主金額 += amount; } else { followerGroups[key].功德數量 += qty; followerGroups[key].功德金額 += amount; } }); // Step 3: 轉換為陣列並排序 return Object.values(followerGroups) .sort((a, b) => { // 依功德主金額降序,再依姓名升序 const amountDiff = (b.功德主金額 + b.功德金額) - (a.功德主金額 + a.功德金額); return amountDiff !== 0 ? amountDiff : a.信眾姓名.localeCompare(b.信眾姓名); }); }, // 搜尋過濾 tab3FilteredData() { if (!this.tab3.search) return this.tab3PivotData; const keyword = this.tab3.search.toLowerCase(); return this.tab3PivotData.filter(row => { return row.信眾編號.toLowerCase().includes(keyword) || row.信眾姓名.toLowerCase().includes(keyword); }); }, // 總計統計 tab3Summary() { const data = this.tab3FilteredData; return { 總人數: data.length, 功德主數量合計: data.reduce((sum, row) => sum + row.功德主數量, 0), 功德主金額合計: data.reduce((sum, row) => sum + row.功德主金額, 0), 功德數量合計: data.reduce((sum, row) => sum + row.功德數量, 0), 功德金額合計: data.reduce((sum, row) => sum + row.功德金額, 0) }; } } ``` ###### UI 實作規格 **表格結構**: ```html mdi-account-multiple 信眾報名分析 {{ tab1.selectedActivity.法會名稱 }} mdi-download 匯出 Excel
總人數
{{ tab3Summary.總人數 }} 人
功德主數量
{{ tab3Summary.功德主數量合計.toLocaleString() }}
功德主金額
{{ formatCurrency(tab3Summary.功德主金額合計) }}
總金額
{{ formatCurrency(tab3Summary.功德主金額合計 + tab3Summary.功德金額合計) }}
``` **樣式設計**: ```css /* Tab3 信眾分析樣式 */ .info-bar { background-color: #f5f5f5; border-radius: 8px; padding: 16px; } .info-bar .text-h6 { color: #1976d2; margin-top: 4px; } /* 功德主高亮 */ .follower-master-highlight { background-color: #fff3e0 !important; } ``` ##### 實作步驟 1. **資料處理邏輯** (2 小時) - 實作 `tab3PivotData` computed 屬性 - 實作信眾分組演算法 - 區分功德主和一般功德統計 - 實作搜尋過濾邏輯 2. **UI 元件實作** (2 小時) - 在 `pivot-01.aspx` 中添加 Tab3 內容 - 實作搜尋框和過濾功能 - 實作統計資訊列 - 實作信眾統計表格 3. **匯出功能** (1 小時) - 實作 `exportTab3ToCSV()` 方法 - 處理 CSV 格式和編碼 4. **測試與優化** (1 小時) - 測試不同搜尋條件 - 驗證統計數字正確性 - 測試匯出功能 ##### 預期成果 **功能完成標準**: - ✅ 可正確按信眾分組並統計 - ✅ 區分功德主和一般功德的數量/金額 - ✅ 搜尋功能正常運作 - ✅ 統計資訊列顯示正確 - ✅ 支援匯出 CSV - ✅ 資料排序合理(按金額降序) **資料範例展示**: ``` ┌────────────────────────────────────────────────────────────────┐ │ 👥 信眾報名分析 114年法會報名統計分析 │ ├────────────────────────────────────────────────────────────────┤ │ 🔍 [搜尋信眾... ] [匯出Excel] │ │ │ │ 📊 總人數: 85 人 │ 功德主數量: 150 │ 總金額: NT$ 1,569,000 │ ├────────────────────────────────────────────────────────────────┤ │ 編號 │ 姓名 │ 功德主數量 │ 功德主金額 │ 功德數量 │ 功德金額 │ │ F001 │ 王大明 │ 5 │ NT$ 15,000 │ 3 │ NT$ 9,000│ │ F002 │ 李小華 │ 3 │ NT$ 9,000 │ 5 │ NT$ 5,000│ │ F003 │ 張美麗 │ 0 │ NT$ 0 │ 2 │ NT$ 6,000│ └────────────────────────────────────────────────────────────────┘ ``` --- ### Phase 5 : Tab4 功德主查詢分析實作計劃 ##### 目標 實作 Tab4 功德主查詢分析功能,提供動態樞紐分析表,可按功德類型和功德項目查看每位信眾的數量或金額 ##### 設計規格 ###### 需求分析 根據文件說明: - **搜尋欄位**: - 關鍵字:姓名過濾 - 功德類型下拉:全部或單一分類 - RADIO:切換值:金額 或 數量 - CHECK:功德主:是/否 - **PIVOT TABLE**: - 第一欄:信眾姓名(FIXED) - 第二欄:總計(金額或數量) - 第三欄以後:所選功德類型(或全部)的功德項目(橫向展開) - 資料列:該信眾在該功德項目的金額或數量 ###### 資料結構分析 - **來源資料**: `rawData` - **動態維度**: - 列:信眾姓名 - 欄:功德項目(依選擇的功德類型過濾) - 值:數量 或 金額(可切換) - **過濾條件**: - 姓名關鍵字 - 功德類型 - 功德主狀態 ###### Vue 資料結構設計 ```javascript // Tab4: 功德主查詢分析 tab4: { search: '', // 姓名關鍵字 meritType: '全部', // 功德類型選擇 meritTypeOptions: [], // 功德類型下拉選項 valueType: 'amount', // 'amount' 或 'quantity' showMasterOnly: false, // 只顯示功德主 loading: false } ``` ###### 樞紐資料轉換演算法 ```javascript computed: { // 功德類型選項(從 rawData 動態產生) tab4MeritTypeOptions() { if (!this.rawData || this.rawData.length === 0) return ['全部']; const types = new Set(this.rawData.map(row => row.功德類型).filter(Boolean)); return ['全部', ...Array.from(types).sort()]; }, // 過濾後的原始資料 tab4FilteredRawData() { let data = this.rawData; // 功德主過濾 if (this.tab4.showMasterOnly) { data = data.filter(row => row.功德主 === '是'); } // 功德類型過濾 if (this.tab4.meritType !== '全部') { data = data.filter(row => row.功德類型 === this.tab4.meritType); } // 姓名過濾 if (this.tab4.search) { const keyword = this.tab4.search.toLowerCase(); data = data.filter(row => row.信眾姓名.toLowerCase().includes(keyword) ); } return data; }, // 取得功德項目清單(橫向欄位) tab4MeritItems() { const items = new Set( this.tab4FilteredRawData.map(row => row.功德名稱).filter(Boolean) ); return Array.from(items).sort(); }, // 動態表頭 tab4Headers() { const headers = [ { text: '信眾姓名', value: '信眾姓名', width: 150, fixed: true, class: 'field-follower' } ]; // 總計欄 const valueLabel = this.tab4.valueType === 'amount' ? '總金額' : '總數量'; headers.push({ text: valueLabel, value: '總計', width: 120, align: 'end', class: 'field-calculated font-weight-bold' }); // 功德項目欄(動態) this.tab4MeritItems.forEach(item => { headers.push({ text: item, value: item, width: 100, align: 'end', class: 'field-merit' }); }); return headers; }, // 樞紐分析資料 tab4PivotData() { if (!this.tab4FilteredRawData || this.tab4FilteredRawData.length === 0) return []; // Step 1: 按信眾和功德項目分組 const followerMeritMap = {}; this.tab4FilteredRawData.forEach(row => { const fName = row.信眾姓名; const mName = row.功德名稱; const qty = parseInt(row.數量) || 0; const amount = parseFloat(row.金額) || 0; if (!followerMeritMap[fName]) { followerMeritMap[fName] = { 信眾姓名: fName }; } if (!followerMeritMap[fName][mName]) { followerMeritMap[fName][mName] = { qty: 0, amount: 0 }; } followerMeritMap[fName][mName].qty += qty; followerMeritMap[fName][mName].amount += amount; }); // Step 2: 轉換為表格格式 const result = Object.values(followerMeritMap).map(follower => { const row = { 信眾姓名: follower.信眾姓名 }; let total = 0; // 計算每個功德項目的值 this.tab4MeritItems.forEach(item => { if (follower[item]) { const value = this.tab4.valueType === 'amount' ? follower[item].amount : follower[item].qty; row[item] = value; total += value; } else { row[item] = 0; } }); row.總計 = total; return row; }); // Step 3: 依總計降序排列 return result.sort((a, b) => b.總計 - a.總計); } } ``` ###### UI 實作規格 **表格結構**: ```html mdi-table-pivot 功德主查詢分析 {{ tab1.selectedActivity.法會名稱 }}
信眾數: {{ tab4PivotData.length }} 人 功德項目: {{ tab4MeritItems.length }} 項 mdi-download 匯出 Excel
``` **樣式設計**: ```css /* Tab4 橫向樞紐表樣式 */ .pivot-table-horizontal { overflow-x: auto; } .pivot-table-horizontal .v-data-table__wrapper { overflow-x: auto; max-height: 600px; } /* 固定第一欄 */ .pivot-table-horizontal th:first-child, .pivot-table-horizontal td:first-child { position: sticky; left: 0; z-index: 2; background-color: #fff; box-shadow: 2px 0 4px rgba(0,0,0,0.1); } /* 總計欄樣式 */ .pivot-table-horizontal th:nth-child(2), .pivot-table-horizontal td:nth-child(2) { background-color: #f5f5f5; font-weight: bold; } /* 零值淡化 */ .grey--text { color: #9e9e9e !important; } ``` ##### 實作步驟 1. **資料處理邏輯** (3 小時) - 實作過濾條件邏輯 - 實作動態表頭生成 - 實作橫向樞紐分析演算法 - 處理金額/數量切換 2. **UI 元件實作** (3 小時) - 實作搜尋與過濾區塊 - 實作動態表頭的 v-data-table - 實作固定第一欄(信眾姓名) - 處理橫向滾動 3. **互動功能** (1 小時) - 實作即時過濾更新 - 實作值類型切換 - 實作功德主過濾 4. **匯出功能** (1 小時) - 實作 `exportTab4ToCSV()` 方法 - 處理動態欄位的 CSV 匯出 5. **測試與優化** (1 小時) - 測試各種過濾組合 - 測試橫向滾動和固定欄 - 驗證統計數字正確性 - 優化大量欄位時的效能 ##### 預期成果 **功能完成標準**: - ✅ 動態產生功德項目欄位 - ✅ 支援姓名、功德類型、功德主過濾 - ✅ 可切換金額/數量顯示 - ✅ 第一欄(姓名)固定,橫向滾動 - ✅ 總計欄醒目顯示 - ✅ 支援匯出 CSV(包含動態欄位) - ✅ 零值適當淡化顯示 **資料範例展示**: ``` ┌───────────────────────────────────────────────────────────────┐ │ 📊 功德主查詢分析 114年法會報名統計分析 │ ├───────────────────────────────────────────────────────────────┤ │ 🔍[姓名] [功德類型:全部▼] ⚪金額 ⚪數量 ☑只顯示功德主 │ │ │ │ 信眾數: 35 人 │ 功德項目: 8 項 │ [匯出Excel] │ ├───────────────────────────────────────────────────────────────┤ │ 姓名 │ 總金額 │ 消災大牌位│ 祈福小牌位│ 供僧 │ 供燈 │...│ │ 王大明 │ 50,000 │ 15,000 │ 10,000 │ 15K │ 10K │... │ │ 李小華 │ 35,000 │ 20,000 │ 5,000 │ 5K │ 5K │... │ │ 張美麗 │ 28,000 │ 0 │ 18,000 │ 5K │ 5K │... │ └───────────────────────────────────────────────────────────────┘ ↑固定欄 ↑ 橫向滾動區域 → ``` --- ## 測試計劃 ### Phase 1 測試 1. **API 端點測試**(使用 Postman) - 測試 `/api/pivot01/activity_stats?year=2025` - 測試不同年份參數 - 測試 `includeNoDate` 參數 - 測試 `/api/pivot01/registration_details?activityNum=123` - 測試分頁參數 - 驗證回應格式和資料正確性 ### Phase 2 測試 1. **UI 功能測試** - 測試年份選擇器 - 測試法會清單載入 - 測試選擇法會功能 - 測試 Tab 切換(Tab2-6 在選擇前應隱藏) 2. **資料顯示測試** - 驗證 Tab5 資料正確顯示 - 測試搜尋功能(信眾、功德類型等) - 測試排序功能 - 測試分頁功能 3. **匯出測試** - 測試匯出 CSV 檔案 - 驗證檔案格式正確(UTF-8 BOM) - 測試特殊字元處理 - 測試中文編碼 4. **瀏覽器相容性** - Chrome - Edge - Firefox --- ## 預期成果 ### Phase 1 完成後 ✅ `pivot01Controller.cs` 正常運作 ✅ API 端點可正確查詢資料 ✅ 回應格式符合前端需求 ✅ 錯誤處理完善 ### Phase 2 完成後 ✅ `pivot-01.aspx` 頁面正常運作 ✅ Tab1 可正常查詢和選擇法會 ✅ Tab5 可正常顯示報名明細 ✅ 搜尋、排序、分頁功能正常 ✅ 匯出 Excel 功能正常 ✅ 使用者體驗良好