1375 lines
64 KiB
Plaintext
1375 lines
64 KiB
Plaintext
<%@ Page Title="法會報名統計分析" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" EnableEventValidation="false" CodeFile="pivot-01.aspx.cs" Inherits="admin_pivot_pivot01" %>
|
||
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" runat="Server">
|
||
<link rel="stylesheet" href="../../js/_bootstrap-icons-1.8.1/bootstrap-icons.css">
|
||
<style>
|
||
/* 限定範圍:使用 #pivot01-app 選擇器 */
|
||
|
||
/* 欄位色彩標記 */
|
||
#pivot01-app .field-activity { border-left: 4px solid #ff9800; } /* 橘色 - 法會 */
|
||
#pivot01-app .field-follower { border-left: 4px solid #2196f3; } /* 藍色 - 信眾 */
|
||
#pivot01-app .field-merit { border-left: 4px solid #4caf50; } /* 綠色 - 功德 */
|
||
#pivot01-app .field-calculated { border-left: 4px solid #9c27b0; } /* 紫色 - 計算 */
|
||
|
||
/* 表格樣式 */
|
||
#pivot01-app .v-data-table thead th {
|
||
background-color: #f5f5f5;
|
||
font-weight: bold;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 2;
|
||
}
|
||
|
||
#pivot01-app .v-data-table tbody tr:hover {
|
||
background-color: #e3f2fd;
|
||
}
|
||
|
||
/* 頁籤樣式 */
|
||
#pivot01-app .v-tabs {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
/* 查詢條件區域 */
|
||
#pivot01-app .query-section {
|
||
background-color: #f8f9fa;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
margin-top: 10px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
/* 資訊列樣式 - 預設使用灰色背景(Tab3/Tab4)*/
|
||
#pivot01-app .info-bar {
|
||
background-color: #f5f5f5;
|
||
border-radius: 8px;
|
||
padding: 16px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
/* 資訊列樣式 - Tab2/Tab5 使用漸層背景 */
|
||
#pivot01-app .info-bar.gradient {
|
||
background-color: #667eea;
|
||
color: white;
|
||
padding: 15px 20px;
|
||
margin: 10px 0;
|
||
}
|
||
|
||
#pivot01-app .info-bar .text-h6 {
|
||
margin-top: 4px;
|
||
}
|
||
|
||
/* 統計卡片 */
|
||
#pivot01-app .stat-card {
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
text-align: center;
|
||
}
|
||
|
||
#pivot01-app .stat-card .stat-value {
|
||
font-size: 1.8em;
|
||
font-weight: bold;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
#pivot01-app .stat-card .stat-label {
|
||
font-size: 0.9em;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
/* 載入動畫 */
|
||
#pivot01-app .loading-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 9999;
|
||
}
|
||
|
||
/* Tab2 樞紐分析表樣式 */
|
||
#pivot01-app .pivot-table .v-data-table__wrapper tbody tr:has(.font-weight-bold.primary--text) {
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
#pivot01-app .pivot-detail-row {
|
||
background-color: #ffffff;
|
||
}
|
||
|
||
#pivot01-app .pivot-detail-row td {
|
||
border-left: 3px solid #4caf50;
|
||
padding-left: 12px;
|
||
}
|
||
|
||
/* 收合按鈕樣式 */
|
||
#pivot01-app .pivot-table .v-btn--icon.v-size--small {
|
||
width: 24px;
|
||
height: 24px;
|
||
}
|
||
|
||
#pivot01-app .pivot-table .v-btn--icon .v-icon {
|
||
font-size: 16px;
|
||
}
|
||
|
||
/* 總計行樣式 */
|
||
#pivot01-app .pivot-table tfoot tr {
|
||
background-color: #e3f2fd;
|
||
}
|
||
|
||
#pivot01-app .pivot-table tfoot td {
|
||
border-top: 2px solid #2196f3;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* 確保欄位對齊 */
|
||
#pivot01-app .pivot-table .v-data-table__wrapper table {
|
||
table-layout: fixed;
|
||
}
|
||
|
||
#pivot01-app .pivot-table .v-data-table__wrapper th:nth-child(1),
|
||
#pivot01-app .pivot-table .v-data-table__wrapper td:nth-child(1) {
|
||
width: 300px;
|
||
}
|
||
|
||
#pivot01-app .pivot-table .v-data-table__wrapper th:nth-child(2),
|
||
#pivot01-app .pivot-table .v-data-table__wrapper td:nth-child(2) {
|
||
width: 120px;
|
||
text-align: right;
|
||
}
|
||
|
||
#pivot01-app .pivot-table .v-data-table__wrapper th:nth-child(3),
|
||
#pivot01-app .pivot-table .v-data-table__wrapper td:nth-child(3) {
|
||
width: 150px;
|
||
text-align: right;
|
||
}
|
||
|
||
/* Tab4 橫向樞紐表樣式 */
|
||
#pivot01-app .pivot-table-horizontal {
|
||
overflow-x: auto;
|
||
}
|
||
|
||
/* 覆蓋全域的表頭固定設定 */
|
||
#pivot01-app .pivot-table-horizontal thead th {
|
||
position: relative;
|
||
top: auto;
|
||
}
|
||
|
||
#pivot01-app .pivot-table-horizontal .v-data-table__wrapper {
|
||
overflow-x: auto;
|
||
overflow-y: visible;
|
||
}
|
||
|
||
/* 固定第一欄(信眾姓名)*/
|
||
#pivot01-app .pivot-table-horizontal thead th:first-child {
|
||
position: sticky;
|
||
left: 0;
|
||
z-index: 5;
|
||
background-color: #f5f5f5;
|
||
box-shadow: 2px 0 4px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
#pivot01-app .pivot-table-horizontal tbody td:first-child {
|
||
position: sticky;
|
||
left: 0;
|
||
z-index: 3;
|
||
background-color: #fff;
|
||
box-shadow: 2px 0 4px rgba(0,0,0,0.1);
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 固定第二欄(總計)*/
|
||
#pivot01-app .pivot-table-horizontal thead th:nth-child(2) {
|
||
position: sticky;
|
||
left: 150px;
|
||
z-index: 5;
|
||
background-color: #e8eaf6;
|
||
box-shadow: 2px 0 4px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
#pivot01-app .pivot-table-horizontal tbody td:nth-child(2) {
|
||
position: sticky;
|
||
left: 150px;
|
||
z-index: 3;
|
||
background-color: #f5f5f5;
|
||
font-weight: bold;
|
||
box-shadow: 2px 0 4px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
/* 零值淡化 */
|
||
#pivot01-app .grey--text {
|
||
color: #9e9e9e;
|
||
}
|
||
|
||
/* 表格包裝容器 */
|
||
#pivot01-app .table-wrapper {
|
||
overflow-x: auto;
|
||
position: relative;
|
||
}
|
||
</style>
|
||
</asp:Content>
|
||
|
||
<asp:Content ID="Content3" ContentPlaceHolderID="page_nav" runat="Server">
|
||
<h5 class="mb-0">
|
||
<i class="bi bi-pie-chart-fill"></i> 法會報名統計分析
|
||
</h5>
|
||
</asp:Content>
|
||
|
||
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
|
||
<div id="pivot01-app"><v-app>
|
||
<!-- 載入遮罩 -->
|
||
<div v-if="loading" class="loading-overlay">
|
||
<v-progress-circular
|
||
:size="70"
|
||
:width="7"
|
||
color="primary"
|
||
indeterminate
|
||
></v-progress-circular>
|
||
</div>
|
||
|
||
<v-container fluid>
|
||
<!-- 頁籤導航 -->
|
||
<v-tabs v-model="activeTab" background-color="white" color="primary" grow>
|
||
<v-tab>
|
||
<v-icon left>mdi-calendar-search</v-icon>
|
||
法會選擇
|
||
</v-tab>
|
||
<v-tab :disabled="!hasData">
|
||
<v-icon left>mdi-tablet-dashboard</v-icon>
|
||
牌位分析
|
||
</v-tab>
|
||
<v-tab :disabled="!hasData">
|
||
<v-icon left>mdi-account-group</v-icon>
|
||
信眾報名分析
|
||
</v-tab>
|
||
<v-tab :disabled="!hasData">
|
||
<v-icon left>mdi-account-star</v-icon>
|
||
功德主查詢分析
|
||
</v-tab>
|
||
<v-tab :disabled="!hasData">
|
||
<v-icon left>mdi-table-large</v-icon>
|
||
報名明細資料
|
||
<v-chip v-if="rawData.length > 0" small class="ml-2">{{ rawData.length }}</v-chip>
|
||
</v-tab>
|
||
</v-tabs>
|
||
|
||
<!-- 頁籤內容 -->
|
||
<v-tabs-items v-model="activeTab">
|
||
<!-- Tab1: 法會選擇 -->
|
||
<v-tab-item>
|
||
<v-card>
|
||
<v-card-title class="primary white--text">
|
||
<v-icon left color="white">mdi-filter</v-icon>
|
||
查詢條件
|
||
</v-card-title>
|
||
<v-card-text>
|
||
<div class="query-section">
|
||
<v-row align="center">
|
||
<v-col cols="12" md="3">
|
||
<v-select
|
||
v-model="tab1.year"
|
||
:items="tab1.yearOptions"
|
||
label="查詢年份"
|
||
dense
|
||
outlined
|
||
prepend-icon="mdi-calendar"
|
||
hide-details
|
||
></v-select>
|
||
</v-col>
|
||
<v-col cols="12" md="2">
|
||
<v-btn
|
||
color="primary"
|
||
large
|
||
block
|
||
@click="loadActivities"
|
||
:loading="tab1.loading"
|
||
>
|
||
<v-icon left>mdi-magnify</v-icon>
|
||
查看
|
||
</v-btn>
|
||
</v-col>
|
||
<v-col cols="12" md="7">
|
||
<v-alert
|
||
v-if="tab1.activities.length > 0"
|
||
dense
|
||
text
|
||
type="info"
|
||
class="mb-0"
|
||
>
|
||
共查詢到 <strong>{{ tab1.activities.length }}</strong> 場法會活動
|
||
</v-alert>
|
||
</v-col>
|
||
</v-row>
|
||
</div>
|
||
|
||
<!-- 法會清單 -->
|
||
<v-data-table
|
||
:headers="tab1Headers"
|
||
:items="tab1.activities"
|
||
:loading="tab1.loading"
|
||
loading-text="載入中..."
|
||
class="elevation-1"
|
||
item-key="num"
|
||
:items-per-page="15"
|
||
:footer-props="{
|
||
'items-per-page-options': [10, 15, 20, 50]
|
||
}"
|
||
>
|
||
<template v-slot:item.開始日期="{ item }">
|
||
{{ formatDate(item.開始日期) }}
|
||
</template>
|
||
<template v-slot:item.結束日期="{ item }">
|
||
{{ formatDate(item.結束日期) }}
|
||
</template>
|
||
<template v-slot:item.報名截止日="{ item }">
|
||
{{ formatDate(item.報名截止日) }}
|
||
</template>
|
||
<template v-slot:item.功德數量="{ item }">
|
||
<v-chip small color="info" text-color="white">{{ item.功德數量 || 0 }}</v-chip>
|
||
</template>
|
||
<template v-slot:item.合計="{ item }">
|
||
<strong>{{ formatCurrency(item.合計) }}</strong>
|
||
</template>
|
||
<template v-slot:item.actions="{ item }">
|
||
<v-btn
|
||
small
|
||
color="success"
|
||
@click="selectActivity(item)"
|
||
:loading="item.loading"
|
||
>
|
||
<v-icon left small>mdi-check-circle</v-icon>
|
||
選擇
|
||
</v-btn>
|
||
</template>
|
||
<template v-slot:no-data>
|
||
<v-alert type="info" class="ma-4">
|
||
請選擇年份後點擊「查看」按鈕查詢法會資料
|
||
</v-alert>
|
||
</template>
|
||
</v-data-table>
|
||
</v-card-text>
|
||
</v-card>
|
||
</v-tab-item>
|
||
|
||
<!-- Tab2: 牌位分析 -->
|
||
<v-tab-item>
|
||
<v-card>
|
||
<v-card-title class="info white--text">
|
||
<v-icon left color="white">mdi-tablet-dashboard</v-icon>
|
||
牌位分析
|
||
<v-spacer></v-spacer>
|
||
<v-chip v-if="tab1.selectedActivity" color="white" text-color="info">
|
||
{{ tab1.selectedActivity.活動名稱 }}
|
||
</v-chip>
|
||
</v-card-title>
|
||
<v-card-text>
|
||
<!-- 資訊列 -->
|
||
<div v-if="tab1.selectedActivity" class="info-bar gradient">
|
||
<v-row>
|
||
<v-col cols="12" md="6">
|
||
<div>
|
||
<v-icon color="white" left>mdi-chart-pie</v-icon>
|
||
<strong>樞紐分析表</strong>
|
||
</div>
|
||
<div class="mt-1">
|
||
<small>按功德類型和功德名稱分組統計</small>
|
||
</div>
|
||
</v-col>
|
||
<v-col cols="12" md="6" class="text-right">
|
||
<div>
|
||
項目總數: <strong>{{ tab2PivotData.filter(item => !item.isGroup).length }}</strong> 項
|
||
</div>
|
||
<div class="mt-1">
|
||
類型總數: <strong>{{ tab2PivotData.filter(item => item.isGroup).length }}</strong> 類
|
||
</div>
|
||
</v-col>
|
||
</v-row>
|
||
</div>
|
||
|
||
<!-- 樞紐分析表格 -->
|
||
<v-data-table
|
||
:headers="tab2.headers"
|
||
:items="tab2PivotData"
|
||
class="elevation-1 pivot-table"
|
||
hide-default-footer
|
||
disable-pagination
|
||
>
|
||
<!-- 功德項目欄位 -->
|
||
<template v-slot:item.name="{ item }">
|
||
<div :class="{'font-weight-bold primary--text': item.isGroup, 'ml-6': !item.isGroup}">
|
||
<!-- 群組項目:功德類型 -->
|
||
<template v-if="item.isGroup">
|
||
<v-btn @click="toggleGroup(item.id)" icon small class="mr-2">
|
||
<v-icon small>{{ tab2.collapsed.includes(item.id) ? 'mdi-chevron-right' : 'mdi-chevron-down' }}</v-icon>
|
||
</v-btn>
|
||
<v-icon left color="warning">mdi-tag</v-icon>
|
||
{{ item.name }}
|
||
</template>
|
||
<!-- 子項目:功德名稱 -->
|
||
<template v-else>
|
||
<v-icon left color="success" small>mdi-circle-small</v-icon>
|
||
{{ item.name }}
|
||
</template>
|
||
</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>
|
||
<td class="font-weight-bold" style="width: 300px;">
|
||
<v-icon left color="primary">mdi-sigma</v-icon>
|
||
總計
|
||
</td>
|
||
<td class="text-right font-weight-bold" style="width: 120px;">{{ tab2GrandTotalQty.toLocaleString() }}</td>
|
||
<td class="text-right font-weight-bold" style="width: 150px;">{{ formatCurrency(tab2GrandTotalAmount) }}</td>
|
||
<td style="width: 48px;"></td>
|
||
</tr>
|
||
</tfoot>
|
||
</template>
|
||
|
||
<!-- 無資料提示 -->
|
||
<template v-slot:no-data>
|
||
<v-alert type="info" class="ma-4">
|
||
請先選擇法會以載入分析資料
|
||
</v-alert>
|
||
</template>
|
||
</v-data-table>
|
||
</v-card-text>
|
||
</v-card>
|
||
</v-tab-item>
|
||
|
||
<!-- Tab3: 信眾報名分析 -->
|
||
<v-tab-item>
|
||
<v-card>
|
||
<v-card-title class="success white--text mb-4">
|
||
<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">
|
||
<v-text-field
|
||
v-model="tab3.search"
|
||
label="搜尋信眾(編號或姓名)"
|
||
prepend-icon="mdi-magnify"
|
||
clearable
|
||
outlined
|
||
dense
|
||
></v-text-field>
|
||
</v-col>
|
||
</v-row>
|
||
|
||
<!-- 統計資訊列 -->
|
||
<div v-if="hasData" class="info-bar mb-4">
|
||
<v-row>
|
||
<v-col cols="6" md="3">
|
||
<div><strong>總人數</strong></div>
|
||
<div class="text-h6 success--text">{{ 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>
|
||
</v-tab-item>
|
||
<!-- Tab4: 功德主查詢分析 -->
|
||
<v-tab-item>
|
||
<v-card>
|
||
<v-card-title class="warning white--text mb-4">
|
||
<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="mt-2 mb-4 pt-4">
|
||
<!-- 姓名搜尋 -->
|
||
<v-col cols="12" md="3">
|
||
<v-text-field
|
||
v-model="tab4.search"
|
||
label="搜尋姓名"
|
||
prepend-icon="mdi-magnify"
|
||
hide-details
|
||
clearable
|
||
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"
|
||
hide-details
|
||
dense
|
||
></v-select>
|
||
</v-col>
|
||
|
||
<!-- 值類型切換 -->
|
||
<v-col cols="12" md="3" class="d-flex align-center">
|
||
<v-radio-group v-model="tab4.valueType" row dense hide-details class="mt-0">
|
||
<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" class="d-flex align-center">
|
||
<v-checkbox
|
||
v-model="tab4.showMasterOnly"
|
||
label="只顯示功德主"
|
||
dense
|
||
hide-details
|
||
class="mt-0"
|
||
></v-checkbox>
|
||
</v-col>
|
||
</v-row>
|
||
|
||
<!-- 統計資訊 -->
|
||
<div v-if="hasData" class="info-bar mb-4">
|
||
<v-row>
|
||
<v-col cols="6">
|
||
<strong>信眾數:</strong> {{ tab4PivotData.length }} 人
|
||
</v-col>
|
||
<v-col cols="6">
|
||
<strong>功德項目:</strong> {{ tab4MeritItems.length }} 項
|
||
</v-col>
|
||
</v-row>
|
||
</div>
|
||
|
||
<!-- 樞紐分析表格 -->
|
||
<div class="table-wrapper">
|
||
<v-data-table
|
||
:headers="tab4Headers"
|
||
:items="tab4PivotData"
|
||
:items-per-page="15"
|
||
class="elevation-1 pivot-table-horizontal"
|
||
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="meritItem in tab4MeritItems" v-slot:[`item.${meritItem}`]="{ item }">
|
||
<span :class="{'grey--text': item[meritItem] === 0}">
|
||
{{ tab4.valueType === 'amount' ? formatCurrency(item[meritItem]) : (item[meritItem] || '-') }}
|
||
</span>
|
||
</template>
|
||
|
||
<!-- 無資料提示 -->
|
||
<template v-slot:no-data>
|
||
<v-alert type="info" class="ma-4">
|
||
{{ tab4.search ? '查無符合條件的信眾' : '請先選擇法會以載入分析資料' }}
|
||
</v-alert>
|
||
</template>
|
||
</v-data-table>
|
||
</div>
|
||
</v-card-text>
|
||
</v-card>
|
||
</v-tab-item>
|
||
|
||
<!-- Tab5: 報名明細資料 -->
|
||
<v-tab-item>
|
||
<v-card>
|
||
<v-card-title class="purple white--text">
|
||
<v-icon left color="white">mdi-table-large</v-icon>
|
||
報名明細資料
|
||
<v-spacer></v-spacer>
|
||
<v-chip v-if="tab1.selectedActivity" color="white" text-color="purple">
|
||
{{ tab1.selectedActivity.活動名稱 }}
|
||
</v-chip>
|
||
</v-card-title>
|
||
<v-card-text>
|
||
<!-- 資訊列 -->
|
||
<div v-if="tab1.selectedActivity" class="info-bar gradient">
|
||
<v-row>
|
||
<v-col cols="12" md="6">
|
||
<div>
|
||
<v-icon color="white" left>mdi-calendar-range</v-icon>
|
||
<strong>{{ tab1.selectedActivity.活動名稱 }}</strong>
|
||
</div>
|
||
<div class="mt-1">
|
||
<small>
|
||
{{ formatDate(tab1.selectedActivity.開始日期) }} ~
|
||
{{ formatDate(tab1.selectedActivity.結束日期) }}
|
||
</small>
|
||
</div>
|
||
</v-col>
|
||
<v-col cols="12" md="6" class="text-right">
|
||
<div>
|
||
總筆數: <strong>{{ filteredData.length }}</strong> 筆
|
||
</div>
|
||
<div class="mt-1">
|
||
總金額: <strong>{{ formatCurrency(totalAmount) }}</strong>
|
||
</div>
|
||
</v-col>
|
||
</v-row>
|
||
</div>
|
||
|
||
<!-- 搜尋和匯出 -->
|
||
<v-row class="my-3">
|
||
<v-col cols="12" md="8">
|
||
<v-text-field
|
||
v-model="tab5.search"
|
||
label="搜尋(姓名、功德類型、功德名稱)"
|
||
prepend-icon="mdi-magnify"
|
||
clearable
|
||
dense
|
||
outlined
|
||
hide-details
|
||
></v-text-field>
|
||
</v-col>
|
||
<v-col cols="12" md="4">
|
||
<v-btn
|
||
color="success"
|
||
large
|
||
block
|
||
@click="exportToExcel"
|
||
:disabled="filteredData.length === 0"
|
||
>
|
||
<v-icon left>mdi-file-excel</v-icon>
|
||
匯出 Excel
|
||
</v-btn>
|
||
</v-col>
|
||
</v-row>
|
||
|
||
<!-- 報名明細表格 -->
|
||
<v-data-table
|
||
:headers="tab5Headers"
|
||
:items="filteredData"
|
||
:items-per-page="50"
|
||
class="elevation-1"
|
||
:footer-props="{
|
||
'items-per-page-options': [20, 50, 100, 200]
|
||
}"
|
||
>
|
||
<template v-slot:item.報名日期="{ item }">
|
||
{{ formatDate(item.報名日期) }}
|
||
</template>
|
||
<template v-slot:item.功德主="{ item }">
|
||
<v-chip :color="item.功德主 === '是' ? 'success' : 'default'" x-small>
|
||
{{ item.功德主 }}
|
||
</v-chip>
|
||
</template>
|
||
<template v-slot:item.金額="{ item }">
|
||
{{ formatCurrency(item.金額) }}
|
||
</template>
|
||
<template v-slot:item.已收="{ item }">
|
||
{{ formatCurrency(item.已收) }}
|
||
</template>
|
||
<template v-slot:item.未收="{ item }">
|
||
<span :class="item.未收 > 0 ? 'error--text font-weight-bold' : ''">
|
||
{{ formatCurrency(item.未收) }}
|
||
</span>
|
||
</template>
|
||
<template v-slot:no-data>
|
||
<v-alert type="info" class="ma-4">
|
||
無報名明細資料
|
||
</v-alert>
|
||
</template>
|
||
</v-data-table>
|
||
</v-card-text>
|
||
</v-card>
|
||
</v-tab-item>
|
||
</v-tabs-items>
|
||
</v-container>
|
||
</v-app>
|
||
</div>
|
||
</asp:Content>
|
||
|
||
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" runat="Server">
|
||
<!-- Master Page 已載入 Vue/Vuetify/Axios/Sweetalert2,不需重複載入 -->
|
||
<script>
|
||
new Vue({
|
||
el: '#pivot01-app',
|
||
vuetify: new Vuetify(),
|
||
data() {
|
||
return {
|
||
// 當前頁籤
|
||
activeTab: 0,
|
||
|
||
// 全域載入狀態
|
||
loading: false,
|
||
|
||
// Tab1: 法會選擇
|
||
tab1: {
|
||
year: new Date().getFullYear(),
|
||
yearOptions: [],
|
||
loading: false,
|
||
activities: [],
|
||
selectedActivity: null
|
||
},
|
||
|
||
// Tab1 表頭
|
||
tab1Headers: [
|
||
{ text: '法會名稱', value: '活動名稱', width: 250 },
|
||
{ text: '活動類型', value: '活動詳細分類', width: 150 },
|
||
{ text: '開始日期', value: '開始日期', width: 120 },
|
||
{ text: '結束日期', value: '結束日期', width: 120 },
|
||
{ text: '報名截止', value: '報名截止日', width: 120 },
|
||
{ text: '報名數', value: '功德數量', width: 100, align: 'center' },
|
||
{ text: '總金額', value: '合計', width: 120, align: 'end' },
|
||
{ text: '操作', value: 'actions', width: 120, sortable: false, align: 'center' }
|
||
],
|
||
|
||
// 全域資料(所有 Tab 共用)
|
||
rawData: [],
|
||
|
||
// Tab2: 牌位分析
|
||
tab2: {
|
||
collapsed: [], // 收合的功德類型 ID 陣列
|
||
headers: [
|
||
{ text: '功德項目', value: 'name', width: 300 },
|
||
{ text: '數量總計', value: 'totalQty', width: 120, align: 'end' },
|
||
{ text: '金額總計', value: 'totalAmount', width: 150, align: 'end' }
|
||
]
|
||
},
|
||
|
||
// 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-merit' },
|
||
{ text: '功德主金額', value: '功德主金額', width: 150, align: 'end', class: 'field-merit' },
|
||
{ text: '功德數量', value: '功德數量', width: 120, align: 'end', class: 'field-calculated' },
|
||
{ text: '功德金額', value: '功德金額', width: 150, align: 'end', class: 'field-calculated' }
|
||
]
|
||
},
|
||
|
||
// Tab4: 功德主查詢分析
|
||
tab4: {
|
||
search: '',
|
||
meritType: '全部',
|
||
valueType: 'amount', // 'amount' 或 'quantity'
|
||
showMasterOnly: false
|
||
},
|
||
|
||
// Tab5: 報名明細
|
||
tab5: {
|
||
search: '',
|
||
headers: [
|
||
{ text: '信眾姓名', value: '信眾姓名', width: 100, class: 'field-follower' },
|
||
{ text: '報名日期', value: '報名日期', width: 120, class: 'field-follower' },
|
||
{ text: '功德類型', value: '功德類型', width: 120, class: 'field-merit' },
|
||
{ text: '功德名稱', value: '功德名稱', width: 180, class: 'field-merit' },
|
||
{ text: '功德主', value: '功德主', width: 80, align: 'center', class: 'field-merit' },
|
||
{ text: '數量', value: '數量', width: 80, align: 'end', class: 'field-calculated' },
|
||
{ text: '金額', value: '金額', width: 100, align: 'end', class: 'field-calculated' },
|
||
{ text: '已收', value: '已收', width: 100, align: 'end', class: 'field-calculated' },
|
||
{ text: '未收', value: '未收', width: 100, align: 'end', class: 'field-calculated' }
|
||
]
|
||
}
|
||
};
|
||
},
|
||
computed: {
|
||
// Tab5 表頭
|
||
tab5Headers() {
|
||
return this.tab5.headers;
|
||
},
|
||
|
||
// 是否有資料(Tab2-5 可用)
|
||
hasData() {
|
||
return this.rawData && this.rawData.length > 0;
|
||
},
|
||
|
||
// Tab2: 樞紐分析表資料
|
||
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: {},
|
||
id: type // 用於展開/收合的唯一標識
|
||
};
|
||
}
|
||
|
||
// Step 2: 按功德名稱分組
|
||
if (!typeGroups[type].children[name]) {
|
||
typeGroups[type].children[name] = {
|
||
name: name,
|
||
isGroup: false,
|
||
totalQty: 0,
|
||
totalAmount: 0,
|
||
parentType: type,
|
||
id: type + '_' + name
|
||
};
|
||
}
|
||
|
||
// 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);
|
||
// 只有當群組未收合時才顯示子項目
|
||
if (!this.tab2.collapsed.includes(group.id)) {
|
||
Object.values(group.children).forEach(child => {
|
||
result.push(child);
|
||
});
|
||
}
|
||
});
|
||
|
||
return result;
|
||
},
|
||
|
||
// Tab2: 總計數量
|
||
tab2GrandTotalQty() {
|
||
return this.rawData.reduce((sum, row) => {
|
||
return sum + (parseInt(row.數量) || 0);
|
||
}, 0);
|
||
},
|
||
|
||
// Tab2: 總計金額
|
||
tab2GrandTotalAmount() {
|
||
return this.rawData.reduce((sum, row) => {
|
||
return sum + (parseFloat(row.金額) || 0);
|
||
}, 0);
|
||
},
|
||
|
||
// Tab3: 樞紐分析表資料
|
||
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.信眾姓名, 'zh-TW');
|
||
});
|
||
},
|
||
|
||
// Tab3: 搜尋過濾
|
||
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);
|
||
});
|
||
},
|
||
|
||
// Tab3: 統計摘要
|
||
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)
|
||
};
|
||
},
|
||
|
||
// Tab4: 功德類型選項
|
||
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()];
|
||
},
|
||
|
||
// Tab4: 過濾後的原始資料
|
||
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.信眾姓名 && row.信眾姓名.toLowerCase().includes(keyword)
|
||
);
|
||
}
|
||
|
||
return data;
|
||
},
|
||
|
||
// Tab4: 取得功德項目清單(橫向欄位)
|
||
tab4MeritItems() {
|
||
const items = new Set(
|
||
this.tab4FilteredRawData.map(row => row.功德名稱).filter(Boolean)
|
||
);
|
||
return Array.from(items).sort();
|
||
},
|
||
|
||
// Tab4: 動態表頭
|
||
tab4Headers() {
|
||
const headers = [
|
||
{ text: '信眾姓名', value: '信眾姓名', width: 150, fixed: true, class: 'field-follower sticky-col' }
|
||
];
|
||
|
||
// 總計欄
|
||
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;
|
||
},
|
||
|
||
// Tab4: 樞紐分析資料
|
||
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.總計);
|
||
},
|
||
|
||
// Tab5: 過濾後的資料
|
||
filteredData() {
|
||
if (!this.tab5.search) return this.rawData;
|
||
|
||
const keyword = this.tab5.search.toLowerCase();
|
||
return this.rawData.filter(row => {
|
||
return (
|
||
(row.信眾姓名 && row.信眾姓名.toLowerCase().includes(keyword)) ||
|
||
(row.功德類型 && row.功德類型.toLowerCase().includes(keyword)) ||
|
||
(row.功德名稱 && row.功德名稱.toLowerCase().includes(keyword))
|
||
);
|
||
});
|
||
},
|
||
|
||
// Tab5: 總金額
|
||
totalAmount() {
|
||
return this.filteredData.reduce((sum, row) => {
|
||
return sum + (row.金額 * row.數量);
|
||
}, 0);
|
||
}
|
||
},
|
||
methods: {
|
||
/**
|
||
* 初始化年份選項
|
||
*/
|
||
initYearOptions() {
|
||
const currentYear = new Date().getFullYear();
|
||
this.tab1.yearOptions = [];
|
||
for (let i = -5; i <= 5; i++) {
|
||
this.tab1.yearOptions.push(currentYear + i);
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 切換群組的收合/展開狀態
|
||
*/
|
||
toggleGroup(groupId) {
|
||
const index = this.tab2.collapsed.indexOf(groupId);
|
||
if (index > -1) {
|
||
// 已收合,展開它
|
||
this.tab2.collapsed.splice(index, 1);
|
||
} else {
|
||
// 未收合,收合它
|
||
this.tab2.collapsed.push(groupId);
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 查詢法會清單
|
||
*/
|
||
async loadActivities() {
|
||
this.tab1.loading = true;
|
||
this.tab1.activities = [];
|
||
|
||
try {
|
||
const response = await axios.get(HTTP_HOST + 'api/pivot01/activity_stats', {
|
||
params: { year: this.tab1.year }
|
||
});
|
||
|
||
if (response.data.success) {
|
||
this.tab1.activities = response.data.data;
|
||
|
||
if (this.tab1.activities.length === 0) {
|
||
Swal.fire({
|
||
icon: 'info',
|
||
title: '查無資料',
|
||
text: `${this.tab1.year} 年度查無法會資料`,
|
||
timer: 2000
|
||
});
|
||
}
|
||
} else {
|
||
throw new Error(response.data.message || '查詢失敗');
|
||
}
|
||
} catch (error) {
|
||
console.error('載入法會清單失敗:', error);
|
||
Swal.fire({
|
||
icon: 'error',
|
||
title: '查詢失敗',
|
||
text: error.response?.data?.Message || error.message || '查詢失敗,請稍後再試'
|
||
});
|
||
} finally {
|
||
this.tab1.loading = false;
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 選擇法會並載入完整資料
|
||
*/
|
||
async selectActivity(activity) {
|
||
this.tab1.selectedActivity = activity;
|
||
this.loading = true;
|
||
|
||
try {
|
||
const response = await axios.get(HTTP_HOST + 'api/pivot01/registration_details', {
|
||
params: {
|
||
activityNum: activity.num
|
||
}
|
||
});
|
||
|
||
if (response.data.success) {
|
||
this.rawData = response.data.data;
|
||
|
||
// 自動切換到 Tab5(報名明細)
|
||
this.activeTab = 4;
|
||
|
||
Swal.fire({
|
||
icon: 'success',
|
||
title: '載入成功',
|
||
text: `已載入 ${this.rawData.length} 筆報名資料`,
|
||
timer: 2000,
|
||
showConfirmButton: false
|
||
});
|
||
} else {
|
||
throw new Error(response.data.message || '載入失敗');
|
||
}
|
||
} catch (error) {
|
||
console.error('載入報名資料失敗:', error);
|
||
Swal.fire({
|
||
icon: 'error',
|
||
title: '載入失敗',
|
||
text: error.response?.data?.Message || error.message || '載入失敗,請稍後再試'
|
||
});
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 匯出 Excel(CSV 格式)
|
||
*/
|
||
exportToExcel() {
|
||
if (!this.filteredData || this.filteredData.length === 0) {
|
||
Swal.fire('提示', '沒有資料可匯出', 'warning');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 顯示載入訊息
|
||
Swal.fire({
|
||
title: '匯出中...',
|
||
text: '請稍候',
|
||
icon: 'info',
|
||
showConfirmButton: false,
|
||
allowOutsideClick: false,
|
||
didOpen: () => {
|
||
Swal.showLoading();
|
||
}
|
||
});
|
||
|
||
// 準備匯出資料
|
||
const exportData = this.filteredData.map(item => ({
|
||
'法會名稱': item.法會名稱,
|
||
'信眾編號': item.信眾編號,
|
||
'信眾姓名': item.信眾姓名,
|
||
'報名日期': this.formatDateTime(item.報名日期),
|
||
'功德類型': item.功德類型,
|
||
'功德名稱': item.功德名稱,
|
||
'功德主': item.功德主,
|
||
'數量': item.數量,
|
||
'金額': item.金額,
|
||
'已收': item.已收,
|
||
'未收': item.未收
|
||
}));
|
||
|
||
// 轉換為 CSV
|
||
const headers = Object.keys(exportData[0]);
|
||
const csvRows = [
|
||
headers.join(','),
|
||
...exportData.map(row => {
|
||
return headers.map(h => {
|
||
let value = row[h];
|
||
|
||
// 信眾編號強制為文字(使用 ="值" 格式)
|
||
if (h === '信眾編號') {
|
||
return `"=""${value}"""`;
|
||
}
|
||
|
||
// 一般文字欄位處理
|
||
if (typeof value === 'string') {
|
||
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
|
||
return `"${value.replace(/"/g, '""')}"`;
|
||
}
|
||
return value;
|
||
}
|
||
|
||
return value !== null && value !== undefined ? value : '';
|
||
}).join(',');
|
||
})
|
||
];
|
||
const csv = csvRows.join('\n');
|
||
|
||
// 下載檔案
|
||
const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' });
|
||
const link = document.createElement('a');
|
||
const url = URL.createObjectURL(blob);
|
||
const fileName = `報名明細_${this.tab1.selectedActivity.活動名稱}_${new Date().getTime()}.csv`;
|
||
|
||
link.setAttribute('href', url);
|
||
link.setAttribute('download', fileName);
|
||
link.style.visibility = 'hidden';
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
|
||
// 直接關閉載入對話框,不顯示完成提示
|
||
Swal.close();
|
||
} catch (error) {
|
||
console.error('匯出失敗:', error);
|
||
Swal.fire('錯誤', `匯出失敗: ${error.message}`, 'error');
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 格式化金額
|
||
*/
|
||
formatCurrency(amount) {
|
||
if (amount === null || amount === undefined) return '-';
|
||
return new Intl.NumberFormat('zh-TW', {
|
||
style: 'currency',
|
||
currency: 'TWD',
|
||
minimumFractionDigits: 0
|
||
}).format(amount);
|
||
},
|
||
|
||
/**
|
||
* 格式化日期
|
||
*/
|
||
formatDate(date) {
|
||
if (!date) return '-';
|
||
const d = new Date(date);
|
||
if (isNaN(d.getTime())) return '-';
|
||
return d.toLocaleDateString('zh-TW', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit'
|
||
});
|
||
},
|
||
|
||
/**
|
||
* 格式化日期時間
|
||
*/
|
||
formatDateTime(datetime) {
|
||
if (!datetime) return '-';
|
||
const d = new Date(datetime);
|
||
if (isNaN(d.getTime())) return '-';
|
||
return d.toLocaleString('zh-TW', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
}
|
||
},
|
||
mounted() {
|
||
console.log('Pivot-01 App 已載入');
|
||
console.log('toggleGroup method exists:', typeof this.toggleGroup === 'function');
|
||
|
||
// 初始化年份選項
|
||
this.initYearOptions();
|
||
|
||
// 頁面載入時自動查詢當年度法會
|
||
this.loadActivities();
|
||
}
|
||
});
|
||
</script>
|
||
</asp:Content>
|
||
|