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

768 lines
27 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.
# Pivot Module 執行計劃
## 概述
建立多頁籤數據透視查詢模組,參考 transfer 模組的設計架構,使用相同的技術棧與 UI/UX 模式,實現法會報名資料的多維度分析與展示。
---
## 技術架構
### 前端技術
- **框架**: Vue.js 2.x + Vuetify 2.x與 transfer 模組一致)
- **表格元件**: v-data-tableVuetify 內建表格元件)
- **頁籤元件**: v-tabs / v-tab / v-tab-itemVuetify 頁籤)
- **UI 框架**: Bootstrap 5 + Bootstrap IconsMasterPage
- **樣式**: 與 transfer 模組保持一致的視覺風格
### 後端 API
- **框架**: ASP.NET Web APIC#
- **ORM**: Entity Framework + LINQ
- **控制器**: `App_Code/api/pivotController.cs`(已建立)
- **資料庫視圖**: `報名明細查詢`SQL View
### 資料流架構(重要)★
```
查詢流程:
┌─────────────┐
│ Tab 1 │ → 選擇法會 → API 查詢一次 →
│ 查詢條件 │ (完整資料集)
└─────────────┘
┌─────────────────────────────────────────┐
│ Vue Data (存於前端 this.rawData) │
│ - 完整報名明細 │
│ - 一次性載入,不重複查詢 │
└─────────────────────────────────────────┘
┌──────┬──────┬──────┬──────┬──────┐
│Tab 2 │Tab 3 │Tab 4 │Tab 5 │Tab 6 │
│明細 │信眾 │收入 │趨勢 │對比 │
│ │ │ │ │ │
│ 純前端計算 / 過濾 / 分組 / 統計 │
│ 使用 computed / methods / filters │
└──────────────────────────────────────┘
```
**優點**:
1.**效能優化**: API 只查詢一次,減少伺服器負載
2.**即時響應**: 切換頁籤無延遲,使用者體驗佳
3.**離線分析**: 資料載入後可離線操作(過濾、排序、統計)
4.**減少流量**: 不重複傳輸相同資料
5.**一致性**: 所有頁籤基於同一份資料,確保一致性
**技術實現**:
- `this.rawData`: 原始完整資料Tab 1 查詢後存入)
- `computed properties`: 各頁籤的資料來源(動態計算)
- `methods`: 過濾、分組、統計邏輯
- `watch`: 監聽過濾條件變化
### 元件規格
1. **日期選擇器**: `v-date-picker``<input type="date">`
2. **下拉選單**: `v-select`(年份、月份、法會選擇)
3. **資料表格**: `v-data-table`(分頁、排序、過濾)
4. **頁籤切換**: `v-tabs`Tab 1~N
5. **按鈕群組**: `v-btn`(查詢、匯出、重設)
6. **載入狀態**: `:loading="loading"`
7. **視覺標記**: Bootstrap Badge橙、藍、綠、紫色標籤
---
## 頁籤設計
### Tab 1: 查詢條件設定
**功能目標**: 提供查詢條件,篩選法會並選擇目標法會
#### UI 布局
```
+--------------------------------------------------------------+
| [查詢條件] |
|--------------------------------------------------------------|
| 時間範圍: [年份 ▼] [月份 ▼] [查詢法會] |
|--------------------------------------------------------------|
| 法會清單: |
| +----------------------------------------------------------+ |
| | 序號 | 法會名稱 | 開始日期 | 結束日期 | 報名人數 | 操作 | |
| +----------------------------------------------------------+ |
| | 1 | 2025春季法會 | 2025-03-01 | 2025-03-15 | 120 | [選擇] | |
| | 2 | 2025夏季法會 | 2025-06-01 | 2025-06-20 | 85 | [選擇] | |
| +----------------------------------------------------------+ |
+--------------------------------------------------------------+
```
#### 查詢流程
1. 選擇年份2020~2025或月份1~12
2. 點擊「查詢法會」按鈕
3. API 回傳該期間的法會清單(`api/pivot/activity_stats`
4. 表格呈現法會清單(法會名稱、日期、統計)
5. 點擊「選擇」按鈕,載入該法會的詳細資料
6. 自動切換到 Tab 2詳細資料頁籤
#### API 整合
- **端點**: `GET api/pivot/activity_stats?startDate={start}&endDate={end}`
- **回傳**: 法會清單(含報名統計)
#### 資料查詢策略
```javascript
// 選擇法會後,一次性載入完整資料
selectActivity(item) {
this.loading = true;
this.selectedActivity = item;
// 一次性查詢完整報名明細(不分頁)
axios.get('/api/pivot/registration_details', {
params: {
activityNum: item.法會ID,
pageSize: 9999 // 取得全部資料
}
}).then(response => {
// 存入原始資料(供所有頁籤使用)
this.rawData = response.data.data.list;
// 自動切換到 Tab 2
this.activeTab = 1;
this.loading = false;
});
}
```
#### 元件範例
```html
<v-select
:items="yearOptions"
v-model="selectedYear"
label="年份"
dense
outlined
></v-select>
<v-select
:items="monthOptions"
v-model="selectedMonth"
label="月份"
dense
outlined
clearable
></v-select>
<v-btn color="primary" @click="loadActivities">查詢法會</v-btn>
<v-data-table
:headers="activityHeaders"
:items="activities"
:loading="loading"
item-key="法會ID"
>
<template v-slot:item.actions="{ item }">
<v-btn small color="success" @click="selectActivity(item)">選擇</v-btn>
</template>
</v-data-table>
```
---
### Tab 2: 報名明細資料
**功能目標**: 完整呈現該場法會的所有報名明細
#### UI 布局
```
+--------------------------------------------------------------+
| [報名明細] 法會: 2025春季法會 (2025-03-01 ~ 2025-03-15) |
|--------------------------------------------------------------|
| 過濾: [信眾姓名] [功德類型 ▼] [狀態 ▼] [查詢] [匯出Excel] |
|--------------------------------------------------------------|
| +----------------------------------------------------------+ |
| | 報名編號 | 報名日期 | 信眾姓名 | 功德名稱 | 數量 | 金額 | |
| +----------------------------------------------------------+ |
| | 20250301001 | 2025-03-01 | 張三 | 點燈 | 1 | 500 | |
| | 20250301002 | 2025-03-01 | 李四 | 安太歲 | 1 | 300 | |
| +----------------------------------------------------------+ |
| 第 1 頁,共 10 頁(共 200 筆) |
+--------------------------------------------------------------+
```
#### 功能特性
1. **欄位顯示**: 報名編號、報名日期、信眾姓名、功德名稱、數量、金額、已收、未收
2. **過濾條件**: 信眾姓名(模糊搜尋)、功德類型(下拉選單)、功德主(是/否)
3. **排序**: 可依任意欄位排序(升序/降序)
4. **分頁**: 預設每頁 50 筆可調整10/20/50/100
5. **匯出**: 匯出 Excel/CSV
#### 欄位色彩標記(參考 Excel 視圖)
- **橙色(法會資料)**: 法會ID、法會名稱、開始日期、結束日期
- **藍色(信眾資料)**: 信眾編號、信眾姓名
- **綠色(功德資訊)**: 報名編號、報名日期、功德主、功德類型、功德名稱
- **紫色(計算欄位)**: 數量、金額、已收、未收
使用 Bootstrap Badge 或背景色區隔:
```html
<span class="badge bg-warning text-dark">法會</span>
<span class="badge bg-info">信眾</span>
<span class="badge bg-success">功德</span>
<span class="badge bg-secondary">計算</span>
```
#### 資料來源(前端計算)
```javascript
computed: {
// Tab 2: 報名明細(前端分頁、過濾)
filteredRegistrations() {
let data = this.rawData;
// 過濾:信眾姓名
if (this.filter.followerName) {
data = data.filter(x => x.信眾姓名.includes(this.filter.followerName));
}
// 過濾:功德類型
if (this.filter.itemKind) {
data = data.filter(x => x.功德類型 === this.filter.itemKind);
}
// 過濾:功德主
if (this.filter.isParent !== null) {
data = data.filter(x => x.功德主 === (this.filter.isParent ? '是' : '否'));
}
return data;
},
// 前端分頁
paginatedRegistrations() {
const start = (this.currentPage - 1) * this.pageSize;
const end = start + this.pageSize;
return this.filteredRegistrations.slice(start, end);
}
}
```
#### 元件範例
```html
<v-data-table
:headers="detailHeaders"
:items="registrations"
:loading="loading"
:server-items-length="totalCount"
:options.sync="options"
item-key="報名編號"
class="elevation-1"
>
<template v-slot:item.信眾姓名="{ item }">
<span class="badge bg-info me-1"></span>{{ item.信眾姓名 }}
</template>
<template v-slot:item.金額="{ item }">
<span class="badge bg-secondary me-1"></span>{{ item.金額 | currency }}
</template>
</v-data-table>
```
---
### Tab 3: 信眾參與分析
**功能目標**: 統計信眾的參與情況(參與次數、金額、最近參與)
#### UI 布局
```
+--------------------------------------------------------------+
| [信眾參與分析] |
|--------------------------------------------------------------|
| 過濾: [信眾編號] [參與次數 ≥] [總金額 ≥] [查詢] |
|--------------------------------------------------------------|
| +----------------------------------------------------------+ |
| | 信眾編號 | 姓名 | 參與次數 | 總金額 | 最近參與日期 | |
| +----------------------------------------------------------+ |
| | F001 | 張三 | 5 | 2500 | 2025-03-01 | |
| | F002 | 李四 | 3 | 1500 | 2025-02-15 | |
| +----------------------------------------------------------+ |
+--------------------------------------------------------------+
```
#### 功能特性
1. **統計欄位**: 參與次數、總金額、平均金額、最近參與日期
2. **過濾條件**: 信眾編號、參與次數閾值、總金額閾值
3. **排序**: 預設依參與次數降序
4. **分頁**: 預設每頁 50 筆
#### 資料來源(前端計算)
```javascript
computed: {
// Tab 3: 信眾參與分析(從 rawData 計算)
followerAnalysis() {
const followerMap = {};
this.rawData.forEach(item => {
const fNum = item.信眾編號;
if (!followerMap[fNum]) {
followerMap[fNum] = {
信眾編號: fNum,
姓名: item.信眾姓名,
參與次數: 0,
總金額: 0,
最近參與日期: item.報名日期
};
}
followerMap[fNum].參與次數++;
followerMap[fNum].總金額 += (item.金額 * item.數量);
// 更新最近參與日期
if (new Date(item.報名日期) > new Date(followerMap[fNum].最近參與日期)) {
followerMap[fNum].最近參與日期 = item.報名日期;
}
});
// 轉換為陣列,並計算平均金額
return Object.values(followerMap).map(f => ({
...f,
平均金額: Math.round(f.總金額 / f.參與次數)
})).sort((a, b) => b.參與次數 - a.參與次數); // 依參與次數降序
}
}
```
---
### Tab 4: 收入統計分析
**功能目標**: 依時間、法會、功德類型統計收入
#### UI 布局
```
+--------------------------------------------------------------+
| [收入統計分析] |
|--------------------------------------------------------------|
| 分組方式: ( ) 月份 ( ) 年度 (•) 法會 ( ) 功德類型 |
|--------------------------------------------------------------|
| +----------------------------------------------------------+ |
| | 分組名稱 | 報名人數 | 總金額 | 已收 | 未收 | 收款率 | |
| +----------------------------------------------------------+ |
| | 2025春季法會 | 120 | 60000 | 50000 | 10000 | 83.3% | |
| | 2025夏季法會 | 85 | 45000 | 40000 | 5000 | 88.9% | |
| +----------------------------------------------------------+ |
+--------------------------------------------------------------+
```
#### 功能特性
1. **分組方式**: 月份、年度、法會、功德類型
2. **統計欄位**: 報名人數、總金額、已收、未收、收款率
3. **圖表呈現**: 可加入 CanvasJS 長條圖/圓餅圖(選配)
4. **匯出**: 支援 Excel/CSV
#### 資料來源(前端計算)
```javascript
computed: {
// Tab 4: 收入統計分析(從 rawData 計算)
incomeStats() {
const statsMap = {};
const groupBy = this.groupBy; // 'monthly', 'yearly', 'activity', 'itemKind'
this.rawData.forEach(item => {
let key;
switch(groupBy) {
case 'monthly':
key = item.報名日期.substring(0, 7); // YYYY-MM
break;
case 'yearly':
key = item.報名日期.substring(0, 4); // YYYY
break;
case 'activity':
key = item.法會名稱;
break;
case 'itemKind':
key = item.功德類型;
break;
}
if (!statsMap[key]) {
statsMap[key] = {
分組名稱: key,
報名人數: 0,
總金額: 0,
已收: 0,
未收: 0
};
}
statsMap[key].報名人數++;
const amount = item.金額 * item.數量;
statsMap[key].總金額 += amount;
statsMap[key].已收 += item.已收 || 0;
statsMap[key].未收 += item.未收 || amount;
});
// 計算收款率
return Object.values(statsMap).map(s => ({
...s,
收款率: s.總金額 > 0 ? ((s.已收 / s.總金額) * 100).toFixed(1) + '%' : '0%'
}));
}
}
```
---
### Tab 5: 趨勢分析
**功能目標**: 顯示時間序列趨勢(收入、參與人數、法會數量)
#### UI 布局
```
+--------------------------------------------------------------+
| [趨勢分析] |
|--------------------------------------------------------------|
| 指標: ( ) 收入 (•) 參與人數 ( ) 法會數量 |
| 時間間隔: ( ) 月份 (•) 季度 ( ) 年度 |
|--------------------------------------------------------------|
| [折線圖] |
| |
| +----------------------------------------------------------+ |
| | 時間 | 數值 | 成長率 | |
| +----------------------------------------------------------+ |
| | 2025-01 | 120 | +10% | |
| | 2025-02 | 132 | +10% | |
| +----------------------------------------------------------+ |
+--------------------------------------------------------------+
```
#### 功能特性
1. **指標選擇**: 收入、參與人數、法會數量
2. **時間間隔**: 月份、季度、年度
3. **成長率計算**: 較前期成長率
4. **圖表**: CanvasJS 折線圖(選配)
#### 資料來源(前端計算)
```javascript
computed: {
// Tab 5: 趨勢分析(從 rawData 計算)
trendAnalysis() {
const metric = this.trendMetric; // 'income', 'followers', 'count'
const interval = this.trendInterval; // 'monthly', 'quarterly', 'yearly'
const trendMap = {};
this.rawData.forEach(item => {
let key;
switch(interval) {
case 'monthly':
key = item.報名日期.substring(0, 7); // YYYY-MM
break;
case 'quarterly':
const month = parseInt(item.報名日期.substring(5, 7));
const quarter = Math.ceil(month / 3);
key = `${item.報名日期.substring(0, 4)}-Q${quarter}`;
break;
case 'yearly':
key = item.報名日期.substring(0, 4); // YYYY
break;
}
if (!trendMap[key]) {
trendMap[key] = { 時間: key, 收入: 0, 人數: 0, 次數: 0 };
}
trendMap[key].收入 += (item.金額 * item.數量);
trendMap[key].人數++; // 簡化計算,實際可用 Set 去重
trendMap[key].次數++;
});
// 轉換為陣列並排序
const result = Object.values(trendMap).sort((a, b) => a.時間.localeCompare(b.時間));
// 計算成長率
return result.map((item, index) => {
if (index === 0) {
return { ...item, 數值: item[metric === 'income' ? '收入' : metric === 'followers' ? '人數' : '次數'], 成長率: '-' };
}
const prev = result[index - 1];
const currentValue = item[metric === 'income' ? '收入' : metric === 'followers' ? '人數' : '次數'];
const prevValue = prev[metric === 'income' ? '收入' : metric === 'followers' ? '人數' : '次數'];
const growthRate = prevValue > 0 ? (((currentValue - prevValue) / prevValue) * 100).toFixed(1) : '0';
return { ...item, 數值: currentValue, 成長率: growthRate + '%' };
});
}
}
```
---
### Tab 6: 對比分析(選配)
**功能目標**: 不同時期、法會的對比分析
#### UI 布局
```
+--------------------------------------------------------------+
| [對比分析] |
|--------------------------------------------------------------|
| 對比類型: (•) 法會對比 ( ) 年度對比 |
| 期間1: [2025春季法會 ▼] 期間2: [2024春季法會 ▼] |
|--------------------------------------------------------------|
| +----------------------------------------------------------+ |
| | 指標 | 期間1 | 期間2 | 差異 | 差異率 | |
| +----------------------------------------------------------+ |
| | 報名人數 | 120 | 100 | +20 | +20% | |
| | 總金額 | 60000 | 50000 | +10000 | +20% | |
| +----------------------------------------------------------+ |
+--------------------------------------------------------------+
```
#### 資料來源(前端計算)
```javascript
computed: {
// Tab 6: 對比分析(從 rawData 計算)
// 注意:對比分析可能需要跨法會資料,如需要可額外查詢
comparativeAnalysis() {
// 如果只在單一法會內對比(如月份對比),可用 rawData
// 如果需要跨法會對比,建議另外查詢或在 Tab 1 時一併載入多場法會資料
const period1Data = this.rawData.filter(x => {
// 依 period1 條件過濾
return this.isPeriod1(x);
});
const period2Data = this.rawData.filter(x => {
// 依 period2 條件過濾
return this.isPeriod2(x);
});
const stats1 = this.calculateStats(period1Data);
const stats2 = this.calculateStats(period2Data);
return [
{ 指標: '報名人數', 期間1: stats1.count, 期間2: stats2.count, 差異: stats1.count - stats2.count },
{ 指標: '總金額', 期間1: stats1.amount, 期間2: stats2.amount, 差異: stats1.amount - stats2.amount },
// ... 其他指標
];
}
}
```
---
## 視覺設計規範
### 色彩系統(參考 transfer
- **主色調**: Bootstrap 5 預設配色
- **成功/確認**: `bg-success` / `text-success`
- **警告**: `bg-warning` / `text-warning`
- **危險/錯誤**: `bg-danger` / `text-danger`
- **資訊**: `bg-info` / `text-info`
- **次要**: `bg-secondary` / `text-muted`
### 欄位色彩標記(對應 Excel
```html
<!-- 橙色:法會資料 -->
<span class="badge bg-warning text-dark"></span>
<!-- 藍色:信眾資料 -->
<span class="badge bg-info"></span>
<!-- 綠色:功德資訊 -->
<span class="badge bg-success">功德</span>
<!-- 紫色:計算欄位 -->
<span class="badge bg-secondary"></span>
```
### 表格樣式
```css
/* 與 transfer 保持一致 */
.v-data-table th {
background-color: #f5f5f5 !important;
font-weight: bold;
}
.v-data-table tbody tr:hover {
background-color: #f0f8ff !important;
}
/* 欄位色彩標記 */
.field-activity { border-left: 3px solid #ffc107; } /* 橙 */
.field-follower { border-left: 3px solid #17a2b8; } /* 藍 */
.field-merit { border-left: 3px solid #28a745; } /* 綠 */
.field-calculated { border-left: 3px solid #6c757d; } /* 紫 */
```
---
## API 端點總覽
| 端點 | 方法 | 功能 | 對應頁籤 | 使用時機 |
|------|------|------|----------|----------|
| `/api/pivot/activity_stats` | GET | 查詢法會清單與統計 | Tab 1 | 選擇查詢條件時 |
| `/api/pivot/registration_details` | GET | 報名明細查詢(**完整資料** | Tab 1 | **選擇法會後一次性載入** ★ |
| `/api/pivot/registration_details_export` | GET | 報名明細匯出 | Tab 2 | 匯出 Excel 時(選配) |
| `/api/pivot/excel_data_structured` | GET | Excel 數據連接 | 外部 | Power Query/Power Pivot |
**重要說明**:
-**Tab 2~6 不呼叫 API**,全部使用前端 `computed` 計算
-`registration_details` 查詢時使用 `pageSize=9999` 取得完整資料
- ✅ 原 `follower_analysis`, `income_stats`, `trend_analysis`, `comparative_analysis` 端點**保留備用**,但前端優先使用 computed
- ✅ 如資料量過大(>5000 筆),可調整策略改用分頁查詢
---
## 檔案結構
```
admin/pivot/
├── index.aspx # 首頁(已完成)
├── index.aspx.cs # 首頁邏輯(已完成)
├── query.aspx # 多頁籤查詢頁面(待建立)★
├── query.aspx.cs # 查詢頁面邏輯(待建立)★
└── README.md # 本文件
App_Code/api/
└── pivotController.cs # API 控制器(已完成)
```
---
## 實作步驟
### Phase 1: 建立查詢頁面骨架
1. ✅ 建立 `query.aspx`(參考 transfer/verify.aspx
2. ✅ 建立 `query.aspx.cs`(參考 transfer/verify.aspx.cs
3. ✅ 引入 Vue.js + Vuetify
4. ✅ 建立頁籤結構v-tabs
### Phase 2: 實作 Tab 1查詢條件
1. ✅ 年份/月份選擇器
2. ✅ 查詢法會按鈕與 API 整合(`activity_stats`
3. ✅ 法會清單表格v-data-table
4. ✅ 選擇法會邏輯(一次性載入完整資料)
5.`this.rawData` 資料結構設計
### Phase 3: 實作 Tab 2報名明細
1. ✅ 報名明細表格(前端分頁、排序、過濾)
2.`computed: filteredRegistrations` 實作
3. ✅ 欄位色彩標記badge
4. ✅ 匯出功能(選配)
### Phase 4: 實作 Tab 3~6分析頁籤
1. ✅ 信眾參與分析Tab 3- `computed: followerAnalysis`
2. ✅ 收入統計分析Tab 4- `computed: incomeStats`
3. ✅ 趨勢分析Tab 5- `computed: trendAnalysis`
4. ✅ 對比分析Tab 6選配- `computed: comparativeAnalysis`
### Phase 5: 優化與測試
1. ✅ UI/UX 調整(與 transfer 保持一致)
2. ✅ 效能優化(分頁、快取)
3. ✅ 瀏覽器相容性測試
4. ✅ 權限控制ezAuthorize
---
## 參考範例
### 1. transfer/verify.aspx
- 多階段流程設計程序1、程序2
- v-data-table 表格元件使用
- 信眾選擇對話框v-dialog
- 狀態選擇v-select
### 2. transfer/verify1.aspx
- 單階段流程設計
- 簡潔的查詢與確認流程
### 3. transfer/index.aspx
- 功能入口頁面設計
- Bootstrap Icons + Badge
- 三欄式布局
---
## 注意事項
1. **元件一致性**: 所有元件、樣式、命名均與 transfer 模組保持一致
2. **API 規範**: 遵循 RESTful 設計,統一回傳格式
3. **權限控制**: 所有 API 加上 `[ezAuthorize]`
4. **效能考量**:
-**前端計算策略**: Tab 1 查詢一次Tab 2~6 使用 Vue computed 計算
-**資料量控制**: 單一法會報名明細預估 <5000 筆,適合前端處理
- ⚠️ **大資料處理**: 若單場法會 >5000 筆,可改用 API 分頁查詢
-**記憶體管理**: 切換法會時清除舊資料(`this.rawData = []`
5. **視覺區隔**: 使用 Badge、邊框色、背景色標記欄位類型
6. **使用者體驗**: 載入狀態loading、錯誤提示alert、空資料提示
7. **前端效能優化**:
- 使用 `computed` 而非 `methods`(自動快取)
- 大型陣列操作使用 `Object.freeze()` 凍結原始資料
- v-data-table 啟用虛擬滾動(`:virtual-scroll="true"`,資料量 >1000 時)
---
## SQL View 參考
```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
```
---
## 資料結構範例
```javascript
// Vue data 結構
data() {
return {
// 原始完整資料Tab 1 查詢後存入,供所有頁籤使用)
rawData: [], // Array<報名明細物件>
// 選中的法會
selectedActivity: null,
// 當前頁籤索引
activeTab: 0, // 0=Tab1, 1=Tab2, 2=Tab3...
// Tab 2 過濾條件
filter: {
followerName: '',
itemKind: null,
isParent: null
},
// Tab 4 分組方式
groupBy: 'activity', // 'monthly', 'yearly', 'activity', 'itemKind'
// Tab 5 趨勢設定
trendMetric: 'income', // 'income', 'followers', 'count'
trendInterval: 'monthly', // 'monthly', 'quarterly', 'yearly'
// 載入狀態
loading: false
}
}
```
## Vue Computed 效能考量
```javascript
computed: {
// 使用 Object.freeze() 凍結大型陣列,提升效能
frozenRawData() {
return Object.freeze(this.rawData);
},
// 各頁籤 computed 基於 frozenRawData
filteredRegistrations() {
return this.frozenRawData.filter(/* 過濾邏輯 */);
}
}
```
## 執行確認
請檢視以上計劃,確認以下事項:
1. ✅ 頁籤設計Tab 1~6符合需求
2. ✅ 查詢流程(年/月 → 法會清單 → 明細資料)清晰
3. ✅ 視覺區隔色彩標記、Badge符合 Excel 概念
4. ✅ 技術架構Vue + Vuetify + API與 transfer 一致
5. ✅ 功能完整性(查詢、過濾、排序、匯出)
6. ✅ **資料流架構(一次查詢 + 前端計算)**合理且高效 ★
**確認後即開始實作 Phase 1建立查詢頁面骨架。**