mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-20 09:44:26 +08:00
move files
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-card :title="$t('Basic Settings')">
|
||||
<v-form>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
item-title="displayName"
|
||||
item-value="value"
|
||||
persistent-placeholder
|
||||
:label="$t('Theme')"
|
||||
:placeholder="$t('Theme')"
|
||||
:items="[
|
||||
{ value: 'auto', displayName: $t('System Default') },
|
||||
{ value: 'light', displayName: $t('Light') },
|
||||
{ value: 'dark', displayName: $t('Dark') }
|
||||
]"
|
||||
v-model="theme"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-autocomplete
|
||||
item-title="displayNameWithUtcOffset"
|
||||
item-value="name"
|
||||
persistent-placeholder
|
||||
:label="$t('Timezone')"
|
||||
:placeholder="$t('Timezone')"
|
||||
:items="allTimezones"
|
||||
:no-data-text="$t('No results')"
|
||||
v-model="timeZone"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
item-title="displayName"
|
||||
item-value="value"
|
||||
persistent-placeholder
|
||||
:label="$t('Auto-update Exchange Rates Data')"
|
||||
:placeholder="$t('Auto-update Exchange Rates Data')"
|
||||
:items="enableDisableOptions"
|
||||
v-model="isAutoUpdateExchangeRatesData"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
item-title="displayName"
|
||||
item-value="value"
|
||||
persistent-placeholder
|
||||
:label="$t('Enable Thousands Separator')"
|
||||
:placeholder="$t('Enable Thousands Separator')"
|
||||
:items="enableDisableOptions"
|
||||
v-model="isEnableThousandsSeparator"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
item-title="displayName"
|
||||
item-value="value"
|
||||
persistent-placeholder
|
||||
:label="$t('Currency Display Mode')"
|
||||
:placeholder="$t('Currency Display Mode')"
|
||||
:items="[
|
||||
{ value: allCurrencyDisplayModes.None, displayName: $t('None') },
|
||||
{ value: allCurrencyDisplayModes.Symbol, displayName: $t('Currency Symbol') },
|
||||
{ value: allCurrencyDisplayModes.Code, displayName: $t('Currency Code') },
|
||||
{ value: allCurrencyDisplayModes.Name, displayName: $t('Currency Name') }
|
||||
]"
|
||||
v-model="currencyDisplayMode"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
item-title="displayName"
|
||||
item-value="value"
|
||||
persistent-placeholder
|
||||
:label="$t('Show Account Balance')"
|
||||
:placeholder="$t('Show Account Balance')"
|
||||
:items="enableDisableOptions"
|
||||
v-model="showAccountBalance"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-form>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-card :title="$t('Overview Page')">
|
||||
<v-form>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
item-title="displayName"
|
||||
item-value="value"
|
||||
persistent-placeholder
|
||||
:label="$t('Show Amount')"
|
||||
:placeholder="$t('Show Amount')"
|
||||
:items="enableDisableOptions"
|
||||
v-model="showAmountInHomePage"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-form>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-card :title="$t('Transaction List Page')">
|
||||
<v-form>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
item-title="displayName"
|
||||
item-value="value"
|
||||
persistent-placeholder
|
||||
:label="$t('Show Monthly Total Amount')"
|
||||
:placeholder="$t('Show Monthly Total Amount')"
|
||||
:items="enableDisableOptions"
|
||||
v-model="showTotalAmountInTransactionListPage"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-form>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-card :title="$t('Transaction Edit Page')">
|
||||
<v-form>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
item-title="displayName"
|
||||
item-value="value"
|
||||
persistent-placeholder
|
||||
:label="$t('Automatically Add Geolocation')"
|
||||
:placeholder="$t('Automatically Add Geolocation')"
|
||||
:items="enableDisableOptions"
|
||||
v-model="isAutoGetCurrentGeoLocation"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-form>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useTheme } from 'vuetify';
|
||||
|
||||
import { mapStores } from 'pinia';
|
||||
import { useRootStore } from '@/stores/index.js';
|
||||
import { useSettingsStore } from '@/stores/setting.js';
|
||||
import { useUserStore } from '@/stores/user.js';
|
||||
import { useTransactionsStore } from '@/stores/transaction.js';
|
||||
import { useOverviewStore } from '@/stores/overview.js';
|
||||
import { useStatisticsStore } from '@/stores/statistics.js';
|
||||
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
|
||||
|
||||
import currencyConstants from '@/consts/currency.js';
|
||||
import { getSystemTheme } from '@/lib/ui.js';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapStores(useRootStore, useSettingsStore, useUserStore, useTransactionsStore, useOverviewStore, useStatisticsStore, useExchangeRatesStore),
|
||||
enableDisableOptions() {
|
||||
return this.$locale.getEnableDisableOptions();
|
||||
},
|
||||
allTimezones() {
|
||||
return this.$locale.getAllTimezones(true);
|
||||
},
|
||||
allCurrencyDisplayModes() {
|
||||
return currencyConstants.allCurrencyDisplayModes;
|
||||
},
|
||||
theme: {
|
||||
get: function () {
|
||||
return this.settingsStore.appSettings.theme;
|
||||
},
|
||||
set: function (value) {
|
||||
if (value !== this.settingsStore.appSettings.theme) {
|
||||
this.settingsStore.setTheme(value);
|
||||
|
||||
if (value === 'light' || value === 'dark') {
|
||||
this.globalTheme.global.name.value = value;
|
||||
} else {
|
||||
this.globalTheme.global.name.value = getSystemTheme();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
timeZone: {
|
||||
get: function () {
|
||||
return this.settingsStore.appSettings.timeZone;
|
||||
},
|
||||
set: function (value) {
|
||||
this.settingsStore.setTimeZone(value);
|
||||
this.$locale.setTimeZone(value);
|
||||
this.transactionsStore.updateTransactionListInvalidState(true);
|
||||
this.overviewStore.updateTransactionOverviewInvalidState(true);
|
||||
this.statisticsStore.updateTransactionStatisticsInvalidState(true);
|
||||
}
|
||||
},
|
||||
isAutoUpdateExchangeRatesData: {
|
||||
get: function () {
|
||||
return this.settingsStore.appSettings.autoUpdateExchangeRatesData;
|
||||
},
|
||||
set: function (value) {
|
||||
this.settingsStore.setAutoUpdateExchangeRatesData(value);
|
||||
}
|
||||
},
|
||||
isEnableThousandsSeparator: {
|
||||
get: function () {
|
||||
return this.settingsStore.appSettings.thousandsSeparator;
|
||||
},
|
||||
set: function (value) {
|
||||
this.settingsStore.setEnableThousandsSeparator(value);
|
||||
}
|
||||
},
|
||||
currencyDisplayMode: {
|
||||
get: function () {
|
||||
return this.settingsStore.appSettings.currencyDisplayMode;
|
||||
},
|
||||
set: function (value) {
|
||||
this.settingsStore.setCurrencyDisplayMode(value);
|
||||
}
|
||||
},
|
||||
showAccountBalance: {
|
||||
get: function () {
|
||||
return this.settingsStore.appSettings.showAccountBalance;
|
||||
},
|
||||
set: function (value) {
|
||||
this.settingsStore.setShowAccountBalance(value);
|
||||
}
|
||||
},
|
||||
showAmountInHomePage: {
|
||||
get: function () {
|
||||
return this.settingsStore.appSettings.showAmountInHomePage;
|
||||
},
|
||||
set: function (value) {
|
||||
this.settingsStore.setShowAmountInHomePage(value);
|
||||
}
|
||||
},
|
||||
showTotalAmountInTransactionListPage: {
|
||||
get: function () {
|
||||
return this.settingsStore.appSettings.showTotalAmountInTransactionListPage;
|
||||
},
|
||||
set: function (value) {
|
||||
this.settingsStore.setShowTotalAmountInTransactionListPage(value);
|
||||
}
|
||||
},
|
||||
isAutoGetCurrentGeoLocation: {
|
||||
get: function () {
|
||||
return this.settingsStore.appSettings.autoGetCurrentGeoLocation;
|
||||
},
|
||||
set: function (value) {
|
||||
this.settingsStore.setAutoGetCurrentGeoLocation(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const theme = useTheme();
|
||||
|
||||
return {
|
||||
globalTheme: theme
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-card :title="$t('Application Lock')">
|
||||
<v-card-text class="pb-0">
|
||||
<p class="text-body-1 font-weight-semibold" v-if="!isEnableApplicationLock">
|
||||
{{ $t('Application lock is not enabled') }}
|
||||
</p>
|
||||
<p class="text-body-1" v-if="isEnableApplicationLock">
|
||||
{{ $t('Application lock has been enabled') }}
|
||||
</p>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text v-if="isEnableApplicationLock">
|
||||
<v-switch inset :disabled="true"
|
||||
:label="$t('Unlock By PIN Code')"
|
||||
v-model="isEnableApplicationLock"/>
|
||||
<v-switch inset
|
||||
:label="$t('Unlock By WebAuthn')"
|
||||
:loading="enablingWebAuthn"
|
||||
v-model="isEnableApplicationLockWebAuthn"/>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text class="pb-0">
|
||||
<p class="text-body-1 font-weight-semibold" v-if="!isEnableApplicationLock">
|
||||
{{ $t('Please input a new 6-digit PIN code. PIN code would encrypt your local data, so you need input this PIN code when you launch this app. If this PIN code is lost, you should re-login.') }}
|
||||
</p>
|
||||
<p class="text-body-1 font-weight-semibold" v-if="isEnableApplicationLock">
|
||||
{{ $t('Please enter your current PIN code when disable application lock.') }}
|
||||
</p>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text class="pb-0">
|
||||
<v-row class="mb-3">
|
||||
<v-col cols="12" md="4">
|
||||
<div style="max-width: 428px">
|
||||
<pin-code-input :secure="true" :length="6" v-model="pinCode" />
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" class="d-flex flex-wrap gap-4">
|
||||
<v-btn :disabled="!pinCodeValid"
|
||||
v-if="!isEnableApplicationLock" @click="enable">
|
||||
{{ $t('Enable Application Lock') }}
|
||||
</v-btn>
|
||||
<v-btn :disabled="!pinCodeValid"
|
||||
v-if="isEnableApplicationLock" @click="disable">
|
||||
{{ $t('Disable Application Lock') }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<snack-bar ref="snackbar" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapStores } from 'pinia';
|
||||
import { useSettingsStore } from '@/stores/setting.js';
|
||||
import { useUserStore } from '@/stores/user.js';
|
||||
|
||||
import logger from '@/lib/logger.js';
|
||||
import webauthn from '@/lib/webauthn.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isSupportedWebAuthn: false,
|
||||
enablingWebAuthn: false,
|
||||
pinCode: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useSettingsStore, useUserStore),
|
||||
isEnableApplicationLock: {
|
||||
get: function () {
|
||||
return this.settingsStore.appSettings.applicationLock;
|
||||
},
|
||||
set: function (value) {
|
||||
this.settingsStore.setEnableApplicationLock(value);
|
||||
}
|
||||
},
|
||||
isEnableApplicationLockWebAuthn: {
|
||||
get: function () {
|
||||
return this.settingsStore.appSettings.applicationLockWebAuthn;
|
||||
},
|
||||
set: function (value) {
|
||||
this.settingsStore.setEnableApplicationLockWebAuthn(value);
|
||||
}
|
||||
},
|
||||
pinCodeValid() {
|
||||
return this.pinCode && this.pinCode.length === 6;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isEnableApplicationLockWebAuthn: function (newValue) {
|
||||
const self = this;
|
||||
|
||||
if (newValue) {
|
||||
self.enablingWebAuthn = true;
|
||||
|
||||
webauthn.registerCredential(
|
||||
self.$user.getUserAppLockState(),
|
||||
self.userStore.currentUserInfo,
|
||||
).then(({ id }) => {
|
||||
self.enablingWebAuthn = false;
|
||||
|
||||
self.$user.saveWebAuthnConfig(id);
|
||||
self.settingsStore.setEnableApplicationLockWebAuthn(true);
|
||||
self.$refs.snackbar.showMessage('You have enabled WebAuthn successfully');
|
||||
}).catch(error => {
|
||||
logger.error('failed to enable WebAuthn', error);
|
||||
|
||||
self.enablingWebAuthn = false;
|
||||
|
||||
if (error.notSupported) {
|
||||
self.$refs.snackbar.showMessage('This device does not support WebAuthn');
|
||||
} else if (error.name === 'NotAllowedError') {
|
||||
self.$refs.snackbar.showMessage('User has canceled authentication');
|
||||
} else if (error.invalid) {
|
||||
self.$refs.snackbar.showMessage('Failed to enable WebAuthn');
|
||||
} else {
|
||||
self.$refs.snackbar.showMessage('User has canceled or this device does not support WebAuthn');
|
||||
}
|
||||
|
||||
self.isEnableApplicationLockWebAuthn = false;
|
||||
self.settingsStore.setEnableApplicationLockWebAuthn(false);
|
||||
self.$user.clearWebAuthnConfig();
|
||||
});
|
||||
} else {
|
||||
self.settingsStore.setEnableApplicationLockWebAuthn(false);
|
||||
self.$user.clearWebAuthnConfig();
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const self = this;
|
||||
webauthn.isCompletelySupported().then(result => {
|
||||
self.isSupportedWebAuthn = result;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
enable() {
|
||||
if (this.settingsStore.appSettings.applicationLock) {
|
||||
this.$refs.snackbar.showMessage('Application lock has been enabled');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.pinCode || this.pinCode.length !== 6) {
|
||||
this.pinCode = '';
|
||||
this.$refs.snackbar.showMessage('PIN code is invalid');
|
||||
return;
|
||||
}
|
||||
|
||||
const user = this.userStore.currentUserInfo;
|
||||
|
||||
if (!user || !user.username) {
|
||||
this.pinCode = '';
|
||||
this.$refs.snackbar.showMessage('An error has occurred');
|
||||
return;
|
||||
}
|
||||
|
||||
this.$user.encryptToken(user.username, this.pinCode);
|
||||
this.settingsStore.setEnableApplicationLock(true);
|
||||
|
||||
this.settingsStore.setEnableApplicationLockWebAuthn(false);
|
||||
this.$user.clearWebAuthnConfig();
|
||||
|
||||
this.pinCode = '';
|
||||
},
|
||||
disable() {
|
||||
if (!this.settingsStore.appSettings.applicationLock) {
|
||||
this.$refs.snackbar.showMessage('Application lock is not enabled');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.$user.isCorrectPinCode(this.pinCode)) {
|
||||
this.pinCode = '';
|
||||
this.$refs.snackbar.showMessage('PIN code is wrong');
|
||||
return;
|
||||
}
|
||||
|
||||
this.pinCode = '';
|
||||
|
||||
this.$user.decryptToken();
|
||||
this.settingsStore.setEnableApplicationLock(false);
|
||||
|
||||
this.settingsStore.setEnableApplicationLockWebAuthn(false);
|
||||
this.$user.clearWebAuthnConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-card :title="$t('Statistics Settings')">
|
||||
<v-form>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
item-title="displayName"
|
||||
item-value="type"
|
||||
persistent-placeholder
|
||||
:label="$t('Default Chart Type')"
|
||||
:placeholder="$t('Default Chart Type')"
|
||||
:items="[
|
||||
{ type: allChartTypes.Pie, displayName: $t('Pie Chart') },
|
||||
{ type: allChartTypes.Bar, displayName: $t('Bar Chart') }
|
||||
]"
|
||||
v-model="defaultChartType"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
item-title="displayName"
|
||||
item-value="type"
|
||||
persistent-placeholder
|
||||
:label="$t('Default Chart Data Type')"
|
||||
:placeholder="$t('Default Chart Data Type')"
|
||||
:items="allChartDataTypes"
|
||||
v-model="defaultChartDataType"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
item-title="displayName"
|
||||
item-value="type"
|
||||
persistent-placeholder
|
||||
:label="$t('Default Date Range')"
|
||||
:placeholder="$t('Default Date Range')"
|
||||
:items="allDateRanges"
|
||||
v-model="defaultDateRange"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
item-title="displayName"
|
||||
item-value="type"
|
||||
persistent-placeholder
|
||||
:label="$t('Default Sort By')"
|
||||
:placeholder="$t('Default Sort By')"
|
||||
:items="allSortingTypes"
|
||||
v-model="defaultSortingType"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-form>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<account-filter-settings-card :auto-save="true" :modify-default="true" />
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<category-filter-settings-card :auto-save="true" :modify-default="true" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapStores } from 'pinia';
|
||||
import { useSettingsStore } from '@/stores/setting.js';
|
||||
|
||||
import statisticsConstants from '@/consts/statistics.js';
|
||||
|
||||
import AccountFilterSettingsCard from '@/views/desktop/statistics/settings/cards/AccountFilterSettingsCard.vue';
|
||||
import CategoryFilterSettingsCard from '@/views/desktop/statistics/settings/cards/CategoryFilterSettingsCard.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AccountFilterSettingsCard,
|
||||
CategoryFilterSettingsCard
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useSettingsStore),
|
||||
allChartTypes() {
|
||||
return statisticsConstants.allChartTypes;
|
||||
},
|
||||
allChartDataTypes() {
|
||||
return this.$locale.getAllStatisticsChartDataTypes();
|
||||
},
|
||||
allSortingTypes() {
|
||||
return this.$locale.getAllStatisticsSortingTypes();
|
||||
},
|
||||
allDateRanges() {
|
||||
return this.$locale.getAllDateRanges(false);
|
||||
},
|
||||
defaultChartType: {
|
||||
get: function () {
|
||||
return this.settingsStore.appSettings.statistics.defaultChartType;
|
||||
},
|
||||
set: function (value) {
|
||||
this.settingsStore.setStatisticsDefaultChartType(value);
|
||||
}
|
||||
},
|
||||
defaultChartDataType: {
|
||||
get: function () {
|
||||
return this.settingsStore.appSettings.statistics.defaultChartDataType;
|
||||
},
|
||||
set: function (value) {
|
||||
this.settingsStore.setStatisticsDefaultChartDataType(value);
|
||||
}
|
||||
},
|
||||
defaultDateRange: {
|
||||
get: function () {
|
||||
return this.settingsStore.appSettings.statistics.defaultDataRangeType;
|
||||
},
|
||||
set: function (value) {
|
||||
this.settingsStore.setStatisticsDefaultDateRange(value);
|
||||
}
|
||||
},
|
||||
defaultSortingType: {
|
||||
get: function () {
|
||||
return this.settingsStore.appSettings.statistics.defaultSortingType;
|
||||
},
|
||||
set: function (value) {
|
||||
this.settingsStore.setStatisticsSortingType(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user