Files
17168ERP/web/admin/pivot/README-pivot-01.md
2025-10-19 21:59:22 +08:00

46 KiB
Raw Blame History

法會報名統計

相關檢視VIEW

法會統計

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

報名明細查詢

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): 是否包含無日期的法會

查詢邏輯:

SELECT * FROM [法會統計]
WHERE (YEAR(開始日期) = @year OR YEAR(結束日期) = @year)
   OR (@includeNoDate = 1 AND (開始日期 IS NULL OR 結束日期 IS NULL))
ORDER BY 開始日期 DESC, 結束日期 DESC

回應格式:

{
  "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

查詢邏輯:

SELECT * FROM [報名明細查詢]
WHERE 法會ID = @activityNum
ORDER BY 報名日期 DESC, 報名編號 DESC
OFFSET (@page - 1) * @pageSize ROWS
FETCH NEXT @pageSize ROWS ONLY

回應格式:

{
  "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

程式架構

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<Dictionary<string, object>> 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);
}

開發步驟

  • 確認資料庫中 VIEW 已建立
  • 建立 pivot01Controller.cs
  • 實作通用 SQL 查詢方法
  • 實作 GetActivityStats 端點
  • 實作 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 樣式
/* 欄位色彩標記 */
.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 資料結構

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 */ }
}

開發步驟

  • 建立 pivot-01.aspx.cs(空架子,參考 query.aspx.cs
  • 建立 pivot-01.aspx 基本框架MasterPage、Content
  • 實作 Tab1 UI年份選擇、法會表格
  • 實作 Tab1 邏輯API 呼叫、資料綁定)
  • 實作 Tab5 UI資訊列、搜尋框、明細表格
  • 實作 Tab5 邏輯(過濾、排序、分頁)
  • 實作匯出 Excel 功能
  • 調整 CSS 樣式(欄位顏色標示)
  • 測試所有功能

資料流程

使用者選擇年份 → API查詢法會列表 → 顯示法會表格
              ↓
使用者點選法會 → API載入完整明細 → 儲存到rawData
              ↓
         切換到Tab5 → 顯示表格 → 可搜尋/排序/匯出

Phase 3 : Tab2 牌位分析實作計劃

目標

實作 Tab2 牌位分析功能,將 rawData 轉換為樞紐分析表格式,提供功德類型和功德名稱的分層統計

設計規格
資料結構分析
  • 來源資料: rawData(從 Tab1 選擇法會後載入的完整報名明細)
  • 分組維度:
    • 第一層:功德類型 (功德類型 欄位)
    • 第二層:功德名稱 (功德名稱 欄位)
  • 統計值:
    • 數量總計sum(數量)
    • 金額總計sum(金額)
Vue 資料結構設計
// 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 } // 展開按鈕欄位
  ]
}
樞紐資料轉換演算法
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 實作規格

表格結構:

<v-data-table
  :headers="tab2.headers"
  :items="tab2PivotData"
  :expanded.sync="tab2.expanded"
  show-expand
  item-key="name"
  class="elevation-1"
  hide-default-footer
>
  <!-- 功德項目欄位 -->
  <template v-slot:item.name="{ item }">
    <div :class="{'font-weight-bold primary--text': item.isGroup, 'ml-6': !item.isGroup}">
      <v-icon v-if="item.isGroup" left color="orange">mdi-tag</v-icon>
      <v-icon v-else left color="green" small>mdi-circle-small</v-icon>
      {{ item.name }}
    </div>
  </template>
  
  <!-- 數量總計欄位 -->
  <template v-slot:item.totalQty="{ item }">
    <span :class="{'font-weight-bold': item.isGroup}">
      {{ item.totalQty.toLocaleString() }}
    </span>
  </template>
  
  <!-- 金額總計欄位 -->
  <template v-slot:item.totalAmount="{ item }">
    <span :class="{'font-weight-bold': item.isGroup}">
      {{ formatCurrency(item.totalAmount) }}
    </span>
  </template>
  
  <!-- 總計 Footer -->
  <template v-slot:foot>
    <tfoot>
      <tr class="grey lighten-3">
        <td class="font-weight-bold">總計</td>
        <td class="text-right font-weight-bold">{{ grandTotalQty.toLocaleString() }}</td>
        <td class="text-right font-weight-bold">{{ formatCurrency(grandTotalAmount) }}</td>
        <td></td>
      </tr>
    </tfoot>
  </template>
</v-data-table>

樣式設計:

