add trend in income and expense card in overview page
This commit is contained in:
@@ -290,7 +290,7 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.Context) (interface{
|
||||
return nil, errs.ErrQueryItemsEmpty
|
||||
}
|
||||
|
||||
if len(requestItems) > 5 {
|
||||
if len(requestItems) > 10 {
|
||||
log.WarnfWithRequestId(c, "[transactions.TransactionAmountsHandler] parse request failed, because there are too many items")
|
||||
return nil, errs.ErrQueryItemsTooMuch
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
@@ -0,0 +1,7 @@
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||
<svg width="256px" height="256px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
||||
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -1,3 +1,18 @@
|
||||
const allMonthsArray = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
];
|
||||
|
||||
const allWeekDays = {
|
||||
Sunday: {
|
||||
type: 0,
|
||||
@@ -190,6 +205,7 @@ const defaultDateTimeFormatValue = 0;
|
||||
export default {
|
||||
allWeekDays: allWeekDays,
|
||||
allWeekDaysArray: allWeekDaysArray,
|
||||
allMonthsArray: allMonthsArray,
|
||||
allLongDateFormat: allLongDateFormat,
|
||||
allLongDateFormatArray: allLongDateFormatArray,
|
||||
allShortDateFormat: allShortDateFormat,
|
||||
|
||||
+4
-1
@@ -46,8 +46,9 @@ import 'vuetify/styles';
|
||||
|
||||
import * as echarts from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { PieChart } from 'echarts/charts';
|
||||
import { BarChart, PieChart } from 'echarts/charts';
|
||||
import {
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
} from 'echarts/components';
|
||||
@@ -363,7 +364,9 @@ const vuetify = createVuetify({
|
||||
|
||||
echarts.use([
|
||||
CanvasRenderer,
|
||||
BarChart,
|
||||
PieChart,
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent
|
||||
]);
|
||||
|
||||
@@ -133,6 +133,11 @@ export function getDayOfWeekName(date) {
|
||||
return dateTimeConstants.allWeekDaysArray[dayOfWeek].name;
|
||||
}
|
||||
|
||||
export function getMonthName(date) {
|
||||
const dayOfWeek = moment(date).month();
|
||||
return dateTimeConstants.allMonthsArray[dayOfWeek];
|
||||
}
|
||||
|
||||
export function getHour(date) {
|
||||
return moment(date).hour();
|
||||
}
|
||||
|
||||
+21
-1
@@ -258,7 +258,7 @@ export default {
|
||||
|
||||
return axios.get('v1/transactions/statistics.json' + (queryParams.length ? '?' + queryParams.join('&') : ''));
|
||||
},
|
||||
getTransactionAmounts: ({ today, thisWeek, thisMonth, thisYear }) => {
|
||||
getTransactionAmounts: ({ today, thisWeek, thisMonth, thisYear, lastMonth, monthBeforeLastMonth, monthBeforeLast2Months, monthBeforeLast3Months, monthBeforeLast4Months }) => {
|
||||
const queryParams = [];
|
||||
|
||||
if (today) {
|
||||
@@ -277,6 +277,26 @@ export default {
|
||||
queryParams.push(`thisYear_${thisYear.startTime}_${thisYear.endTime}`);
|
||||
}
|
||||
|
||||
if (lastMonth) {
|
||||
queryParams.push(`lastMonth_${lastMonth.startTime}_${lastMonth.endTime}`);
|
||||
}
|
||||
|
||||
if (monthBeforeLastMonth) {
|
||||
queryParams.push(`monthBeforeLastMonth_${monthBeforeLastMonth.startTime}_${monthBeforeLastMonth.endTime}`);
|
||||
}
|
||||
|
||||
if (monthBeforeLast2Months) {
|
||||
queryParams.push(`monthBeforeLast2Months_${monthBeforeLast2Months.startTime}_${monthBeforeLast2Months.endTime}`);
|
||||
}
|
||||
|
||||
if (monthBeforeLast3Months) {
|
||||
queryParams.push(`monthBeforeLast3Months_${monthBeforeLast3Months.startTime}_${monthBeforeLast3Months.endTime}`);
|
||||
}
|
||||
|
||||
if (monthBeforeLast4Months) {
|
||||
queryParams.push(`monthBeforeLast4Months_${monthBeforeLast4Months.startTime}_${monthBeforeLast4Months.endTime}`);
|
||||
}
|
||||
|
||||
return axios.get('v1/transactions/amounts.json' + (queryParams.length ? '?query=' + queryParams.join('|') : ''));
|
||||
},
|
||||
getTransaction: ({ id }) => {
|
||||
|
||||
@@ -837,6 +837,7 @@ export default {
|
||||
'PIN code is wrong': 'PIN code is wrong',
|
||||
'Sign Up': 'Sign Up',
|
||||
'Overview': 'Overview',
|
||||
'Trend in Income and Expense': 'Trend in Income and Expense',
|
||||
'View Details': 'View Details',
|
||||
'Transaction List': 'Transaction List',
|
||||
'Account List': 'Account List',
|
||||
|
||||
@@ -837,6 +837,7 @@ export default {
|
||||
'PIN code is wrong': 'PIN码错误',
|
||||
'Sign Up': '注册',
|
||||
'Overview': '总览',
|
||||
'Trend in Income and Expense': '收入与支出趋势',
|
||||
'View Details': '查看详情',
|
||||
'Transaction List': '交易列表',
|
||||
'Account List': '账户列表',
|
||||
|
||||
+65
-9
@@ -5,6 +5,7 @@ import { useExchangeRatesStore } from './exchangeRates.js';
|
||||
|
||||
import { isNumber, isEquals } from '@/lib/common.js';
|
||||
import {
|
||||
getUnixTimeBeforeUnixTime,
|
||||
getTodayFirstUnixTime,
|
||||
getTodayLastUnixTime,
|
||||
getThisWeekFirstUnixTime,
|
||||
@@ -31,6 +32,21 @@ function updateTransactionDateRange(state) {
|
||||
|
||||
state.transactionDataRange.thisYear.startTime = getThisYearFirstUnixTime();
|
||||
state.transactionDataRange.thisYear.endTime = getThisYearLastUnixTime();
|
||||
|
||||
state.transactionDataRange.lastMonth.startTime = getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 1, 'months');
|
||||
state.transactionDataRange.lastMonth.endTime = getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 1, 'months');
|
||||
|
||||
state.transactionDataRange.monthBeforeLastMonth.startTime = getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 2, 'months');
|
||||
state.transactionDataRange.monthBeforeLastMonth.endTime = getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 2, 'months');
|
||||
|
||||
state.transactionDataRange.monthBeforeLast2Months.startTime = getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 3, 'months');
|
||||
state.transactionDataRange.monthBeforeLast2Months.endTime = getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 3, 'months');
|
||||
|
||||
state.transactionDataRange.monthBeforeLast3Months.startTime = getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 4, 'months');
|
||||
state.transactionDataRange.monthBeforeLast3Months.endTime = getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 4, 'months');
|
||||
|
||||
state.transactionDataRange.monthBeforeLast4Months.startTime = getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 5, 'months');
|
||||
state.transactionDataRange.monthBeforeLast4Months.endTime = getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 5, 'months');
|
||||
}
|
||||
|
||||
export const useOverviewStore = defineStore('overview', {
|
||||
@@ -51,8 +67,31 @@ export const useOverviewStore = defineStore('overview', {
|
||||
thisYear: {
|
||||
startTime: getThisYearFirstUnixTime(),
|
||||
endTime: getThisYearLastUnixTime()
|
||||
},
|
||||
lastMonth: {
|
||||
startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 1, 'months'),
|
||||
endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 1, 'months')
|
||||
},
|
||||
monthBeforeLastMonth: {
|
||||
startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 2, 'months'),
|
||||
endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 2, 'months')
|
||||
},
|
||||
monthBeforeLast2Months: {
|
||||
startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 3, 'months'),
|
||||
endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 3, 'months')
|
||||
},
|
||||
monthBeforeLast3Months: {
|
||||
startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 4, 'months'),
|
||||
endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 4, 'months')
|
||||
},
|
||||
monthBeforeLast4Months: {
|
||||
startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 5, 'months'),
|
||||
endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 5, 'months')
|
||||
}
|
||||
},
|
||||
transactionOverviewOptions: {
|
||||
loadLast5Months: false
|
||||
},
|
||||
transactionOverviewData: {},
|
||||
transactionOverviewStateInvalid: true
|
||||
}),
|
||||
@@ -78,7 +117,7 @@ export const useOverviewStore = defineStore('overview', {
|
||||
const finalOverviewData = {};
|
||||
const defaultCurrency = userStore.currentUserDefaultCurrency;
|
||||
|
||||
[ 'today', 'thisWeek', 'thisMonth', 'thisYear' ].forEach(field => {
|
||||
[ 'today', 'thisWeek', 'thisMonth', 'thisYear', 'lastMonth', 'monthBeforeLastMonth', 'monthBeforeLast2Months', 'monthBeforeLast3Months', 'monthBeforeLast4Months' ].forEach(field => {
|
||||
if (!Object.prototype.hasOwnProperty.call(overviewData, field)) {
|
||||
return;
|
||||
}
|
||||
@@ -139,31 +178,47 @@ export const useOverviewStore = defineStore('overview', {
|
||||
},
|
||||
resetTransactionOverview() {
|
||||
updateTransactionDateRange(this);
|
||||
this.transactionOverviewOptions.loadLast5Months = false;
|
||||
this.transactionOverviewData = {};
|
||||
this.transactionOverviewStateInvalid = true;
|
||||
},
|
||||
loadTransactionOverview({ force }) {
|
||||
loadTransactionOverview({ force, loadLast5Months }) {
|
||||
const self = this;
|
||||
let dateChanged = false;
|
||||
let rangeChanged = false;
|
||||
|
||||
if (self.transactionDataRange.today.startTime !== getTodayFirstUnixTime()) {
|
||||
dateChanged = true;
|
||||
updateTransactionDateRange(self);
|
||||
}
|
||||
|
||||
if (!dateChanged && !force && !self.transactionOverviewStateInvalid) {
|
||||
if (loadLast5Months && !self.transactionOverviewOptions.loadLast5Months) {
|
||||
rangeChanged = true;
|
||||
}
|
||||
|
||||
if (!dateChanged && !rangeChanged && !force && !self.transactionOverviewStateInvalid) {
|
||||
return new Promise((resolve) => {
|
||||
resolve(self.transactionOverviewData);
|
||||
});
|
||||
}
|
||||
|
||||
const requestParams = {
|
||||
today: self.transactionDataRange.today,
|
||||
thisWeek: self.transactionDataRange.thisWeek,
|
||||
thisMonth: self.transactionDataRange.thisMonth,
|
||||
thisYear: self.transactionDataRange.thisYear
|
||||
};
|
||||
|
||||
if (loadLast5Months) {
|
||||
requestParams.lastMonth = self.transactionDataRange.lastMonth;
|
||||
requestParams.monthBeforeLastMonth = self.transactionDataRange.monthBeforeLastMonth;
|
||||
requestParams.monthBeforeLast2Months = self.transactionDataRange.monthBeforeLast2Months;
|
||||
requestParams.monthBeforeLast3Months = self.transactionDataRange.monthBeforeLast3Months;
|
||||
requestParams.monthBeforeLast4Months = self.transactionDataRange.monthBeforeLast4Months;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
services.getTransactionAmounts({
|
||||
today: self.transactionDataRange.today,
|
||||
thisWeek: self.transactionDataRange.thisWeek,
|
||||
thisMonth: self.transactionDataRange.thisMonth,
|
||||
thisYear: self.transactionDataRange.thisYear
|
||||
}).then(response => {
|
||||
services.getTransactionAmounts(requestParams).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
@@ -181,6 +236,7 @@ export const useOverviewStore = defineStore('overview', {
|
||||
}
|
||||
|
||||
self.transactionOverviewData = data.result;
|
||||
self.transactionOverviewOptions.loadLast5Months = loadLast5Months;
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
<v-card :class="{ 'disabled': loadingOverview }">
|
||||
<template #title>
|
||||
<div class="d-flex align-center">
|
||||
<span class="text-2xl font-weight-bold">{{ displayDateRange.thisMonth.displayTime }}</span>
|
||||
<span>·</span>
|
||||
<small>{{ $t('Expense') }}</small>
|
||||
<div class="d-flex align-baseline">
|
||||
<span class="text-2xl font-weight-bold">{{ displayDateRange.thisMonth.displayTime }}</span>
|
||||
<span>·</span>
|
||||
<small>{{ $t('Expense') }}</small>
|
||||
</div>
|
||||
<v-btn density="compact" color="default" variant="text"
|
||||
class="ml-2" :icon="true"
|
||||
v-if="!loadingOverview" @click="reload(true)">
|
||||
@@ -25,11 +27,13 @@
|
||||
<v-icon :icon="showAmountInHomePage ? icons.eyeSlash : icons.eye" size="20" />
|
||||
</v-btn>
|
||||
</h5>
|
||||
<p>
|
||||
<div class="mt-2 mb-3">
|
||||
<span class="mr-2">{{ $t('Monthly income') }}</span>
|
||||
<span>{{ transactionOverview && transactionOverview.thisMonth ? getDisplayIncomeAmount(transactionOverview.thisMonth) : '-' }}</span>
|
||||
</p>
|
||||
<v-btn size="small" to="/transactions">{{ $t('View Details') }}</v-btn>
|
||||
</div>
|
||||
<v-btn size="small" to="/transactions?dateType=7">{{ $t('View Details') }}</v-btn>
|
||||
<v-img class="overview-card-background" src="img/desktop/card-background.png"/>
|
||||
<v-img class="overview-card-background-image" width="116px" src="img/desktop/document.svg"/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
@@ -97,13 +101,21 @@
|
||||
</template>
|
||||
</income-expense-overview-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<monthly-income-and-expense-card :data="monthlyIncomeAndExpenseData"
|
||||
:disabled="loadingOverview" :is-dark-mode="isDarkMode" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<snack-bar ref="snackbar" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useTheme } from 'vuetify';
|
||||
|
||||
import IncomeExpenseOverviewCard from './overview/IncomeExpenseOverviewCard.vue';
|
||||
import MonthlyIncomeAndExpenseCard from './overview/MonthlyIncomeAndExpenseCard.vue';
|
||||
|
||||
import { mapStores } from 'pinia';
|
||||
import { useSettingsStore } from '@/stores/setting.js';
|
||||
@@ -117,6 +129,7 @@ import {
|
||||
mdiRefresh,
|
||||
mdiEyeOutline,
|
||||
mdiEyeOffOutline,
|
||||
mdiCartOutline,
|
||||
mdiCalendarTodayOutline,
|
||||
mdiCalendarWeekOutline,
|
||||
mdiCalendarMonthOutline,
|
||||
@@ -127,7 +140,8 @@ import {
|
||||
|
||||
export default {
|
||||
components: {
|
||||
IncomeExpenseOverviewCard
|
||||
IncomeExpenseOverviewCard,
|
||||
MonthlyIncomeAndExpenseCard
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -136,6 +150,7 @@ export default {
|
||||
refresh: mdiRefresh,
|
||||
eye: mdiEyeOutline,
|
||||
eyeSlash: mdiEyeOffOutline,
|
||||
cart: mdiCartOutline,
|
||||
calendarToday: mdiCalendarTodayOutline,
|
||||
calendarWeek: mdiCalendarWeekOutline,
|
||||
calendarMonth: mdiCalendarMonthOutline,
|
||||
@@ -147,6 +162,9 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useSettingsStore, useUserStore, useOverviewStore),
|
||||
isDarkMode() {
|
||||
return this.globalTheme.global.name.value === 'dark';
|
||||
},
|
||||
showAmountInHomePage: {
|
||||
get: function() {
|
||||
return this.settingsStore.appSettings.showAmountInHomePage;
|
||||
@@ -184,6 +202,34 @@ export default {
|
||||
},
|
||||
transactionOverview() {
|
||||
return this.overviewStore.transactionOverview;
|
||||
},
|
||||
monthlyIncomeAndExpenseData() {
|
||||
const self = this;
|
||||
const data = [];
|
||||
|
||||
[ 'monthBeforeLast4Months', 'monthBeforeLast3Months', 'monthBeforeLast2Months', 'monthBeforeLastMonth', 'lastMonth', 'thisMonth' ].forEach(fieldName => {
|
||||
if (!Object.prototype.hasOwnProperty.call(self.transactionOverview, fieldName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dateRange = self.overviewStore.transactionDataRange[fieldName];
|
||||
|
||||
if (!dateRange) {
|
||||
return;
|
||||
}
|
||||
|
||||
const item = self.overviewStore.transactionOverview[fieldName];
|
||||
|
||||
data.push({
|
||||
monthStartTime: dateRange.startTime,
|
||||
incomeAmount: item ? item.incomeAmount : 0,
|
||||
expenseAmount: item ? item.expenseAmount : 0,
|
||||
incompleteIncomeAmount: item ? item.incompleteIncomeAmount : true,
|
||||
incompleteExpenseAmount: item ? item.incompleteExpenseAmount : true
|
||||
});
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -191,6 +237,13 @@ export default {
|
||||
this.reload(false);
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const theme = useTheme();
|
||||
|
||||
return {
|
||||
globalTheme: theme
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
reload(force) {
|
||||
const self = this;
|
||||
@@ -198,7 +251,8 @@ export default {
|
||||
self.loadingOverview = true;
|
||||
|
||||
self.overviewStore.loadTransactionOverview({
|
||||
force: force
|
||||
force: force,
|
||||
loadLast5Months: true
|
||||
}).then(() => {
|
||||
self.loadingOverview = false;
|
||||
|
||||
@@ -235,3 +289,19 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.overview-card-background {
|
||||
position: absolute;
|
||||
inline-size: 9rem;
|
||||
inset-block-end: 0;
|
||||
inset-inline-end: 0;
|
||||
}
|
||||
|
||||
.overview-card-background-image {
|
||||
position: absolute;
|
||||
inline-size: 5rem;
|
||||
inset-block-end: 0.5rem;
|
||||
inset-inline-end: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<v-card :class="{ 'disabled': disabled }">
|
||||
<template #title>
|
||||
<span class="text-2xl font-weight-bold">{{ $t('Trend in Income and Expense') }}</span>
|
||||
</template>
|
||||
|
||||
<v-chart class="overview-monthly-chart" autoresize :option="chartOptions" />
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapStores } from 'pinia';
|
||||
import { useSettingsStore } from '@/stores/setting.js';
|
||||
import { useUserStore } from '@/stores/user.js';
|
||||
|
||||
import {
|
||||
parseDateFromUnixTime,
|
||||
getMonthName
|
||||
} from '@/lib/datetime.js';
|
||||
|
||||
export default {
|
||||
props: [
|
||||
'data',
|
||||
'disabled',
|
||||
'isDarkMode'
|
||||
],
|
||||
computed: {
|
||||
...mapStores(useSettingsStore, useUserStore),
|
||||
showAmountInHomePage() {
|
||||
return this.settingsStore.appSettings.showAmountInHomePage;
|
||||
},
|
||||
defaultCurrency() {
|
||||
return this.userStore.currentUserDefaultCurrency;
|
||||
},
|
||||
chartOptions() {
|
||||
const self = this;
|
||||
const monthNames = [];
|
||||
const incomeAmounts = [];
|
||||
const expenseAmounts = [];
|
||||
let minAmount = 0;
|
||||
let maxAmount = 0;
|
||||
|
||||
if (self.data) {
|
||||
for (let i = 0; i < self.data.length; i++) {
|
||||
const item = self.data[i];
|
||||
const month = getMonthName(parseDateFromUnixTime(item.monthStartTime));
|
||||
|
||||
monthNames.push(self.$locale.getMonthShortName(month));
|
||||
incomeAmounts.push(item.incomeAmount);
|
||||
expenseAmounts.push(-item.expenseAmount);
|
||||
|
||||
if (item.incomeAmount > maxAmount) {
|
||||
maxAmount = item.incomeAmount;
|
||||
}
|
||||
|
||||
if (-item.expenseAmount > maxAmount) {
|
||||
maxAmount = -item.expenseAmount;
|
||||
}
|
||||
|
||||
if (item.incomeAmount < minAmount) {
|
||||
minAmount = item.incomeAmount;
|
||||
}
|
||||
|
||||
if (-item.expenseAmount < minAmount) {
|
||||
minAmount = -item.expenseAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let amountGap = maxAmount - minAmount;
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
},
|
||||
backgroundColor: self.isDarkMode ? '#333' : '#fff',
|
||||
borderColor: self.isDarkMode ? '#333' : '#fff',
|
||||
textStyle: {
|
||||
color: self.isDarkMode ? '#eee' : '#333'
|
||||
},
|
||||
formatter: params => {
|
||||
let incomeAmount = 0;
|
||||
let incomeColor = '#cc4a66';
|
||||
let expenseAmount = 0;
|
||||
let expenseColor = '#4dd291';
|
||||
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
const param = params[i];
|
||||
const dataIndex = param.dataIndex;
|
||||
const data = self.data[dataIndex];
|
||||
|
||||
if (param.seriesId === 'seriesIncome') {
|
||||
incomeAmount = self.getDisplayIncomeAmount(data);
|
||||
incomeColor = param.color;
|
||||
} else if (param.seriesId === 'seriesExpense') {
|
||||
expenseAmount = self.getDisplayExpenseAmount(data);
|
||||
expenseColor = param.color;
|
||||
}
|
||||
}
|
||||
|
||||
return `<table>` +
|
||||
`<thead>` +
|
||||
`<tr>` +
|
||||
`<td colspan="2" class="text-left">${params[0].name}</td>` +
|
||||
`</tr>` +
|
||||
`</thead>` +
|
||||
`<tbody>` +
|
||||
`<tr>` +
|
||||
`<td><span class="overview-monthly-chart-tooltip-indicator mr-1" style="background-color: ${incomeColor}"></span><span class="mr-4">${self.$t('Income')}</span></td>` +
|
||||
`<td><strong>${incomeAmount}</strong></td>` +
|
||||
`</tr>` +
|
||||
`<tr>` +
|
||||
`<td><span class="overview-monthly-chart-tooltip-indicator mr-1" style="background-color: ${expenseColor}"></span><span class="mr-4">${self.$t('Expense')}</span></td>` +
|
||||
`<td><strong>${expenseAmount}</strong></td>` +
|
||||
`</tr>` +
|
||||
`</tbody>` +
|
||||
`</table>`;
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
bottom: 20,
|
||||
itemWidth: 14,
|
||||
itemHeight: 14,
|
||||
textStyle: {
|
||||
color: self.isDarkMode ? '#eee' : '#333'
|
||||
},
|
||||
icon: 'circle',
|
||||
data: [ self.$t('Income'), self.$t('Expense') ]
|
||||
},
|
||||
grid: {
|
||||
left: '20px',
|
||||
right: '20px',
|
||||
top: '10px',
|
||||
bottom: '100px'
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: monthNames,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
padding: [ 20, 0, 0, 0 ]
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
min: minAmount - amountGap / 20,
|
||||
max: maxAmount,
|
||||
splitNumber: 10,
|
||||
axisLabel: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
min: minAmount,
|
||||
max: maxAmount + amountGap / 20,
|
||||
splitNumber: 10,
|
||||
axisLabel: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
id: 'seriesIncome',
|
||||
name: self.$t('Income'),
|
||||
yAxisIndex: 0,
|
||||
stack: 'Total',
|
||||
itemStyle: {
|
||||
color: '#cc4a66',
|
||||
borderRadius: 16
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
labelLine: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
barMaxWidth: 40,
|
||||
data: incomeAmounts
|
||||
},
|
||||
{
|
||||
type: 'bar',
|
||||
id: 'seriesExpense',
|
||||
name: self.$t('Expense'),
|
||||
yAxisIndex: 1,
|
||||
stack: 'Total',
|
||||
itemStyle: {
|
||||
color: '#4dd291',
|
||||
borderRadius: 16
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
labelLine: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
barMaxWidth: 40,
|
||||
data: expenseAmounts
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDisplayCurrency(value, currencyCode) {
|
||||
return this.$locale.getDisplayCurrency(value, currencyCode, {
|
||||
currencyDisplayMode: this.settingsStore.appSettings.currencyDisplayMode,
|
||||
enableThousandsSeparator: this.settingsStore.appSettings.thousandsSeparator
|
||||
});
|
||||
},
|
||||
getDisplayAmount(amount, incomplete) {
|
||||
if (!this.showAmountInHomePage) {
|
||||
return this.getDisplayCurrency('***', this.defaultCurrency);
|
||||
}
|
||||
|
||||
return this.getDisplayCurrency(amount, this.defaultCurrency) + (incomplete ? '+' : '');
|
||||
},
|
||||
getDisplayIncomeAmount(category) {
|
||||
return this.getDisplayAmount(category.incomeAmount, category.incompleteIncomeAmount);
|
||||
},
|
||||
getDisplayExpenseAmount(category) {
|
||||
return this.getDisplayAmount(category.expenseAmount, category.incompleteExpenseAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.overview-monthly-chart {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.overview-monthly-chart-tooltip-indicator {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -270,6 +270,11 @@
|
||||
"url": "https://materialdesignicons.com",
|
||||
"licenseUrl": "https://github.com/Templarian/MaterialDesign-JS/blob/v7.2.96/LICENSE"
|
||||
},
|
||||
{
|
||||
"name": "Solar Icons Set",
|
||||
"url": "https://www.figma.com/community/file/1166831539721848736/Solar-Icons-Set",
|
||||
"licenseUrl": "https://creativecommons.org/licenses/by/4.0"
|
||||
},
|
||||
{
|
||||
"name": "Hand drawn minimal background",
|
||||
"url": "https://www.freepik.com/free-vector/hand-drawn-minimal-background_15441932.htm",
|
||||
|
||||
Reference in New Issue
Block a user