1401 lines
46 KiB
Markdown
1401 lines
46 KiB
Markdown
# 法會報名統計
|
||
|
||
## 相關檢視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 功能正常
|
||
✅ 使用者體驗良好 |