/* 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 資料結構設計
// 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' }
  ]
}
樞紐資料轉換演算法
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 實作規格

表格結構:

<v-card>
  <v-card-title class="success white--text">
    <v-icon left color="white">mdi-account-multiple</v-icon>
    信眾報名分析
    <v-spacer></v-spacer>
    <v-chip v-if="tab1.selectedActivity" color="white" text-color="success">
      {{ tab1.selectedActivity.法會名稱 }}
    </v-chip>
  </v-card-title>
  
  <v-card-text>
    <!-- 搜尋列 -->
    <v-row class="mb-4">
      <v-col cols="12" md="6">
        <v-text-field
          v-model="tab3.search"
          label="搜尋信眾(編號或姓名)"
          prepend-icon="mdi-magnify"
          clearable
          outlined
          dense
        ></v-text-field>
      </v-col>
      <v-col cols="12" md="6" class="text-right">
        <v-btn color="primary" @click="exportTab3ToCSV">
          <v-icon left>mdi-download</v-icon>
          匯出 Excel
        </v-btn>
      </v-col>
    </v-row>
    
    <!-- 統計資訊列 -->
    <div class="info-bar mb-4">
      <v-row>
        <v-col cols="6" md="3">
          <div><strong>總人數</strong></div>
          <div class="text-h6">{{ tab3Summary.總人數 }} 人</div>
        </v-col>
        <v-col cols="6" md="3">
          <div><strong>功德主數量</strong></div>
          <div class="text-h6">{{ tab3Summary.功德主數量合計.toLocaleString() }}</div>
        </v-col>
        <v-col cols="6" md="3">
          <div><strong>功德主金額</strong></div>
          <div class="text-h6">{{ formatCurrency(tab3Summary.功德主金額合計) }}</div>
        </v-col>
        <v-col cols="6" md="3">
          <div><strong>總金額</strong></div>
          <div class="text-h6 primary--text">
            {{ formatCurrency(tab3Summary.功德主金額合計 + tab3Summary.功德金額合計) }}
          </div>
        </v-col>
      </v-row>
    </div>
    
    <!-- 信眾統計表格 -->
    <v-data-table
      :headers="tab3.headers"
      :items="tab3FilteredData"
      :items-per-page="20"
      class="elevation-1"
    >
      <!-- 金額欄位格式化 -->
      <template v-slot:item.功德主金額="{ item }">
        <span :class="{'font-weight-bold success--text': item.功德主金額 > 0}">
          {{ formatCurrency(item.功德主金額) }}
        </span>
      </template>
      
      <template v-slot:item.功德金額="{ item }">
        {{ formatCurrency(item.功德金額) }}
      </template>
      
      <!-- 無資料提示 -->
      <template v-slot:no-data>
        <v-alert type="info" class="ma-4">
          {{ tab3.search ? '查無符合條件的信眾' : '請先選擇法會以載入分析資料' }}
        </v-alert>
      </template>
    </v-data-table>
  </v-card-text>
</v-card>

樣式設計:

/* 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 資料結構設計
// Tab4: 功德主查詢分析
tab4: {
  search: '',  // 姓名關鍵字
  meritType: '全部',  // 功德類型選擇
  meritTypeOptions: [],  // 功德類型下拉選項
  valueType: 'amount',  // 'amount' 或 'quantity'
  showMasterOnly: false,  // 只顯示功德主
  loading: false
}
樞紐資料轉換演算法
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 實作規格

表格結構:

<v-card>
  <v-card-title class="warning white--text">
    <v-icon left color="white">mdi-table-pivot</v-icon>
    功德主查詢分析
    <v-spacer></v-spacer>
    <v-chip v-if="tab1.selectedActivity" color="white" text-color="warning">
      {{ tab1.selectedActivity.法會名稱 }}
    </v-chip>
  </v-card-title>
  
  <v-card-text>
    <!-- 搜尋與過濾列 -->
    <v-row class="mb-4">
      <!-- 姓名搜尋 -->
      <v-col cols="12" md="3">
        <v-text-field
          v-model="tab4.search"
          label="搜尋姓名"
          prepend-icon="mdi-magnify"
          clearable
          outlined
          dense
        ></v-text-field>
      </v-col>
      
      <!-- 功德類型下拉 -->
      <v-col cols="12" md="3">
        <v-select
          v-model="tab4.meritType"
          :items="tab4MeritTypeOptions"
          label="功德類型"
          prepend-icon="mdi-tag"
          outlined
          dense
        ></v-select>
      </v-col>
      
      <!-- 值類型切換 -->
      <v-col cols="12" md="3">
        <v-radio-group v-model="tab4.valueType" row dense>
          <v-radio label="金額" value="amount"></v-radio>
          <v-radio label="數量" value="quantity"></v-radio>
        </v-radio-group>
      </v-col>
      
      <!-- 功德主過濾 -->
      <v-col cols="12" md="3">
        <v-checkbox
          v-model="tab4.showMasterOnly"
          label="只顯示功德主"
          dense
        ></v-checkbox>
      </v-col>
    </v-row>
    
    <!-- 統計資訊 -->
    <div class="info-bar mb-4">
      <v-row>
        <v-col cols="4">
          <strong>信眾數:</strong> {{ tab4PivotData.length }} 人
        </v-col>
        <v-col cols="4">
          <strong>功德項目:</strong> {{ tab4MeritItems.length }} 項
        </v-col>
        <v-col cols="4">
          <v-btn color="primary" small @click="exportTab4ToCSV">
            <v-icon left small>mdi-download</v-icon>
            匯出 Excel
          </v-btn>
        </v-col>
      </v-row>
    </div>
    
    <!-- 樞紐分析表格 -->
    <v-data-table
      :headers="tab4Headers"
      :items="tab4PivotData"
      :items-per-page="20"
      class="elevation-1 pivot-table-horizontal"
      fixed-header
      dense
    >
      <!-- 總計欄位強調 -->
      <template v-slot:item.總計="{ item }">
        <span class="font-weight-bold primary--text">
          {{ tab4.valueType === 'amount' ? formatCurrency(item.總計) : item.總計.toLocaleString() }}
        </span>
      </template>
      
      <!-- 功德項目欄位格式化 -->
      <template v-for="item in tab4MeritItems" v-slot:[`item.${item}`]="{ item: row }">
        <span :class="{'grey--text': row[item] === 0}">
          {{ tab4.valueType === 'amount' ? formatCurrency(row[item]) : (row[item] || '-') }}
        </span>
      </template>
      
      <!-- 無資料提示 -->
      <template v-slot:no-data>
        <v-alert type="info" class="ma-4">
          {{ tab4.search ? '查無符合條件的信眾' : '請先選擇法會以載入分析資料' }}
        </v-alert>
      </template>
    </v-data-table>
  </v-card-text>
</v-card>

樣式設計:

/* 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 功能正常 使用者體驗良好