export statistics & analysis data in desktop version

This commit is contained in:
MaysWind
2025-06-15 21:50:31 +08:00
parent 8edc3640f5
commit 4336d1ed1a
14 changed files with 390 additions and 11 deletions
+32
View File
@@ -25,6 +25,9 @@ import {
isArray, isArray,
isNumber isNumber
} from '@/lib/common.ts'; } from '@/lib/common.ts';
import {
formatAmount
} from '@/lib/numeral.ts';
import { import {
getYearMonthFirstUnixTime, getYearMonthFirstUnixTime,
getYearMonthLastUnixTime, getYearMonthLastUnixTime,
@@ -420,9 +423,38 @@ function clickItem(e: ECElementEvent): void {
}); });
} }
function exportData(): { headers: string[], data: string[][] } {
const headers: string[] = [];
const data: string[][] = [];
headers.push(tt('Date'));
for (let i = 0; i < allSeries.value.length; i++) {
const id = allSeries.value[i].id;
const name = itemsMap.value[id] && props.nameField && itemsMap.value[id][props.nameField] ? getItemName(itemsMap.value[id][props.nameField] as string) : id;
headers.push(name);
}
for (let i = 0; i < allDisplayDateRanges.value.length; i++) {
const row: string[] = [];
row.push(allDisplayDateRanges.value[i]);
row.push(...allSeries.value.map(item => formatAmount(item.data[i], {})));
data.push(row);
}
return {
headers: headers,
data: data
};
}
function onLegendSelectChanged(e: { selected: Record<string, boolean> }): void { function onLegendSelectChanged(e: { selected: Record<string, boolean> }): void {
selectedLegends.value = e.selected; selectedLegends.value = e.selected;
} }
defineExpose({
exportData
})
</script> </script>
<style scoped> <style scoped>
+9 -1
View File
@@ -118,7 +118,9 @@
}, },
"dataExport": { "dataExport": {
"defaultExportFilename": "ezBookkeeping_export_data", "defaultExportFilename": "ezBookkeeping_export_data",
"exportFilename": "ezBookkeeping_{nickname}_export_data" "exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
}, },
"datetime": { "datetime": {
"AM": { "AM": {
@@ -1429,7 +1431,11 @@
"Select All Invalid Items": "Alle ungültigen Elemente auswählen", "Select All Invalid Items": "Alle ungültigen Elemente auswählen",
"Back": "Zurück", "Back": "Zurück",
"Load More": "Mehr laden", "Load More": "Mehr laden",
"Export Results": "Export Results",
"No data": "Keine Daten", "No data": "Keine Daten",
"Data copied": "Data copied",
"Table": "Table",
"Raw Data": "Raw Data",
"Zoom in": "Vergrößern", "Zoom in": "Vergrößern",
"Zoom out": "Verkleinern", "Zoom out": "Verkleinern",
"Drag to Reorder": "Zum Neuanordnen ziehen", "Drag to Reorder": "Zum Neuanordnen ziehen",
@@ -1814,6 +1820,7 @@
"Maximum Amount": "Höchstbetrag", "Maximum Amount": "Höchstbetrag",
"Display Order": "Anzeigereihenfolge", "Display Order": "Anzeigereihenfolge",
"Name": "Name", "Name": "Name",
"Proportion (%)": "Proportion (%)",
"Sort by Amount": "Nach Betrag sortieren", "Sort by Amount": "Nach Betrag sortieren",
"Sort by Display Order": "Nach Anzeigereihenfolge sortieren", "Sort by Display Order": "Nach Anzeigereihenfolge sortieren",
"Sort by Name": "Nach Name sortieren", "Sort by Name": "Nach Name sortieren",
@@ -1915,6 +1922,7 @@
"Data Management": "Datenverwaltung", "Data Management": "Datenverwaltung",
"Unable to retrieve user statistics data": "Benutzerstatistikdaten können nicht abgerufen werden", "Unable to retrieve user statistics data": "Benutzerstatistikdaten können nicht abgerufen werden",
"Export Data": "Daten exportieren", "Export Data": "Daten exportieren",
"Field Separator": "Field Separator",
"CSV (Comma-separated values) File": "CSV (Kommagetrennte Werte) Datei", "CSV (Comma-separated values) File": "CSV (Kommagetrennte Werte) Datei",
"TSV (Tab-separated values) File": "TSV (Tabulatorgetrennte Werte) Datei", "TSV (Tab-separated values) File": "TSV (Tabulatorgetrennte Werte) Datei",
"Clear User Data": "Benutzerdaten löschen", "Clear User Data": "Benutzerdaten löschen",
+9 -1
View File
@@ -118,7 +118,9 @@
}, },
"dataExport": { "dataExport": {
"defaultExportFilename": "ezBookkeeping_export_data", "defaultExportFilename": "ezBookkeeping_export_data",
"exportFilename": "ezBookkeeping_{nickname}_export_data" "exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
}, },
"datetime": { "datetime": {
"AM": { "AM": {
@@ -1429,7 +1431,11 @@
"Select All Invalid Items": "Select All Invalid Items", "Select All Invalid Items": "Select All Invalid Items",
"Back": "Back", "Back": "Back",
"Load More": "Load More", "Load More": "Load More",
"Export Results": "Export Results",
"No data": "No data", "No data": "No data",
"Data copied": "Data copied",
"Table": "Table",
"Raw Data": "Raw Data",
"Zoom in": "Zoom in", "Zoom in": "Zoom in",
"Zoom out": "Zoom out", "Zoom out": "Zoom out",
"Drag to Reorder": "Drag to Reorder", "Drag to Reorder": "Drag to Reorder",
@@ -1814,6 +1820,7 @@
"Maximum Amount": "Maximum Amount", "Maximum Amount": "Maximum Amount",
"Display Order": "Display Order", "Display Order": "Display Order",
"Name": "Name", "Name": "Name",
"Proportion (%)": "Proportion (%)",
"Sort by Amount": "Sort by Amount", "Sort by Amount": "Sort by Amount",
"Sort by Display Order": "Sort by Display Order", "Sort by Display Order": "Sort by Display Order",
"Sort by Name": "Sort by Name", "Sort by Name": "Sort by Name",
@@ -1915,6 +1922,7 @@
"Data Management": "Data Management", "Data Management": "Data Management",
"Unable to retrieve user statistics data": "Unable to retrieve user statistics data", "Unable to retrieve user statistics data": "Unable to retrieve user statistics data",
"Export Data": "Export Data", "Export Data": "Export Data",
"Field Separator": "Field Separator",
"CSV (Comma-separated values) File": "CSV (Comma-separated values) File", "CSV (Comma-separated values) File": "CSV (Comma-separated values) File",
"TSV (Tab-separated values) File": "TSV (Tab-separated values) File", "TSV (Tab-separated values) File": "TSV (Tab-separated values) File",
"Clear User Data": "Clear User Data", "Clear User Data": "Clear User Data",
+9 -1
View File
@@ -118,7 +118,9 @@
}, },
"dataExport": { "dataExport": {
"defaultExportFilename": "ezBookkeeping_export_data", "defaultExportFilename": "ezBookkeeping_export_data",
"exportFilename": "ezBookkeeping_{nickname}_export_data" "exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
}, },
"datetime": { "datetime": {
"AM": { "AM": {
@@ -1429,7 +1431,11 @@
"Select All Invalid Items": "Seleccionar todos los artículos no válidos", "Select All Invalid Items": "Seleccionar todos los artículos no válidos",
"Back": "Atrás", "Back": "Atrás",
"Load More": "Cargar más", "Load More": "Cargar más",
"Export Results": "Export Results",
"No data": "Sin datos", "No data": "Sin datos",
"Data copied": "Data copied",
"Table": "Table",
"Raw Data": "Raw Data",
"Zoom in": "Acercar", "Zoom in": "Acercar",
"Zoom out": "Alejar", "Zoom out": "Alejar",
"Drag to Reorder": "Arrastrar para reordenar", "Drag to Reorder": "Arrastrar para reordenar",
@@ -1814,6 +1820,7 @@
"Maximum Amount": "Importe Máximo", "Maximum Amount": "Importe Máximo",
"Display Order": "Orden de visualización", "Display Order": "Orden de visualización",
"Name": "Nombre", "Name": "Nombre",
"Proportion (%)": "Proportion (%)",
"Sort by Amount": "Ordenar por Importe", "Sort by Amount": "Ordenar por Importe",
"Sort by Display Order": "Ordenar por orden de visualización", "Sort by Display Order": "Ordenar por orden de visualización",
"Sort by Name": "Ordenar por Nombre", "Sort by Name": "Ordenar por Nombre",
@@ -1915,6 +1922,7 @@
"Data Management": "Gestión de datos", "Data Management": "Gestión de datos",
"Unable to retrieve user statistics data": "No se pueden recuperar datos de estadísticas de usuario", "Unable to retrieve user statistics data": "No se pueden recuperar datos de estadísticas de usuario",
"Export Data": "Exportar datos", "Export Data": "Exportar datos",
"Field Separator": "Field Separator",
"CSV (Comma-separated values) File": "Archivo CSV (valores separados por comas)", "CSV (Comma-separated values) File": "Archivo CSV (valores separados por comas)",
"TSV (Tab-separated values) File": "Archivo TSV (valores separados por tabulaciones)", "TSV (Tab-separated values) File": "Archivo TSV (valores separados por tabulaciones)",
"Clear User Data": "Borrar datos de usuario", "Clear User Data": "Borrar datos de usuario",
+9 -1
View File
@@ -118,7 +118,9 @@
}, },
"dataExport": { "dataExport": {
"defaultExportFilename": "ezBookkeeping_export_data", "defaultExportFilename": "ezBookkeeping_export_data",
"exportFilename": "ezBookkeeping_{nickname}_export_data" "exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
}, },
"datetime": { "datetime": {
"AM": { "AM": {
@@ -1429,7 +1431,11 @@
"Select All Invalid Items": "Seleziona tutti gli elementi non validi", "Select All Invalid Items": "Seleziona tutti gli elementi non validi",
"Back": "Indietro", "Back": "Indietro",
"Load More": "Carica altro", "Load More": "Carica altro",
"Export Results": "Export Results",
"No data": "Nessun dato", "No data": "Nessun dato",
"Data copied": "Data copied",
"Table": "Table",
"Raw Data": "Raw Data",
"Zoom in": "Ingrandisci", "Zoom in": "Ingrandisci",
"Zoom out": "Riduci", "Zoom out": "Riduci",
"Drag to Reorder": "Trascina per riordinare", "Drag to Reorder": "Trascina per riordinare",
@@ -1814,6 +1820,7 @@
"Maximum Amount": "Importo massimo", "Maximum Amount": "Importo massimo",
"Display Order": "Ordine di visualizzazione", "Display Order": "Ordine di visualizzazione",
"Name": "Nome", "Name": "Nome",
"Proportion (%)": "Proportion (%)",
"Sort by Amount": "Ordina per importo", "Sort by Amount": "Ordina per importo",
"Sort by Display Order": "Ordina per ordine di visualizzazione", "Sort by Display Order": "Ordina per ordine di visualizzazione",
"Sort by Name": "Ordina per nome", "Sort by Name": "Ordina per nome",
@@ -1915,6 +1922,7 @@
"Data Management": "Gestione dati", "Data Management": "Gestione dati",
"Unable to retrieve user statistics data": "Impossibile recuperare i dati statistici dell'utente", "Unable to retrieve user statistics data": "Impossibile recuperare i dati statistici dell'utente",
"Export Data": "Esporta dati", "Export Data": "Esporta dati",
"Field Separator": "Field Separator",
"CSV (Comma-separated values) File": "File CSV (valori separati da virgola)", "CSV (Comma-separated values) File": "File CSV (valori separati da virgola)",
"TSV (Tab-separated values) File": "File TSV (valori separati da tabulazione)", "TSV (Tab-separated values) File": "File TSV (valori separati da tabulazione)",
"Clear User Data": "Cancella dati utente", "Clear User Data": "Cancella dati utente",
+9 -1
View File
@@ -118,7 +118,9 @@
}, },
"dataExport": { "dataExport": {
"defaultExportFilename": "ezBookkeeping_エクスポートデータ", "defaultExportFilename": "ezBookkeeping_エクスポートデータ",
"exportFilename": "ezBookkeeping_{nickname}_エクスポートデータ" "exportFilename": "ezBookkeeping_{nickname}_エクスポートデータ",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
}, },
"datetime": { "datetime": {
"AM": { "AM": {
@@ -1429,7 +1431,11 @@
"Select All Invalid Items": "すべての無効なアイテムを選択します", "Select All Invalid Items": "すべての無効なアイテムを選択します",
"Back": "戻る", "Back": "戻る",
"Load More": "さらに読み込む", "Load More": "さらに読み込む",
"Export Results": "Export Results",
"No data": "データはありません", "No data": "データはありません",
"Data copied": "Data copied",
"Table": "Table",
"Raw Data": "Raw Data",
"Zoom in": "拡大", "Zoom in": "拡大",
"Zoom out": "縮小", "Zoom out": "縮小",
"Drag to Reorder": "ドラッグして並べ替え", "Drag to Reorder": "ドラッグして並べ替え",
@@ -1814,6 +1820,7 @@
"Maximum Amount": "最大金額", "Maximum Amount": "最大金額",
"Display Order": "表示順", "Display Order": "表示順",
"Name": "名前", "Name": "名前",
"Proportion (%)": "Proportion (%)",
"Sort by Amount": "金額で並べ替え", "Sort by Amount": "金額で並べ替え",
"Sort by Display Order": "表示で並べ替え", "Sort by Display Order": "表示で並べ替え",
"Sort by Name": "名前で並べ替え", "Sort by Name": "名前で並べ替え",
@@ -1915,6 +1922,7 @@
"Data Management": "データ管理", "Data Management": "データ管理",
"Unable to retrieve user statistics data": "ユーザー統計データを取得できません", "Unable to retrieve user statistics data": "ユーザー統計データを取得できません",
"Export Data": "データのエクスポート", "Export Data": "データのエクスポート",
"Field Separator": "Field Separator",
"CSV (Comma-separated values) File": "CSV(コンマ区切り)ファイル", "CSV (Comma-separated values) File": "CSV(コンマ区切り)ファイル",
"TSV (Tab-separated values) File": "TSV(タブ区切り)ファイル", "TSV (Tab-separated values) File": "TSV(タブ区切り)ファイル",
"Clear User Data": "ユーザーデータをクリア", "Clear User Data": "ユーザーデータをクリア",
+9 -1
View File
@@ -118,7 +118,9 @@
}, },
"dataExport": { "dataExport": {
"defaultExportFilename": "ezBookkeeping_export_data", "defaultExportFilename": "ezBookkeeping_export_data",
"exportFilename": "ezBookkeeping_{nickname}_export_data" "exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
}, },
"datetime": { "datetime": {
"AM": { "AM": {
@@ -1429,7 +1431,11 @@
"Select All Invalid Items": "Выбрать все недействительные элементы", "Select All Invalid Items": "Выбрать все недействительные элементы",
"Back": "Назад", "Back": "Назад",
"Load More": "Загрузить еще", "Load More": "Загрузить еще",
"Export Results": "Export Results",
"No data": "Нет данных", "No data": "Нет данных",
"Data copied": "Data copied",
"Table": "Table",
"Raw Data": "Raw Data",
"Zoom in": "Увеличить", "Zoom in": "Увеличить",
"Zoom out": "Уменьшить", "Zoom out": "Уменьшить",
"Drag to Reorder": "Перетащите для изменения порядка", "Drag to Reorder": "Перетащите для изменения порядка",
@@ -1814,6 +1820,7 @@
"Maximum Amount": "Максимальная сумма", "Maximum Amount": "Максимальная сумма",
"Display Order": "Порядок отображения", "Display Order": "Порядок отображения",
"Name": "Имя", "Name": "Имя",
"Proportion (%)": "Proportion (%)",
"Sort by Amount": "Сортировать по сумме", "Sort by Amount": "Сортировать по сумме",
"Sort by Display Order": "Сортировать по порядку отображения", "Sort by Display Order": "Сортировать по порядку отображения",
"Sort by Name": "Сортировать по имени", "Sort by Name": "Сортировать по имени",
@@ -1915,6 +1922,7 @@
"Data Management": "Управление данными", "Data Management": "Управление данными",
"Unable to retrieve user statistics data": "Не удалось получить статистические данные пользователя", "Unable to retrieve user statistics data": "Не удалось получить статистические данные пользователя",
"Export Data": "Экспорт данных", "Export Data": "Экспорт данных",
"Field Separator": "Field Separator",
"CSV (Comma-separated values) File": "Файл CSV (значения, разделенные запятыми)", "CSV (Comma-separated values) File": "Файл CSV (значения, разделенные запятыми)",
"TSV (Tab-separated values) File": "Файл TSV (значения, разделенные табуляцией)", "TSV (Tab-separated values) File": "Файл TSV (значения, разделенные табуляцией)",
"Clear User Data": "Очистить данные пользователя", "Clear User Data": "Очистить данные пользователя",
+9 -1
View File
@@ -118,7 +118,9 @@
}, },
"dataExport": { "dataExport": {
"defaultExportFilename": "ezBookkeeping_export_data", "defaultExportFilename": "ezBookkeeping_export_data",
"exportFilename": "ezBookkeeping_{nickname}_export_data" "exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
}, },
"datetime": { "datetime": {
"AM": { "AM": {
@@ -1429,7 +1431,11 @@
"Select All Invalid Items": "Вибрати всі недійсні елементи", "Select All Invalid Items": "Вибрати всі недійсні елементи",
"Back": "Назад", "Back": "Назад",
"Load More": "Завантажити ще", "Load More": "Завантажити ще",
"Export Results": "Export Results",
"No data": "Немає даних", "No data": "Немає даних",
"Data copied": "Data copied",
"Table": "Table",
"Raw Data": "Raw Data",
"Zoom in": "Збільшити", "Zoom in": "Збільшити",
"Zoom out": "Зменшити", "Zoom out": "Зменшити",
"Drag to Reorder": "Перетягніть для зміни порядку", "Drag to Reorder": "Перетягніть для зміни порядку",
@@ -1814,6 +1820,7 @@
"Maximum Amount": "Максимальна сума", "Maximum Amount": "Максимальна сума",
"Display Order": "Порядок відображення", "Display Order": "Порядок відображення",
"Name": "Ім'я", "Name": "Ім'я",
"Proportion (%)": "Proportion (%)",
"Sort by Amount": "Сортувати за сумою", "Sort by Amount": "Сортувати за сумою",
"Sort by Display Order": "Сортувати за порядком відображення", "Sort by Display Order": "Сортувати за порядком відображення",
"Sort by Name": "Сортувати за назвою", "Sort by Name": "Сортувати за назвою",
@@ -1915,6 +1922,7 @@
"Data Management": "Керування даними", "Data Management": "Керування даними",
"Unable to retrieve user statistics data": "Не вдалося отримати статистику користувача", "Unable to retrieve user statistics data": "Не вдалося отримати статистику користувача",
"Export Data": "Експорт даних", "Export Data": "Експорт даних",
"Field Separator": "Field Separator",
"CSV (Comma-separated values) File": "Файл CSV (значення, розділені комами)", "CSV (Comma-separated values) File": "Файл CSV (значення, розділені комами)",
"TSV (Tab-separated values) File": "Файл TSV (значення, розділені табуляцією)", "TSV (Tab-separated values) File": "Файл TSV (значення, розділені табуляцією)",
"Clear User Data": "Очистити дані користувача", "Clear User Data": "Очистити дані користувача",
+9 -1
View File
@@ -118,7 +118,9 @@
}, },
"dataExport": { "dataExport": {
"defaultExportFilename": "ezBookkeeping_export_data", "defaultExportFilename": "ezBookkeeping_export_data",
"exportFilename": "ezBookkeeping_{nickname}_export_data" "exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
}, },
"datetime": { "datetime": {
"AM": { "AM": {
@@ -1429,7 +1431,11 @@
"Select All Invalid Items": "Chọn tất cả các mục không hợp lệ", "Select All Invalid Items": "Chọn tất cả các mục không hợp lệ",
"Back": "Quay lại", "Back": "Quay lại",
"Load More": "Tải thêm", "Load More": "Tải thêm",
"Export Results": "Export Results",
"No data": "Không có dữ liệu", "No data": "Không có dữ liệu",
"Data copied": "Data copied",
"Table": "Table",
"Raw Data": "Raw Data",
"Zoom in": "Phóng to", "Zoom in": "Phóng to",
"Zoom out": "Thu nhỏ", "Zoom out": "Thu nhỏ",
"Drag to Reorder": "Kéo để sắp xếp lại", "Drag to Reorder": "Kéo để sắp xếp lại",
@@ -1814,6 +1820,7 @@
"Maximum Amount": "Số tiền tối đa", "Maximum Amount": "Số tiền tối đa",
"Display Order": "Thứ tự hiển thị", "Display Order": "Thứ tự hiển thị",
"Name": "Tên", "Name": "Tên",
"Proportion (%)": "Proportion (%)",
"Sort by Amount": "Sắp xếp theo số tiền", "Sort by Amount": "Sắp xếp theo số tiền",
"Sort by Display Order": "Sắp xếp theo thứ tự hiển thị", "Sort by Display Order": "Sắp xếp theo thứ tự hiển thị",
"Sort by Name": "Sắp xếp theo tên", "Sort by Name": "Sắp xếp theo tên",
@@ -1915,6 +1922,7 @@
"Data Management": "Quản lý dữ liệu", "Data Management": "Quản lý dữ liệu",
"Unable to retrieve user statistics data": "Không thể lấy dữ liệu thống kê người dùng", "Unable to retrieve user statistics data": "Không thể lấy dữ liệu thống kê người dùng",
"Export Data": "Xuất dữ liệu", "Export Data": "Xuất dữ liệu",
"Field Separator": "Field Separator",
"CSV (Comma-separated values) File": "Tệp CSV (Giá trị phân cách bằng dấu phẩy)", "CSV (Comma-separated values) File": "Tệp CSV (Giá trị phân cách bằng dấu phẩy)",
"TSV (Tab-separated values) File": "Tệp TSV (Giá trị phân cách bằng tab)", "TSV (Tab-separated values) File": "Tệp TSV (Giá trị phân cách bằng tab)",
"Clear User Data": "Xóa dữ liệu người dùng", "Clear User Data": "Xóa dữ liệu người dùng",
+9 -1
View File
@@ -118,7 +118,9 @@
}, },
"dataExport": { "dataExport": {
"defaultExportFilename": "ezBookkeeping_导出数据", "defaultExportFilename": "ezBookkeeping_导出数据",
"exportFilename": "ezBookkeeping_{nickname}_导出数据" "exportFilename": "ezBookkeeping_{nickname}_导出数据",
"defaultExportStatisticsFileName": "ezBookkeeping_统计数据",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_统计数据"
}, },
"datetime": { "datetime": {
"AM": { "AM": {
@@ -1429,7 +1431,11 @@
"Select All Invalid Items": "选择全部无效项目", "Select All Invalid Items": "选择全部无效项目",
"Back": "返回", "Back": "返回",
"Load More": "加载更多", "Load More": "加载更多",
"Export Results": "导出结果",
"No data": "没有数据", "No data": "没有数据",
"Data copied": "数据已复制",
"Table": "表格",
"Raw Data": "原始数据",
"Zoom in": "放大", "Zoom in": "放大",
"Zoom out": "缩小", "Zoom out": "缩小",
"Drag to Reorder": "拖拽改变顺序", "Drag to Reorder": "拖拽改变顺序",
@@ -1814,6 +1820,7 @@
"Maximum Amount": "最大金额", "Maximum Amount": "最大金额",
"Display Order": "显示顺序", "Display Order": "显示顺序",
"Name": "名称", "Name": "名称",
"Proportion (%)": "比例 (%)",
"Sort by Amount": "按金额排序", "Sort by Amount": "按金额排序",
"Sort by Display Order": "按显示顺序排序", "Sort by Display Order": "按显示顺序排序",
"Sort by Name": "按名称排序", "Sort by Name": "按名称排序",
@@ -1915,6 +1922,7 @@
"Data Management": "数据管理", "Data Management": "数据管理",
"Unable to retrieve user statistics data": "无法获取用户统计数据", "Unable to retrieve user statistics data": "无法获取用户统计数据",
"Export Data": "导出数据", "Export Data": "导出数据",
"Field Separator": "字段分隔符",
"CSV (Comma-separated values) File": "CSV (逗号分隔的值) 文件", "CSV (Comma-separated values) File": "CSV (逗号分隔的值) 文件",
"TSV (Tab-separated values) File": "TSV (制表符分隔的值) 文件", "TSV (Tab-separated values) File": "TSV (制表符分隔的值) 文件",
"Clear User Data": "清除用户数据", "Clear User Data": "清除用户数据",
+9 -1
View File
@@ -118,7 +118,9 @@
}, },
"dataExport": { "dataExport": {
"defaultExportFilename": "ezBookkeeping_匯出資料", "defaultExportFilename": "ezBookkeeping_匯出資料",
"exportFilename": "ezBookkeeping_{nickname}_匯出資料" "exportFilename": "ezBookkeeping_{nickname}_匯出資料",
"defaultExportStatisticsFileName": "ezBookkeeping_統計資料",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_統計資料"
}, },
"datetime": { "datetime": {
"AM": { "AM": {
@@ -1429,7 +1431,11 @@
"Select All Invalid Items": "選擇全部無效項目", "Select All Invalid Items": "選擇全部無效項目",
"Back": "返回", "Back": "返回",
"Load More": "載入更多", "Load More": "載入更多",
"Export Results": "匯出結果",
"No data": "無資料", "No data": "無資料",
"Data copied": "資料已複製",
"Table": "表格",
"Raw Data": "原始資料",
"Zoom in": "放大", "Zoom in": "放大",
"Zoom out": "縮小", "Zoom out": "縮小",
"Drag to Reorder": "拖曳以重新排序", "Drag to Reorder": "拖曳以重新排序",
@@ -1814,6 +1820,7 @@
"Maximum Amount": "最大金額", "Maximum Amount": "最大金額",
"Display Order": "顯示順序", "Display Order": "顯示順序",
"Name": "名稱", "Name": "名稱",
"Proportion (%)": "比例 (%)",
"Sort by Amount": "依金額排序", "Sort by Amount": "依金額排序",
"Sort by Display Order": "依顯示順序排序", "Sort by Display Order": "依顯示順序排序",
"Sort by Name": "依名稱排序", "Sort by Name": "依名稱排序",
@@ -1915,6 +1922,7 @@
"Data Management": "資料管理", "Data Management": "資料管理",
"Unable to retrieve user statistics data": "無法取得使用者統計資料", "Unable to retrieve user statistics data": "無法取得使用者統計資料",
"Export Data": "匯出資料", "Export Data": "匯出資料",
"Field Separator": "欄位分隔符",
"CSV (Comma-separated values) File": "CSV (逗號分隔的值) 檔案", "CSV (Comma-separated values) File": "CSV (逗號分隔的值) 檔案",
"TSV (Tab-separated values) File": "TSV (定位點分隔的值) 檔案", "TSV (Tab-separated values) File": "TSV (定位點分隔的值) 檔案",
"Clear User Data": "清除使用者資料", "Clear User Data": "清除使用者資料",
+8
View File
@@ -133,6 +133,14 @@ input[type=number] {
height: 10px; height: 10px;
} }
.code-container {
background-color: #efefef;
}
.v-theme--dark .code-container {
background-color: #2f2f2f;
}
/** Common class for replacing the default style of vuetify **/ /** Common class for replacing the default style of vuetify **/
.v-input.v-input--readonly input, .v-input.v-input--readonly input,
.v-input.v-input--readonly textarea, .v-input.v-input--readonly textarea,
@@ -128,6 +128,11 @@
:title="tt('Filter Transaction Tags')" :title="tt('Filter Transaction Tags')"
@click="showFilterTagDialog = true"></v-list-item> @click="showFilterTagDialog = true"></v-list-item>
<v-divider class="my-2"/> <v-divider class="my-2"/>
<v-list-item :prepend-icon="mdiExport"
:title="tt('Export Results')"
:disabled="!statisticsDataHasData"
@click="exportResults"></v-list-item>
<v-divider class="my-2"/>
<v-list-item to="/app/settings?tab=statisticsSetting" <v-list-item to="/app/settings?tab=statisticsSetting"
:prepend-icon="mdiFilterCogOutline" :prepend-icon="mdiFilterCogOutline"
:title="tt('Settings')"></v-list-item> :title="tt('Settings')"></v-list-item>
@@ -270,6 +275,7 @@
:enable-click-item="true" :enable-click-item="true"
:default-currency="defaultCurrency" :default-currency="defaultCurrency"
:show-total-amount-in-tooltip="showTotalAmountInTrendsChart" :show-total-amount-in-tooltip="showTotalAmountInTrendsChart"
ref="trendsChart"
id-field="id" id-field="id"
name-field="name" name-field="name"
value-field="totalAmount" value-field="totalAmount"
@@ -317,14 +323,18 @@
@settings:change="setTagFilter" /> @settings:change="setTagFilter" />
</v-dialog> </v-dialog>
<export-dialog ref="exportDialog" />
<snack-bar ref="snackbar" /> <snack-bar ref="snackbar" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import SnackBar from '@/components/desktop/SnackBar.vue'; import SnackBar from '@/components/desktop/SnackBar.vue';
import TrendsChart from '@/components/desktop/TrendsChart.vue';
import AccountFilterSettingsCard from '@/views/desktop/common/cards/AccountFilterSettingsCard.vue'; import AccountFilterSettingsCard from '@/views/desktop/common/cards/AccountFilterSettingsCard.vue';
import CategoryFilterSettingsCard from '@/views/desktop/common/cards/CategoryFilterSettingsCard.vue'; import CategoryFilterSettingsCard from '@/views/desktop/common/cards/CategoryFilterSettingsCard.vue';
import TransactionTagFilterSettingsCard from '@/views/desktop/common/cards/TransactionTagFilterSettingsCard.vue'; import TransactionTagFilterSettingsCard from '@/views/desktop/common/cards/TransactionTagFilterSettingsCard.vue';
import ExportDialog from '@/views/desktop/statistics/transaction/dialogs/ExportDialog.vue';
import { ref, computed, useTemplateRef, watch } from 'vue'; import { ref, computed, useTemplateRef, watch } from 'vue';
import { useRouter, onBeforeRouteUpdate } from 'vue-router'; import { useRouter, onBeforeRouteUpdate } from 'vue-router';
@@ -353,7 +363,10 @@ import {
isString, isString,
isNumber, isNumber,
arrayItemToObjectField arrayItemToObjectField
} from '@/lib/common.ts' } from '@/lib/common.ts';
import {
formatAmount
} from '@/lib/numeral.ts';
import { import {
getYearAndMonthFromUnixTime, getYearAndMonthFromUnixTime,
getYearMonthFirstUnixTime, getYearMonthFirstUnixTime,
@@ -373,10 +386,13 @@ import {
mdiMenu, mdiMenu,
mdiFilterOutline, mdiFilterOutline,
mdiFilterCogOutline, mdiFilterCogOutline,
mdiExport,
mdiDotsVertical mdiDotsVertical
} from '@mdi/js'; } from '@mdi/js';
type SnackBarType = InstanceType<typeof SnackBar>; type SnackBarType = InstanceType<typeof SnackBar>;
type TrendsChartType = InstanceType<typeof TrendsChart>;
type ExportDialogType = InstanceType<typeof ExportDialog>;
interface TransactionStatisticsProps { interface TransactionStatisticsProps {
initAnalysisType?: string, initAnalysisType?: string,
@@ -433,6 +449,8 @@ const transactionCategoriesStore = useTransactionCategoriesStore();
const statisticsStore = useStatisticsStore(); const statisticsStore = useStatisticsStore();
const snackbar = useTemplateRef<SnackBarType>('snackbar'); const snackbar = useTemplateRef<SnackBarType>('snackbar');
const trendsChart = useTemplateRef<TrendsChartType>('trendsChart');
const exportDialog = useTemplateRef<ExportDialogType>('exportDialog');
const activeTab = ref<string>('statisticsPage'); const activeTab = ref<string>('statisticsPage');
const initing = ref<boolean>(true); const initing = ref<boolean>(true);
@@ -446,6 +464,16 @@ const showFilterTagDialog = ref<boolean>(false);
const isDarkMode = computed<boolean>(() => theme.global.name.value === ThemeType.Dark); const isDarkMode = computed<boolean>(() => theme.global.name.value === ThemeType.Dark);
const statisticsDataHasData = computed<boolean>(() => {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
return !!categoricalAnalysisData.value && !!categoricalAnalysisData.value.items && categoricalAnalysisData.value.items.length > 0;
} else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) {
return !!trendsAnalysisData.value && !!trendsAnalysisData.value.items && trendsAnalysisData.value.items.length > 0 && !!trendsChart.value;
}
return false;
});
const allChartTypes = computed<TypeAndDisplayName[]>(() => { const allChartTypes = computed<TypeAndDisplayName[]>(() => {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) { if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
return getAllCategoricalChartTypes(); return getAllCategoricalChartTypes();
@@ -876,6 +904,31 @@ function setTagFilter(changed: boolean): void {
} }
} }
function exportResults(): void {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis && categoricalAnalysisData.value && categoricalAnalysisData.value.items) {
exportDialog.value?.open({
headers: [
tt('Name'),
tt('Amount') + ` (${defaultCurrency.value})`,
tt('Proportion (%)')
],
data: categoricalAnalysisData.value.items
.filter(item => !item.hidden)
.map(item => [
item.name,
formatAmount(item.totalAmount, {}),
item.percent.toFixed(4)
])
});
} else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis && trendsAnalysisData.value && trendsAnalysisData.value.items && trendsChart.value) {
const exportData = trendsChart.value.exportData();
exportDialog.value?.open({
headers: exportData.headers || [],
data: exportData.data || []
});
}
}
function onClickPieChartItem(item: Record<string, unknown>): void { function onClickPieChartItem(item: Record<string, unknown>): void {
router.push(getTransactionItemLinkUrl(item['id'] as string)); router.push(getTransactionItemLinkUrl(item['id'] as string));
} }
@@ -0,0 +1,206 @@
<template>
<v-dialog width="1000" v-model="showState">
<v-card class="pa-2 pa-sm-4 pa-md-4">
<template #title>
<div class="d-flex align-center justify-center">
<div class="d-flex w-100 align-center justify-center">
<h4 class="text-h4">{{ tt('Export Results') }}</h4>
</div>
<v-btn density="comfortable" color="default" variant="text" class="ml-2" :icon="true">
<v-icon :icon="mdiDotsVertical" />
<v-menu activator="parent">
<v-list>
<v-list-subheader :title="tt('Field Separator')"/>
<v-list-item :prepend-icon="mdiComma"
:append-icon="separator === ',' ? mdiCheck : undefined"
:title="tt('Comma')"
@click="separator = ','"></v-list-item>
<v-list-item :prepend-icon="mdiKeyboardTab"
:append-icon="separator === '\t' ? mdiCheck : undefined"
:title="tt('Tab')"
@click="separator = '\t'"></v-list-item>
</v-list>
</v-menu>
</v-btn>
</div>
</template>
<v-card-text class="py-0 w-100 d-flex justify-center">
<v-switch class="export-data-display-switch" color="secondary"
:label="tt('Raw Data')"
v-model="showRawData"
@click="showRawData = !showRawData">
<template #prepend>
<span>{{ tt('Table') }}</span>
</template>
</v-switch>
</v-card-text>
<v-card-text class="my-md-4 w-100 d-flex justify-center">
<v-data-table
fixed-header
fixed-footer
multi-sort
density="compact"
height="365"
:headers="dataTableHeaders"
:items="dataTableItems"
:hover="true"
:hide-default-footer="true"
:items-per-page="dataTableItems.length"
:no-data-text="tt('No data')"
v-if="!showRawData"
></v-data-table>
<div class="w-100 pl-2 code-container" v-if="showRawData">
<textarea class="w-100" style="outline: none; height: 360px" :readonly="true" :value="exportedData"></textarea>
</div>
</v-card-text>
<v-card-text class="overflow-y-visible">
<div ref="buttonContainer" class="w-100 d-flex justify-center gap-4">
<v-btn-group variant="tonal" density="comfortable">
<v-btn color="primary" :disabled="!exportedData" @click="copy">{{ tt('Copy') }}</v-btn>
<v-btn density="compact" color="primary" :disabled="!exportedData" :icon="true">
<v-icon :icon="mdiMenuDown" size="24" />
<v-menu activator="parent">
<v-list>
<v-list-item :title="tt('Save')" @click="save()"></v-list-item>
</v-list>
</v-menu>
</v-btn>
</v-btn-group>
<v-btn color="secondary" variant="tonal" @click="cancel">{{ tt('Cancel') }}</v-btn>
</div>
</v-card-text>
</v-card>
</v-dialog>
<snack-bar ref="snackbar" />
</template>
<script setup lang="ts">
import SnackBar from '@/components/desktop/SnackBar.vue';
import { ref, computed, useTemplateRef } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import { useUserStore } from '@/stores/user.ts';
import { copyTextToClipboard, startDownloadFile } from '@/lib/ui/common.ts';
import {
mdiDotsVertical,
mdiCheck,
mdiComma,
mdiKeyboardTab,
mdiMenuDown
} from '@mdi/js';
type SnackBarType = InstanceType<typeof SnackBar>;
const { tt } = useI18n();
const userStore = useUserStore();
const buttonContainer = useTemplateRef<HTMLElement>('buttonContainer');
const snackbar = useTemplateRef<SnackBarType>('snackbar');
const showState = ref<boolean>(false);
const headers = ref<string[]>([]);
const data = ref<string[][]>([]);
const separator = ref<string>(',');
const showRawData = ref<boolean>(false);
const dataTableHeaders = computed<object[]>(() => {
return headers.value.map((header, index) => ({
key: index.toString(),
value: `column${index}`,
title: header,
sortable: index > 0,
nowrap: true
}));
});
const dataTableItems = computed<object[]>(() => {
return data.value.map(row => {
const item: Record<string, string> = {};
row.forEach((value, index) => {
item[`column${index}`] = value;
});
return item;
});
});
const exportedData = computed<string>(() => {
let ret = '';
if (headers.value.length > 0) {
ret += headers.value.join(separator.value);
}
for (const row of data.value) {
ret += '\n';
ret += row.join(separator.value);
}
return ret;
});
function getExportFileName(fileExtension: string): string {
const nickname = userStore.currentUserNickname;
if (nickname) {
return tt('dataExport.exportStatisticsFileName', {
nickname: nickname
}) + '.' + fileExtension;
}
return tt('dataExport.defaultExportStatisticsFileName') + '.' + fileExtension;
}
function open(options: { headers: string[], data: string[][] }): void {
headers.value = options.headers || [];
data.value = options.data || [];
separator.value = ',';
showRawData.value = false;
showState.value = true;
}
function copy(): void {
copyTextToClipboard(exportedData.value, buttonContainer.value);
snackbar.value?.showMessage('Data copied');
}
function save(): void {
let fileExtension = 'csv';
let contentType = 'text/csv';
if (separator.value === '\t') {
fileExtension = 'tsv';
contentType = 'text/tab-separated-values';
}
startDownloadFile(getExportFileName(fileExtension), new Blob([exportedData.value], { type: contentType }));
}
function cancel(): void {
showState.value = false;
}
defineExpose({
open
});
</script>
<style>
.export-data-display-switch {
cursor: pointer;
}
.export-data-display-switch.v-input--horizontal .v-input__prepend {
margin-right: 10px; /* same as the padding-left of `.v-switch .v-label` */
}
</style>