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

1401 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 法會報名統計
## 相關檢視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<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);
}
```
#### 開發步驟
- [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
<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>
```
**樣式設計**:
```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
<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>
```
**樣式設計**:
```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
<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>
```
**樣式設計**:
```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 功能正常
✅ 使用者體驗良好