add desktop frontend framework
This commit is contained in:
Generated
+820
-26
File diff suppressed because it is too large
Load Diff
+5
-1
@@ -18,6 +18,7 @@
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/js": "^7.2.96",
|
||||
"@vuepic/vue-datepicker": "^5.1.2",
|
||||
"axios": "^1.4.0",
|
||||
"cbor-js": "^0.1.0",
|
||||
@@ -38,7 +39,10 @@
|
||||
"swiper": "^9.3.2",
|
||||
"ua-parser-js": "^1.0.35",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.2.2"
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "^4.2.2",
|
||||
"vue3-perfect-scrollbar": "^1.6.1",
|
||||
"vuetify": "^3.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.0",
|
||||
|
||||
+67
-6
@@ -1,15 +1,76 @@
|
||||
<template>
|
||||
<div></div>
|
||||
<img style="display: none;" :src="devCookiePath" v-if="!isProduction" />
|
||||
<v-app>
|
||||
<router-view />
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useTheme } from 'vuetify';
|
||||
|
||||
import { mapStores } from 'pinia';
|
||||
import { useSettingsStore } from '@/stores/setting.js';
|
||||
import { useUserStore } from '@/stores/user.js';
|
||||
import { useTokensStore } from '@/stores/token.js';
|
||||
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
|
||||
|
||||
import { loadMapAssets } from '@/lib/map/index.js';
|
||||
|
||||
export default {
|
||||
created() {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
window.location.replace('../mobile/');
|
||||
} else {
|
||||
window.location.replace('../mobile.html');
|
||||
data() {
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
isProduction: self.$settings.isProduction(),
|
||||
devCookiePath: self.$settings.isProduction() ? '' : '/dev/cookies'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useSettingsStore, useUserStore, useTokensStore, useExchangeRatesStore),
|
||||
},
|
||||
created() {
|
||||
const self = this;
|
||||
const theme = useTheme();
|
||||
|
||||
if (self.$settings.getTheme() === 'light') {
|
||||
theme.global.name.value = 'light';
|
||||
} else if (self.$settings.getTheme() === 'dark') {
|
||||
theme.global.name.value = 'dark';
|
||||
}
|
||||
|
||||
let localeDefaultSettings = self.$locale.initLocale(self.userStore.currentUserLanguage);
|
||||
self.settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
||||
|
||||
if (self.$user.isUserLogined()) {
|
||||
if (!self.$settings.isEnableApplicationLock()) {
|
||||
// refresh token if user is logined
|
||||
self.tokensStore.refreshTokenAndRevokeOldToken().then(response => {
|
||||
if (response.user && response.user.language) {
|
||||
localeDefaultSettings = self.$locale.setLanguage(response.user.language);
|
||||
self.settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
||||
}
|
||||
});
|
||||
|
||||
// auto refresh exchange rates data
|
||||
if (self.$settings.isAutoUpdateExchangeRatesData()) {
|
||||
self.exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const languageInfo = this.$locale.getCurrentLanguageInfo();
|
||||
loadMapAssets(languageInfo ? languageInfo.code : null);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/** Global style **/
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0
|
||||
}
|
||||
</style>
|
||||
|
||||
+311
-1
@@ -1,6 +1,316 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import { createI18n } from 'vue-i18n';
|
||||
|
||||
import { createVuetify } from 'vuetify';
|
||||
import { VApp } from 'vuetify/components/VApp';
|
||||
import { VAvatar } from 'vuetify/components/VAvatar';
|
||||
import { VBtn } from 'vuetify/components/VBtn';
|
||||
import { VCard, VCardActions, VCardItem, VCardSubtitle, VCardText, VCardTitle } from 'vuetify/components/VCard';
|
||||
import { VChip } from 'vuetify/components/VChip';
|
||||
import { VDialog } from 'vuetify/components/VDialog';
|
||||
import { VDivider } from 'vuetify/components/VDivider';
|
||||
import { VForm } from 'vuetify/components/VForm';
|
||||
import { VContainer, VCol, VRow, VSpacer } from 'vuetify/components/VGrid';
|
||||
import { VIcon } from 'vuetify/components/VIcon';
|
||||
import { VImg } from 'vuetify/components/VImg';
|
||||
import { VInput } from 'vuetify/components/VInput';
|
||||
import { VList, VListGroup, VListImg, VListItem, VListItemAction, VListItemMedia, VListItemSubtitle, VListItemTitle, VListSubheader } from 'vuetify/components/VList';
|
||||
import { VMenu } from 'vuetify/components/VMenu';
|
||||
import { VOverlay } from 'vuetify/components/VOverlay';
|
||||
import { VPagination } from 'vuetify/components/VPagination';
|
||||
import { VProgressCircular } from 'vuetify/components/VProgressCircular';
|
||||
import { VProgressLinear } from 'vuetify/components/VProgressLinear';
|
||||
import { VSelect } from 'vuetify/components/VSelect';
|
||||
import { VSheet } from 'vuetify/components/VSheet';
|
||||
import { VSnackbar } from 'vuetify/components/VSnackbar';
|
||||
import { VTabs } from 'vuetify/components/VTabs';
|
||||
import { VTable } from 'vuetify/components/VTable';
|
||||
import { VTextField } from 'vuetify/components/VTextField';
|
||||
import { aliases, mdi } from 'vuetify/iconsets/mdi-svg';
|
||||
import 'vuetify/styles';
|
||||
|
||||
import 'line-awesome/dist/line-awesome/css/line-awesome.css';
|
||||
|
||||
import { PerfectScrollbar } from 'vue3-perfect-scrollbar';
|
||||
import 'vue3-perfect-scrollbar/dist/vue3-perfect-scrollbar.min.css';
|
||||
|
||||
import VueDatePicker from '@vuepic/vue-datepicker';
|
||||
import '@vuepic/vue-datepicker/dist/main.css';
|
||||
|
||||
import router from '@/router/desktop.js';
|
||||
|
||||
import version from '@/lib/version.js';
|
||||
import settings from '@/lib/settings.js';
|
||||
import userstate from '@/lib/userstate.js';
|
||||
import {
|
||||
getI18nOptions,
|
||||
translateIf,
|
||||
translateError,
|
||||
i18nFunctions
|
||||
} from '@/lib/i18n.js';
|
||||
|
||||
import '@/styles/desktop/base.css';
|
||||
import '@/styles/desktop/layout.css';
|
||||
import '@/styles/desktop/font-size.css';
|
||||
import '@/styles/desktop/gap-size.css';
|
||||
import '@/styles/desktop/vuetify.css';
|
||||
import '@/styles/desktop/classess.css';
|
||||
|
||||
import App from './DesktopApp.vue';
|
||||
|
||||
const app = createApp(App);
|
||||
const pinia = createPinia();
|
||||
const i18n = createI18n(getI18nOptions());
|
||||
const vuetify = createVuetify({
|
||||
components: {
|
||||
VApp,
|
||||
VAvatar,
|
||||
VBtn,
|
||||
VCard,
|
||||
VCardActions,
|
||||
VCardItem,
|
||||
VCardSubtitle,
|
||||
VCardText,
|
||||
VCardTitle,
|
||||
VChip,
|
||||
VDialog,
|
||||
VDivider,
|
||||
VForm,
|
||||
VContainer,
|
||||
VCol,
|
||||
VRow,
|
||||
VSpacer,
|
||||
VIcon,
|
||||
VImg,
|
||||
VInput,
|
||||
VList,
|
||||
VListGroup,
|
||||
VListImg,
|
||||
VListItem,
|
||||
VListItemAction,
|
||||
VListItemMedia,
|
||||
VListItemSubtitle,
|
||||
VListItemTitle,
|
||||
VListSubheader,
|
||||
VMenu,
|
||||
VOverlay,
|
||||
VPagination,
|
||||
VProgressCircular,
|
||||
VProgressLinear,
|
||||
VSelect,
|
||||
VSheet,
|
||||
VSnackbar,
|
||||
VTabs,
|
||||
VTable,
|
||||
VTextField
|
||||
},
|
||||
icons: {
|
||||
defaultSet: 'mdi',
|
||||
aliases,
|
||||
sets: {
|
||||
mdi
|
||||
}
|
||||
},
|
||||
|
||||
defaults: {
|
||||
VAlert: {
|
||||
VBtn: {
|
||||
color: undefined
|
||||
}
|
||||
},
|
||||
VAutocomplete: {
|
||||
variant: 'outlined',
|
||||
color: 'primary',
|
||||
hideDetails: 'auto'
|
||||
},
|
||||
VAvatar: {
|
||||
variant: 'flat',
|
||||
VIcon: {
|
||||
size: 24,
|
||||
},
|
||||
},
|
||||
VBadge: {
|
||||
color: 'primary'
|
||||
},
|
||||
VBtn: {
|
||||
color: 'primary'
|
||||
},
|
||||
VCheckbox: {
|
||||
color: 'primary',
|
||||
hideDetails: 'auto'
|
||||
},
|
||||
VChip: {
|
||||
elevation: 0
|
||||
},
|
||||
VList: {
|
||||
color: 'primary'
|
||||
},
|
||||
VPagination: {
|
||||
activeColor: 'primary'
|
||||
},
|
||||
VRadio: {
|
||||
color: 'primary',
|
||||
hideDetails: 'auto'
|
||||
},
|
||||
VSelect: {
|
||||
variant: 'outlined',
|
||||
color: 'primary',
|
||||
hideDetails: 'auto'
|
||||
},
|
||||
VSlider: {
|
||||
color: 'primary',
|
||||
hideDetails: 'auto'
|
||||
},
|
||||
VSwitch: {
|
||||
color: 'primary',
|
||||
hideDetails: 'auto'
|
||||
},
|
||||
VProgressCircular: {
|
||||
size: 40
|
||||
},
|
||||
VTabs: {
|
||||
color: 'primary',
|
||||
VSlideGroup: {
|
||||
showArrows: true
|
||||
}
|
||||
},
|
||||
VTextarea: {
|
||||
variant: 'outlined',
|
||||
color: 'primary',
|
||||
hideDetails: 'auto'
|
||||
},
|
||||
VTextField: {
|
||||
variant: 'outlined',
|
||||
color: 'primary',
|
||||
hideDetails: 'auto'
|
||||
},
|
||||
VTooltip: {
|
||||
location: 'top'
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
defaultTheme: 'light',
|
||||
themes: {
|
||||
light: {
|
||||
dark: false,
|
||||
colors: {
|
||||
'primary': '#c67e48',
|
||||
'secondary': '#8a8d93',
|
||||
'on-secondary': '#fff',
|
||||
'success': '#4cd964',
|
||||
'info': '#2196f3',
|
||||
'warning': '#ff9500',
|
||||
'error': '#ff3b30',
|
||||
'on-primary': '#ffffff',
|
||||
'on-success': '#ffffff',
|
||||
'on-warning': '#ffffff',
|
||||
'background': '#faf8f4',
|
||||
'on-background': '#413935',
|
||||
'on-surface': '#413935',
|
||||
'grey-50': '#fafafa',
|
||||
'grey-100': '#f0f2f8',
|
||||
'grey-200': '#eeeeee',
|
||||
'grey-300': '#e0e0e0',
|
||||
'grey-400': '#bdbdbd',
|
||||
'grey-500': '#9e9e9e',
|
||||
'grey-600': '#757575',
|
||||
'grey-700': '#616161',
|
||||
'grey-800': '#424242',
|
||||
'grey-900': '#212121',
|
||||
'perfect-scrollbar-thumb': '#dbdade',
|
||||
'skin-bordered-background': '#fff',
|
||||
'skin-bordered-surface': '#fff'
|
||||
},
|
||||
variables: {
|
||||
'btn-height': '38px',
|
||||
'code-color': '#ff8000',
|
||||
'overlay-scrim-background': '#3a3541',
|
||||
'overlay-scrim-opacity': 0.5,
|
||||
'hover-opacity': 0.04,
|
||||
'focus-opacity': 0.1,
|
||||
'selected-opacity': 0.12,
|
||||
'activated-opacity': 0.1,
|
||||
'pressed-opacity': 0.14,
|
||||
'dragged-opacity': 0.1,
|
||||
'border-color': '#3a3541',
|
||||
'table-header-background': '#f9fafc',
|
||||
'custom-background': '#f9f8f9',
|
||||
'shadow-key-umbra-opacity': 'rgba(var(--v-theme-on-surface), 0.08)',
|
||||
'shadow-key-penumbra-opacity': 'rgba(var(--v-theme-on-surface), 0.12)',
|
||||
'shadow-key-ambient-opacity': 'rgba(var(--v-theme-on-surface), 0.04)'
|
||||
}
|
||||
},
|
||||
dark: {
|
||||
dark: true,
|
||||
colors: {
|
||||
'primary': '#c67e48',
|
||||
'secondary': '#8a8d93',
|
||||
'on-secondary': '#fff',
|
||||
'success': '#4cd964',
|
||||
'info': '#2196f3',
|
||||
'warning': '#ff9500',
|
||||
'error': '#ff3b30',
|
||||
'on-primary': '#ffffff',
|
||||
'on-success': '#ffffff',
|
||||
'on-warning': '#ffffff',
|
||||
'background': '#28243d',
|
||||
'on-background': '#fcf0e3',
|
||||
'surface': '#312d4b',
|
||||
'on-surface': '#fcf0e3',
|
||||
'grey-50': '#2a2e42',
|
||||
'grey-100': '#474360',
|
||||
'grey-200': '#4a5072',
|
||||
'grey-300': '#5e6692',
|
||||
'grey-400': '#7983bb',
|
||||
'grey-500': '#8692d0',
|
||||
'grey-600': '#aab3de',
|
||||
'grey-700': '#b6bee3',
|
||||
'grey-800': '#cfd3ec',
|
||||
'grey-900': '#e7e9f6',
|
||||
'perfect-scrollbar-thumb': '#4a5072',
|
||||
'skin-bordered-background': '#312d4b',
|
||||
'skin-bordered-surface': '#312d4b'
|
||||
},
|
||||
variables: {
|
||||
'btn-height': '38px',
|
||||
'code-color': '#ff8000',
|
||||
'overlay-scrim-background': '#2C2942',
|
||||
'overlay-scrim-opacity': 0.6,
|
||||
'hover-opacity': 0.04,
|
||||
'focus-opacity': 0.1,
|
||||
'selected-opacity': 0.12,
|
||||
'activated-opacity': 0.1,
|
||||
'pressed-opacity': 0.14,
|
||||
'dragged-opacity': 0.1,
|
||||
'border-color': '#E7E3FC',
|
||||
'table-header-background': '#3D3759',
|
||||
'custom-background': '#373452',
|
||||
'shadow-key-umbra-opacity': 'rgba(20, 18, 33, 0.08)',
|
||||
'shadow-key-penumbra-opacity': 'rgba(20, 18, 33, 0.12)',
|
||||
'shadow-key-ambient-opacity': 'rgba(20, 18, 33, 0.04)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.use(pinia);
|
||||
app.use(i18n);
|
||||
app.use(vuetify);
|
||||
app.use(router);
|
||||
|
||||
app.component('PerfectScrollbar', PerfectScrollbar);
|
||||
app.component('VueDatePicker', VueDatePicker);
|
||||
|
||||
app.config.globalProperties.$version = version.getVersion();
|
||||
app.config.globalProperties.$buildTime = version.getBuildTime();
|
||||
|
||||
app.config.globalProperties.$settings = settings;
|
||||
app.config.globalProperties.$locale = i18nFunctions(i18n.global);
|
||||
app.config.globalProperties.$tIf = (text, isTranslate) => translateIf(text, isTranslate, i18n.global.t);
|
||||
app.config.globalProperties.$tError = (message) => translateError(message, i18n.global.t);
|
||||
|
||||
app.config.globalProperties.$user = userstate;
|
||||
|
||||
app.mount('#app');
|
||||
|
||||
@@ -702,6 +702,7 @@ export default {
|
||||
'Not Specified': 'Not Specified',
|
||||
'No results': 'No results',
|
||||
'Unknown': 'Unknown',
|
||||
'Other': 'Other',
|
||||
'Default': 'Default',
|
||||
'Done': 'Done',
|
||||
'Continue': 'Continue',
|
||||
@@ -746,6 +747,7 @@ export default {
|
||||
'Accounts': 'Accounts',
|
||||
'Statistics': 'Statistics',
|
||||
'Settings': 'Settings',
|
||||
'Application Settings': 'Application Settings',
|
||||
'Select All': 'Select All',
|
||||
'Select None': 'Select None',
|
||||
'Invert Selection': 'Invert Selection',
|
||||
@@ -806,6 +808,7 @@ export default {
|
||||
'PIN code is invalid': 'PIN code is invalid',
|
||||
'PIN code is wrong': 'PIN code is wrong',
|
||||
'Sign Up': 'Sign Up',
|
||||
'Overview': 'Overview',
|
||||
'Transaction List': 'Transaction List',
|
||||
'Account List': 'Account List',
|
||||
'This Week': 'This Week',
|
||||
@@ -915,6 +918,8 @@ export default {
|
||||
'No transaction data': 'No transaction data',
|
||||
'Are you sure you want to delete this transaction?': 'Are you sure you want to delete this transaction?',
|
||||
'Unable to delete this transaction': 'Unable to delete this transaction',
|
||||
'Transaction Data': 'Transaction Data',
|
||||
'Statistics Data': 'Statistics Data',
|
||||
'Unable to get transaction statistics': 'Unable to get transaction statistics',
|
||||
'Total Amount': 'Total Amount',
|
||||
'Total Assets': 'Total Assets',
|
||||
|
||||
@@ -702,6 +702,7 @@ export default {
|
||||
'Not Specified': '未指定',
|
||||
'No results': '无结果',
|
||||
'Unknown': '未知',
|
||||
'Other': '其他',
|
||||
'Default': '默认',
|
||||
'Done': '完成',
|
||||
'Continue': '继续',
|
||||
@@ -746,6 +747,7 @@ export default {
|
||||
'Accounts': '账户',
|
||||
'Statistics': '统计',
|
||||
'Settings': '设置',
|
||||
'Application Settings': '应用设置',
|
||||
'Select All': '全部选择',
|
||||
'Select None': '全部不选',
|
||||
'Invert Selection': '反向选择',
|
||||
@@ -806,6 +808,7 @@ export default {
|
||||
'PIN code is invalid': 'PIN码无效',
|
||||
'PIN code is wrong': 'PIN码错误',
|
||||
'Sign Up': '注册',
|
||||
'Overview': '总览',
|
||||
'Transaction List': '交易列表',
|
||||
'Account List': '账户列表',
|
||||
'This Week': '本周',
|
||||
@@ -915,6 +918,8 @@ export default {
|
||||
'No transaction data': '没有交易数据',
|
||||
'Are you sure you want to delete this transaction?': '您确定要删除该交易?',
|
||||
'Unable to delete this transaction': '无法删除该交易',
|
||||
'Transaction Data': '交易数据',
|
||||
'Statistics Data': '统计数据',
|
||||
'Unable to get transaction statistics': '无法获取交易统计数据',
|
||||
'Total Amount': '总金额',
|
||||
'Total Assets': '总资产',
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router';
|
||||
|
||||
import userState from '@/lib/userstate.js';
|
||||
|
||||
import MainLayout from '@/views/desktop/MainLayout.vue';
|
||||
import LoginPage from '@/views/desktop/LoginPage.vue';
|
||||
import SignUpPage from '@/views/desktop/SignupPage.vue';
|
||||
import UnlockPage from '@/views/desktop/UnlockPage.vue';
|
||||
|
||||
import HomePage from '@/views/desktop/HomePage.vue';
|
||||
import TransactionsPage from '@/views/desktop/TransactionsPage.vue';
|
||||
import StatisticsTransactionPage from '@/views/desktop/statistics/TransactionPage.vue';
|
||||
import AccountsPage from '@/views/desktop/AccountsPage.vue';
|
||||
import TransactionCategoriesPage from '@/views/desktop/TransactionCategoriesPage.vue';
|
||||
import TransactionTagsPage from '@/views/desktop/TransactionTagsPage.vue';
|
||||
import ExchangeRatesPage from '@/views/desktop/ExchangeRatesPage.vue';
|
||||
import UserSettingsPage from '@/views/desktop/user/UserSettingsPage.vue';
|
||||
import AppSettingsPage from '@/views/desktop/app/AppSettingsPage.vue';
|
||||
import AboutPage from '@/views/desktop/AboutPage.vue';
|
||||
|
||||
function checkLogin() {
|
||||
if (!userState.isUserLogined()) {
|
||||
return {
|
||||
path: '/login',
|
||||
replace: true
|
||||
};
|
||||
}
|
||||
|
||||
if (!userState.isUserUnlocked()) {
|
||||
return {
|
||||
path: '/unlock',
|
||||
replace: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function checkLocked() {
|
||||
if (!userState.isUserLogined()) {
|
||||
return {
|
||||
path: '/login',
|
||||
replace: true
|
||||
};
|
||||
}
|
||||
|
||||
if (userState.isUserUnlocked()) {
|
||||
return {
|
||||
path: '/',
|
||||
replace: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function checkNotLogin() {
|
||||
if (userState.isUserLogined() && !userState.isUserUnlocked()) {
|
||||
return {
|
||||
path: '/unlock',
|
||||
replace: true
|
||||
};
|
||||
}
|
||||
|
||||
if (userState.isUserLogined()) {
|
||||
return {
|
||||
path: '/',
|
||||
replace: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
component: MainLayout,
|
||||
beforeEnter: checkLogin,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: HomePage
|
||||
},
|
||||
{
|
||||
path: '/transactions',
|
||||
component: TransactionsPage
|
||||
},
|
||||
{
|
||||
path: '/statistics/transaction',
|
||||
component: StatisticsTransactionPage
|
||||
},
|
||||
{
|
||||
path: '/accounts',
|
||||
component: AccountsPage
|
||||
},
|
||||
{
|
||||
path: '/categories',
|
||||
component: TransactionCategoriesPage
|
||||
},
|
||||
{
|
||||
path: '/tags',
|
||||
component: TransactionTagsPage
|
||||
},
|
||||
{
|
||||
path: '/exchange_rates',
|
||||
component: ExchangeRatesPage
|
||||
},
|
||||
{
|
||||
path: '/user/settings',
|
||||
component: UserSettingsPage
|
||||
},
|
||||
{
|
||||
path: '/app/settings',
|
||||
component: AppSettingsPage
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
component: AboutPage
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
component: LoginPage,
|
||||
beforeEnter: checkNotLogin
|
||||
},
|
||||
{
|
||||
path: '/signup',
|
||||
component: LoginPage,
|
||||
beforeEnter: SignUpPage
|
||||
},
|
||||
{
|
||||
path: '/unlock',
|
||||
component: UnlockPage,
|
||||
beforeEnter: checkLocked
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,42 @@
|
||||
/** Base class **/
|
||||
/** reference: https://github.com/themeselection/materio-vuetify-vuejs-admin-template-free **/
|
||||
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: inherit;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:root {
|
||||
--v-theme-overlay-multiplier: 1;
|
||||
--v-scrollbar-offset: 0px;
|
||||
}
|
||||
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: inter,sans-serif,-apple-system,blinkmacsystemfont,Segoe UI,roboto,Helvetica Neue,arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol;
|
||||
line-height: 1.5;
|
||||
font-size: 1rem;
|
||||
overflow-x: hidden;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
html.overflow-y-hidden {
|
||||
overflow-y: hidden!important;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgb(var(--v-theme-primary));
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-block-end: 1rem;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
.disabled {
|
||||
opacity: 0.55 !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
.readonly {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/** Text size class **/
|
||||
/** reference: https://github.com/themeselection/materio-vuetify-vuejs-admin-template-free **/
|
||||
|
||||
.text-xs {
|
||||
font-size: .75rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: .875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.text-base {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.text-2xl {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
.text-3xl {
|
||||
font-size: 1.875rem;
|
||||
line-height: 2.25rem;
|
||||
}
|
||||
|
||||
.text-4xl {
|
||||
font-size: 2.25rem;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
|
||||
.text-5xl {
|
||||
font-size: 3rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.text-6xl {
|
||||
font-size: 3.75rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.text-7xl {
|
||||
font-size: 4.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.text-8xl {
|
||||
font-size: 6rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.text-9xl {
|
||||
font-size: 8rem;
|
||||
line-height: 1;
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
/** Gap size class **/
|
||||
/** reference: https://github.com/themeselection/materio-vuetify-vuejs-admin-template-free **/
|
||||
|
||||
.gap-0 {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.gap-x-0 {
|
||||
column-gap: 0;
|
||||
}
|
||||
|
||||
.gap-y-0 {
|
||||
row-gap: 0;
|
||||
}
|
||||
|
||||
.gap-1 {
|
||||
gap: .25rem;
|
||||
}
|
||||
|
||||
.gap-x-1 {
|
||||
column-gap: .25rem;
|
||||
}
|
||||
|
||||
.gap-y-1 {
|
||||
row-gap: .25rem;
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: .5rem;
|
||||
}
|
||||
|
||||
.gap-x-2 {
|
||||
column-gap: .5rem;
|
||||
}
|
||||
|
||||
.gap-y-2 {
|
||||
row-gap: .5rem;
|
||||
}
|
||||
|
||||
.gap-3 {
|
||||
gap: .75rem;
|
||||
}
|
||||
|
||||
.gap-x-3 {
|
||||
column-gap: .75rem;
|
||||
}
|
||||
|
||||
.gap-y-3 {
|
||||
row-gap: .75rem;
|
||||
}
|
||||
|
||||
.gap-4 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.gap-x-4 {
|
||||
column-gap: 1rem;
|
||||
}
|
||||
|
||||
.gap-y-4 {
|
||||
row-gap: 1rem;
|
||||
}
|
||||
|
||||
.gap-5 {
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.gap-x-5 {
|
||||
column-gap: 1.25rem;
|
||||
}
|
||||
|
||||
.gap-y-5 {
|
||||
row-gap: 1.25rem;
|
||||
}
|
||||
|
||||
.gap-6 {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.gap-x-6 {
|
||||
column-gap: 1.5rem;
|
||||
}
|
||||
|
||||
.gap-y-6 {
|
||||
row-gap: 1.5rem;
|
||||
}
|
||||
|
||||
.gap-7 {
|
||||
gap: 1.75rem;
|
||||
}
|
||||
|
||||
.gap-x-7 {
|
||||
column-gap: 1.75rem;
|
||||
}
|
||||
|
||||
.gap-y-7 {
|
||||
row-gap: 1.75rem;
|
||||
}
|
||||
|
||||
.gap-8 {
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.gap-x-8 {
|
||||
column-gap: 2rem;
|
||||
}
|
||||
|
||||
.gap-y-8 {
|
||||
row-gap: 2rem;
|
||||
}
|
||||
|
||||
.gap-9 {
|
||||
gap: 2.25rem;
|
||||
}
|
||||
|
||||
.gap-x-9 {
|
||||
column-gap: 2.25rem;
|
||||
}
|
||||
|
||||
.gap-y-9 {
|
||||
row-gap: 2.25rem;
|
||||
}
|
||||
|
||||
.gap-10 {
|
||||
gap: 2.5rem;
|
||||
}
|
||||
|
||||
.gap-x-10 {
|
||||
column-gap: 2.5rem;
|
||||
}
|
||||
|
||||
.gap-y-10 {
|
||||
row-gap: 2.5rem;
|
||||
}
|
||||
|
||||
.gap-11 {
|
||||
gap: 2.75rem;
|
||||
}
|
||||
|
||||
.gap-x-11 {
|
||||
column-gap: 2.75rem;
|
||||
}
|
||||
|
||||
.gap-y-11 {
|
||||
row-gap: 2.75rem;
|
||||
}
|
||||
|
||||
.gap-12 {
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
.gap-x-12 {
|
||||
column-gap: 3rem;
|
||||
}
|
||||
|
||||
.gap-y-12 {
|
||||
row-gap: 3rem;
|
||||
}
|
||||
|
||||
.gap-14 {
|
||||
gap: 3.5rem;
|
||||
}
|
||||
|
||||
.gap-x-14 {
|
||||
column-gap: 3.5rem;
|
||||
}
|
||||
|
||||
.gap-y-14 {
|
||||
row-gap: 3.5rem;
|
||||
}
|
||||
|
||||
.gap-16 {
|
||||
gap: 4rem;
|
||||
}
|
||||
|
||||
.gap-x-16 {
|
||||
column-gap: 4rem;
|
||||
}
|
||||
|
||||
.gap-y-16 {
|
||||
row-gap: 4rem;
|
||||
}
|
||||
|
||||
.gap-20 {
|
||||
gap: 5rem;
|
||||
}
|
||||
|
||||
.gap-x-20 {
|
||||
column-gap: 5rem;
|
||||
}
|
||||
|
||||
.gap-y-20 {
|
||||
row-gap: 5rem;
|
||||
}
|
||||
|
||||
.gap-24 {
|
||||
gap: 6rem;
|
||||
}
|
||||
|
||||
.gap-x-24 {
|
||||
column-gap: 6rem;
|
||||
}
|
||||
|
||||
.gap-y-24 {
|
||||
row-gap: 6rem;
|
||||
}
|
||||
|
||||
.gap-28 {
|
||||
gap: 7rem;
|
||||
}
|
||||
|
||||
.gap-x-28 {
|
||||
column-gap: 7rem;
|
||||
}
|
||||
|
||||
.gap-y-28 {
|
||||
row-gap: 7rem;
|
||||
}
|
||||
|
||||
.gap-32 {
|
||||
gap: 8rem;
|
||||
}
|
||||
|
||||
.gap-x-32 {
|
||||
column-gap: 8rem;
|
||||
}
|
||||
|
||||
.gap-y-32 {
|
||||
row-gap: 8rem;
|
||||
}
|
||||
|
||||
.gap-36 {
|
||||
gap: 9rem;
|
||||
}
|
||||
|
||||
.gap-x-36 {
|
||||
column-gap: 9rem;
|
||||
}
|
||||
|
||||
.gap-y-36 {
|
||||
row-gap: 9rem;
|
||||
}
|
||||
|
||||
.gap-40 {
|
||||
gap: 10rem;
|
||||
}
|
||||
|
||||
.gap-x-40 {
|
||||
column-gap: 10rem;
|
||||
}
|
||||
|
||||
.gap-y-40 {
|
||||
row-gap: 10rem;
|
||||
}
|
||||
|
||||
.gap-44 {
|
||||
gap: 11rem;
|
||||
}
|
||||
|
||||
.gap-x-44 {
|
||||
column-gap: 11rem;
|
||||
}
|
||||
|
||||
.gap-y-44 {
|
||||
row-gap: 11rem;
|
||||
}
|
||||
|
||||
.gap-48 {
|
||||
gap: 12rem;
|
||||
}
|
||||
|
||||
.gap-x-48 {
|
||||
column-gap: 12rem;
|
||||
}
|
||||
|
||||
.gap-y-48 {
|
||||
row-gap: 12rem;
|
||||
}
|
||||
|
||||
.gap-52 {
|
||||
gap: 13rem;
|
||||
}
|
||||
|
||||
.gap-x-52 {
|
||||
column-gap: 13rem;
|
||||
}
|
||||
|
||||
.gap-y-52 {
|
||||
row-gap: 13rem;
|
||||
}
|
||||
|
||||
.gap-56 {
|
||||
gap: 14rem;
|
||||
}
|
||||
|
||||
.gap-x-56 {
|
||||
column-gap: 14rem;
|
||||
}
|
||||
|
||||
.gap-y-56 {
|
||||
row-gap: 14rem;
|
||||
}
|
||||
|
||||
.gap-60 {
|
||||
gap: 15rem;
|
||||
}
|
||||
|
||||
.gap-x-60 {
|
||||
column-gap: 15rem;
|
||||
}
|
||||
|
||||
.gap-y-60 {
|
||||
row-gap: 15rem;
|
||||
}
|
||||
|
||||
.gap-64 {
|
||||
gap: 16rem;
|
||||
}
|
||||
|
||||
.gap-x-64 {
|
||||
column-gap: 16rem;
|
||||
}
|
||||
|
||||
.gap-y-64 {
|
||||
row-gap: 16rem;
|
||||
}
|
||||
|
||||
.gap-72 {
|
||||
gap: 18rem;
|
||||
}
|
||||
|
||||
.gap-x-72 {
|
||||
column-gap: 18rem;
|
||||
}
|
||||
|
||||
.gap-y-72 {
|
||||
row-gap: 18rem;
|
||||
}
|
||||
|
||||
.gap-80 {
|
||||
gap: 20rem;
|
||||
}
|
||||
|
||||
.gap-x-80 {
|
||||
column-gap: 20rem;
|
||||
}
|
||||
|
||||
.gap-y-80 {
|
||||
row-gap: 20rem;
|
||||
}
|
||||
|
||||
.gap-96 {
|
||||
gap: 24rem;
|
||||
}
|
||||
|
||||
.gap-x-96 {
|
||||
column-gap: 24rem;
|
||||
}
|
||||
|
||||
.gap-y-96 {
|
||||
row-gap: 24rem;
|
||||
}
|
||||
@@ -0,0 +1,544 @@
|
||||
/** Layout class **/
|
||||
/** reference: https://github.com/themeselection/materio-vuetify-vuejs-admin-template-free **/
|
||||
|
||||
html,
|
||||
body {
|
||||
min-block-size: 100%;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-section-title,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-link > :first-child,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group > :first-child {
|
||||
margin-block: 0;
|
||||
margin-inline: 0 1.125rem;
|
||||
padding-block: 0;
|
||||
padding-inline: 1.375rem 1rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-link > :first-child,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group > :first-child {
|
||||
border-radius: .4rem;
|
||||
block-size: 2.75rem;
|
||||
margin-block-end: .375rem;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-link .nav-item-icon,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group .nav-item-icon {
|
||||
flex-shrink: 0;
|
||||
font-size: 1.5rem;
|
||||
margin-inline-end: .625rem;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group .nav-group .nav-item-icon,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group .nav-link .nav-item-icon {
|
||||
font-size: .9rem;
|
||||
margin-inline-end: .925rem;
|
||||
margin-inline-start: .3rem;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group .nav-group .nav-link .nav-item-icon,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group .nav-group .nav-group .nav-item-icon {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group.active > :first-child:before,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group.open > :first-child:before {
|
||||
opacity: calc(var(--v-selected-opacity) * var(--v-theme-overlay-multiplier));
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group.active > :hover:first-child .nav-group.active > :first-child:before,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group.open > :hover:first-child .nav-group.active > :first-child:before,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group.active > :hover:first-child .nav-group.open > :first-child:before,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group.open > :hover:first-child .nav-group.open > :first-child:before {
|
||||
opacity: calc(var(--v-selected-opacity) + var(--v-hover-opacity) * var(--v-theme-overlay-multiplier));
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group.active > :focus-visible:first-child .nav-group.active > :first-child:before,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group.open > :focus-visible:first-child .nav-group.active > :first-child:before,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group.active > :focus-visible:first-child .nav-group.open > :first-child:before,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group.open > :focus-visible:first-child .nav-group.open > :first-child:before {
|
||||
opacity: calc(var(--v-selected-opacity) + var(--v-focus-opacity) * var(--v-theme-overlay-multiplier));
|
||||
}
|
||||
|
||||
@supports not selector(:focus-visible) {
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group.active > :focus:first-child:before,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group.open > :focus:first-child:before {
|
||||
opacity: calc(var(--v-selected-opacity) + var(--v-focus-opacity) * var(--v-theme-overlay-multiplier));
|
||||
}
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-section-title {
|
||||
block-size: 1.5rem;
|
||||
color: rgba(var(--v-theme-on-surface), var(--v-disabled-opacity));
|
||||
font-size: .75rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-item-badge {
|
||||
display: inline-block;
|
||||
border-radius: 1.5rem;
|
||||
font-size: .8em;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
padding-block: .25em;
|
||||
padding-inline: .55em;
|
||||
text-align: center;
|
||||
vertical-align: baseline;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav {
|
||||
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-item-title {
|
||||
letter-spacing: .15px;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-section-title {
|
||||
letter-spacing: .4px;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-link > .router-link-exact-active {
|
||||
background-color: rgb(var(--v-theme-primary));
|
||||
color: rgb(var(--v-theme-on-primary));
|
||||
box-shadow: 0 4px 14px -4px var(--v-shadow-key-umbra-opacity), 0 4px 8px -4px var(--v-shadow-key-penumbra-opacity), 0 4px 8px -4px var(--v-shadow-key-ambient-opacity);
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-link a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.layout-navbar {
|
||||
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
|
||||
}
|
||||
|
||||
html.v-overlay-scroll-blocked:not([style*="--v-body-scroll-y: 0px;"]) .layout-navbar-sticky .navbar-blur.layout-navbar .navbar-content-container,
|
||||
.layout-wrapper.layout-nav-type-vertical.window-scrolled.layout-navbar-sticky .navbar-blur.layout-navbar .navbar-content-container {
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
backdrop-filter: blur(6px);
|
||||
background-color: rgb(var(--v-theme-surface), .9);
|
||||
}
|
||||
|
||||
html.v-overlay-scroll-blocked:not([style*="--v-body-scroll-y: 0px;"]) .layout-navbar-sticky .layout-navbar .navbar-content-container,
|
||||
.layout-wrapper.layout-nav-type-vertical.window-scrolled.layout-navbar-sticky .layout-navbar .navbar-content-container {
|
||||
background-color: rgb(var(--v-theme-surface));
|
||||
}
|
||||
|
||||
html.v-overlay-scroll-blocked:not([style*="--v-body-scroll-y: 0px;"]) .layout-navbar-sticky .layout-navbar .navbar-content-container,
|
||||
.layout-wrapper.layout-nav-type-vertical.window-scrolled.layout-navbar-sticky .layout-navbar .navbar-content-container {
|
||||
box-shadow: 0 4px 14px -4px var(--v-shadow-key-umbra-opacity), 0 4px 8px -4px var(--v-shadow-key-penumbra-opacity), 0 4px 8px -4px var(--v-shadow-key-ambient-opacity);
|
||||
padding-inline: 1.2rem;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-link > .router-link-exact-active {
|
||||
background: linear-gradient(-72.47deg, rgb(var(--v-theme-primary)) 22.16%, rgba(var(--v-theme-primary), .7) 76.47%) !important;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-section-title .title-text {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
column-gap: .625rem;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-section-title .title-text:before,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-section-title .title-text:after {
|
||||
border-block-end: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||
content: "";
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-section-title .title-text:after {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-section-title .title-text:before {
|
||||
flex: 0 1 .75rem;
|
||||
margin-inline-start: -1.375rem;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical.layout-vertical-nav-collapsed .layout-vertical-nav:not(.hovered) .nav-section-title {
|
||||
margin-inline: 4px 0;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-link > :first-child,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group > :first-child {
|
||||
block-size: 2.625rem !important;
|
||||
border-end-end-radius: 3.125rem !important;
|
||||
border-end-start-radius: 0 !important;
|
||||
border-start-end-radius: 3.125rem !important;
|
||||
border-start-start-radius: 0 !important;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-link > :first-child,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group > :first-child {
|
||||
transition: margin-inline .15s ease-in-out;
|
||||
will-change: margin-inline;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical.layout-vertical-nav-collapsed .layout-vertical-nav:not(.hovered) .nav-link > :first-child,
|
||||
.layout-nav-type-vertical.layout-vertical-nav-collapsed .layout-vertical-nav:not(.hovered) .nav-group > :first-child {
|
||||
margin-inline: 0 5px;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav {
|
||||
background-color: rgb(var(--v-theme-background));
|
||||
}
|
||||
|
||||
.layout-vertical-nav-collapsed.layout-nav-type-vertical .layout-vertical-nav.hovered {
|
||||
box-shadow: 0 4px 5px -2px var(--v-shadow-key-umbra-opacity), 0 2px 10px 1px var(--v-shadow-key-penumbra-opacity), 0 2px 16px 1px var(--v-shadow-key-ambient-opacity);
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-header {
|
||||
overflow: hidden;
|
||||
padding: 1rem .25rem 1rem 1.375rem;
|
||||
margin-inline: 0 1.125rem;
|
||||
min-block-size: 64px;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-header .app-logo {
|
||||
flex-shrink: 0;
|
||||
transition: transform .25s ease-in-out;
|
||||
}
|
||||
|
||||
.layout-vertical-nav-collapsed.layout-nav-type-vertical .layout-vertical-nav:not(.hovered) .nav-header .app-logo {
|
||||
transform: translate(-4px);
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-header .app-title {
|
||||
margin-inline-start: .9rem;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .vertical-nav-items-shadow {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
background: linear-gradient(rgb(var(--v-theme-background)) 5%, rgba(var(--v-theme-background), 75%) 45%, rgba(var(--v-theme-background), 20%) 80%, transparent);
|
||||
block-size: 55px;
|
||||
inline-size: 100%;
|
||||
inset-block-start: 62px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity .15s ease-in-out;
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav.scrolled .vertical-nav-items-shadow {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .ps__rail-y {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-section-title {
|
||||
margin-block-end: .5rem;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-section-title:not(:first-child) {
|
||||
margin-block-start: 1.5rem;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-section-title .placeholder-icon {
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-link,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-link.disabled,
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group.disabled {
|
||||
opacity: var(--v-disabled-opacity);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-link > a {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-link > a:before {
|
||||
position: absolute;
|
||||
border-radius: inherit;
|
||||
background: currentcolor;
|
||||
block-size: 100%;
|
||||
content: "";
|
||||
inline-size: 100%;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-link > a:hover:before {
|
||||
opacity: calc(var(--v-hover-opacity) * var(--v-theme-overlay-multiplier));
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-link > a:focus-visible:before {
|
||||
opacity: calc(var(--v-focus-opacity) * var(--v-theme-overlay-multiplier));
|
||||
}
|
||||
|
||||
@supports not selector(:focus-visible) {
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-link > a:focus:before {
|
||||
opacity: calc(var(--v-focus-opacity) * var(--v-theme-overlay-multiplier));
|
||||
}
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group .nav-group-arrow {
|
||||
flex-shrink: 0;
|
||||
transform-origin: center;
|
||||
transition: transform .15s ease-in-out;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group.open > .nav-group-label .nav-group-arrow {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group > :first-child {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group > :first-child:before {
|
||||
position: absolute;
|
||||
border-radius: inherit;
|
||||
background: currentcolor;
|
||||
block-size: 100%;
|
||||
content: "";
|
||||
inline-size: 100%;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group > :first-child:hover:before {
|
||||
opacity: calc(var(--v-hover-opacity) * var(--v-theme-overlay-multiplier));
|
||||
}
|
||||
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group > :first-child:focus-visible:before {
|
||||
opacity: calc(var(--v-focus-opacity) * var(--v-theme-overlay-multiplier));
|
||||
}
|
||||
|
||||
@supports not selector(:focus-visible) {
|
||||
.layout-nav-type-vertical .layout-vertical-nav .nav-group > :first-child:focus:before {
|
||||
opacity: calc(var(--v-focus-opacity) * var(--v-theme-overlay-multiplier));
|
||||
}
|
||||
}
|
||||
|
||||
.vertical-nav-section-title-enter-active,
|
||||
.vertical-nav-section-title-leave-active {
|
||||
transition: opacity .1s ease-in-out, transform .1s ease-in-out;
|
||||
}
|
||||
|
||||
.vertical-nav-section-title-enter-from,
|
||||
.vertical-nav-section-title-leave-to {
|
||||
opacity: 0;
|
||||
transform: translate(15px);
|
||||
}
|
||||
|
||||
.transition-slide-x-enter-active,
|
||||
.transition-slide-x-leave-active {
|
||||
transition: opacity .1s ease-in-out, transform .12s ease-in-out;
|
||||
}
|
||||
|
||||
.transition-slide-x-enter-from,
|
||||
.transition-slide-x-leave-to {
|
||||
opacity: 0;
|
||||
transform: translate(-15px);
|
||||
}
|
||||
|
||||
.vertical-nav-app-title-enter-active,
|
||||
.vertical-nav-app-title-leave-active {
|
||||
transition: opacity .1s ease-in-out, transform .12s ease-in-out;
|
||||
}
|
||||
|
||||
.vertical-nav-app-title-enter-from,
|
||||
.vertical-nav-app-title-leave-to {
|
||||
opacity: 0;
|
||||
transform: translate(-15px);
|
||||
}
|
||||
|
||||
.layout-vertical-nav ol, .layout-vertical-nav ul,
|
||||
.layout-horizontal-nav ol, .layout-horizontal-nav ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.scrollable-content.v-navigation-drawer .v-navigation-drawer__content {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical .layout-navbar .navbar-content-container {
|
||||
transition: padding .2s ease, background-color .18s ease;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical .layout-navbar .navbar-content-container {
|
||||
border-radius: 0 0 10px 10px;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical .layout-footer .footer-content-container {
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
|
||||
.layout-footer-sticky.layout-wrapper.layout-nav-type-vertical .layout-footer .footer-content-container {
|
||||
background-color: rgb(var(--v-theme-surface));
|
||||
padding-block: 0;
|
||||
padding-inline: 1.2rem;
|
||||
box-shadow: 0 4px 14px -4px var(--v-shadow-key-umbra-opacity), 0 4px 8px -4px var(--v-shadow-key-penumbra-opacity), 0 4px 8px -4px var(--v-shadow-key-ambient-opacity);
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical.layout-content-height-fixed .page-content-container > .v-layout:first-child {
|
||||
overflow: hidden;
|
||||
min-block-size: 100%;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical.layout-content-height-fixed .page-content-container > .v-layout:first-child > .v-main .v-main__wrap > :first-child {
|
||||
block-size: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-horizontal.layout-content-height-fixed > .layout-page-content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.layout-vertical-nav {
|
||||
position: fixed;
|
||||
z-index: 1004;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
block-size: 100%;
|
||||
inline-size: 260px;
|
||||
inset-block-start: 0;
|
||||
inset-inline-start: 0;
|
||||
transition: transform .25s ease-in-out, inline-size .25s ease-in-out, box-shadow .25s ease-in-out;
|
||||
will-change: transform, inline-size;
|
||||
}
|
||||
|
||||
.layout-vertical-nav .nav-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.layout-vertical-nav .app-title-wrapper {
|
||||
margin-inline-end: auto;
|
||||
}
|
||||
|
||||
.layout-vertical-nav .nav-items {
|
||||
block-size: 100%;
|
||||
}
|
||||
|
||||
.layout-vertical-nav .nav-item-title {
|
||||
overflow: hidden;
|
||||
margin-inline-end: auto;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.layout-vertical-nav-collapsed .layout-vertical-nav:not(.hovered) {
|
||||
inline-size: 68px
|
||||
}
|
||||
|
||||
.layout-vertical-nav.overlay-nav:not(.visible) {
|
||||
transform: translate(-260px);
|
||||
}
|
||||
|
||||
.layout-content-width-boxed.layout-wrapper.layout-nav-type-vertical .layout-navbar {
|
||||
inline-size: 100%;
|
||||
margin-inline: auto;
|
||||
max-inline-size: 1440px;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical .layout-navbar {
|
||||
padding-inline: 1.5rem;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical.layout-navbar-hidden .layout-navbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical.layout-navbar-sticky .layout-navbar {
|
||||
position: sticky;
|
||||
inset-block-start: 0;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical {
|
||||
block-size: 100%;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical .layout-content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
min-block-size: calc(var(--vh, 1vh) * 100);
|
||||
transition: padding-inline-start .2s ease-in-out;
|
||||
will-change: padding-inline-start;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical .layout-navbar {
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical .layout-navbar .navbar-content-container {
|
||||
block-size: 64px;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical .layout-overlay {
|
||||
position: fixed;
|
||||
z-index: 1003;
|
||||
background-color: #0009;
|
||||
cursor: pointer;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity .25s ease-in-out;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical .layout-overlay.visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical:not(.layout-overlay-nav) .layout-content-wrapper {
|
||||
padding-inline-start: 260px;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical.layout-vertical-nav-collapsed .layout-content-wrapper {
|
||||
padding-inline-start: 68px;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical.layout-content-height-fixed .layout-content-wrapper {
|
||||
max-block-size: calc(var(--vh) * 100);
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical.layout-content-height-fixed .layout-page-content {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical.layout-content-height-fixed .layout-page-content .page-content-container {
|
||||
inline-size: 100%;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-nav-type-vertical.layout-content-height-fixed .layout-page-content .page-content-container > :first-child {
|
||||
max-block-size: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.layout-vertical-nav .nav-link a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.layout-page-content {
|
||||
flex-grow: 1;
|
||||
padding-block: 1.5rem;
|
||||
}
|
||||
|
||||
.layout-page-content {
|
||||
padding-inline: 1.5rem;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
/** Vuetify class overrides **/
|
||||
/** reference: https://github.com/themeselection/materio-vuetify-vuejs-admin-template-free **/
|
||||
|
||||
.v-application__wrap {
|
||||
min-height: calc(var(--vh, 1vh) * 100);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
.text-h1,
|
||||
.text-h2,
|
||||
.text-h3,
|
||||
.text-h4,
|
||||
.text-h5,
|
||||
.text-h6,
|
||||
.text-button,
|
||||
.text-overline,
|
||||
.v-card-title {
|
||||
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
|
||||
}
|
||||
|
||||
.v-application,
|
||||
.text-body-1,
|
||||
.text-body-2,
|
||||
.text-subtitle-1,
|
||||
.text-subtitle-2 {
|
||||
color: rgba(var(--v-theme-on-background), var(--v-medium-emphasis-opacity));
|
||||
}
|
||||
|
||||
.v-row .v-col .v-input__details,
|
||||
.v-row [class^="v-col-*"] .v-input__details {
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
.v-btn--density-compact.v-btn--size-default .v-btn__content > svg {
|
||||
block-size: 22px;
|
||||
font-size: 22px;
|
||||
inline-size: 22px;
|
||||
}
|
||||
|
||||
.v-card-text + .v-card-text {
|
||||
padding-block-start: 0 !important;
|
||||
}
|
||||
|
||||
.v-checkbox.v-input,
|
||||
.v-switch.v-input {
|
||||
--v-input-control-height: auto;
|
||||
flex: unset;
|
||||
}
|
||||
|
||||
.v-selection-control--density-comfortable.v-checkbox-btn .v-selection-control__wrapper,
|
||||
.v-selection-control--density-comfortable.v-radio .v-selection-control__wrapper,
|
||||
.v-selection-control--density-comfortable.v-radio-btn .v-selection-control__wrapper {
|
||||
margin-inline-start: -.5625rem;
|
||||
}
|
||||
|
||||
.v-selection-control--density-compact.v-radio .v-selection-control__wrapper,
|
||||
.v-selection-control--density-compact.v-radio-btn .v-selection-control__wrapper,
|
||||
.v-selection-control--density-compact.v-checkbox-btn .v-selection-control__wrapper {
|
||||
margin-inline-start: -.3125rem;
|
||||
}
|
||||
|
||||
.v-selection-control--density-default.v-checkbox-btn .v-selection-control__wrapper,
|
||||
.v-selection-control--density-default.v-radio .v-selection-control__wrapper,
|
||||
.v-selection-control--density-default.v-radio-btn .v-selection-control__wrapper {
|
||||
margin-inline-start: -.6875rem;
|
||||
}
|
||||
|
||||
.v-radio-group .v-selection-control-group .v-radio:not(:last-child) {
|
||||
margin-inline-end: .9rem;
|
||||
}
|
||||
|
||||
.disable-tab-transition {
|
||||
overflow: unset !important;
|
||||
}
|
||||
|
||||
.disable-tab-transition .v-window__container {
|
||||
block-size: auto !important;
|
||||
}
|
||||
|
||||
.disable-tab-transition .v-window-item:not(.v-window-item--active) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.disable-tab-transition .v-window__container .v-window-item {
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.v-list .v-list-item__prepend > .v-icon,
|
||||
.v-list .v-list-item__append > .v-icon {
|
||||
opacity: var(--v-high-emphasis-opacity);
|
||||
}
|
||||
|
||||
.card-list {
|
||||
--v-card-list-gap: 20px;
|
||||
}
|
||||
|
||||
.card-list.v-list {
|
||||
padding-block: 0;
|
||||
}
|
||||
|
||||
.card-list .v-list-item {
|
||||
min-block-size: unset;
|
||||
min-block-size: auto !important;
|
||||
padding-block: 0 !important;
|
||||
padding-inline: 0 !important;
|
||||
}
|
||||
|
||||
.card-list .v-list-item > .v-ripple__container {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.card-list .v-list-item:not(:last-child) {
|
||||
padding-block-end: var(--v-card-list-gap) !important;
|
||||
}
|
||||
|
||||
.card-list .v-list-item: hover > .v-list-item__overlay,
|
||||
.card-list .v-list-item:focus > .v-list-item__overlay,
|
||||
.card-list .v-list-item:active > .v-list-item__overlay,
|
||||
.card-list .v-list-item.active > .v-list-item__overlay {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
.v-divider {
|
||||
color: rgb(var(--v-border-color));
|
||||
}
|
||||
|
||||
.v-data-table .v-checkbox-btn .v-selection-control__wrapper {
|
||||
margin-inline-start: 0 !important;
|
||||
}
|
||||
|
||||
.v-data-table .v-selection-control {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.v-data-table .v-pagination {
|
||||
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
|
||||
}
|
||||
|
||||
.v-data-table-footer {
|
||||
margin-block-start: 1rem;
|
||||
}
|
||||
|
||||
.v-field:hover .v-field__outline {
|
||||
--v-field-border-opacity: var(--v-medium-emphasis-opacity);
|
||||
}
|
||||
|
||||
.v-label {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.v-label:not(.v-field-label--floating) {
|
||||
color: rgba(var(--v-theme-on-background), var(--v-medium-emphasis-opacity));
|
||||
}
|
||||
|
||||
.v-messages {
|
||||
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.v-alert__close .v-btn--icon .v-icon {
|
||||
--v-icon-size-multiplier: 1.5;
|
||||
}
|
||||
|
||||
.v-badge__badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.v-btn:focus-visible:after {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
.v-input:not(.v-select--chips) .v-select__selection .v-chip {
|
||||
margin-block: 2px var(--select-chips-margin-bottom);
|
||||
}
|
||||
|
||||
.v-card-subtitle,
|
||||
.v-list-item-subtitle {
|
||||
color: rgba(var(--v-theme-on-background), var(--v-medium-emphasis-opacity));
|
||||
}
|
||||
|
||||
.v-field__input input::placeholder,
|
||||
input.v-field__input::placeholder,
|
||||
textarea.v-field__input::placeholder {
|
||||
color: rgba(var(--v-theme-on-surface), var(--v-disabled-opacity)) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.v-card-item {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.v-card-text {
|
||||
letter-spacing: .0094rem;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.v-card--variant-elevated {
|
||||
box-shadow: 0 4px 5px -2px var(--v-shadow-key-umbra-opacity), 0 2px 10px 1px var(--v-shadow-key-penumbra-opacity), 0 2px 16px 1px var(--v-shadow-key-ambient-opacity);
|
||||
}
|
||||
|
||||
.v-card--variant-elevated,
|
||||
.v-card--variant-flat {
|
||||
background: rgb(var(--v-theme-surface));
|
||||
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<v-row class="match-height">
|
||||
about
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<v-row class="match-height">
|
||||
accounts
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<v-row class="match-height">
|
||||
exchange rates
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<v-row class="match-height">
|
||||
overview
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,344 @@
|
||||
<template>
|
||||
<div class="auth-wrapper d-flex align-center justify-center pa-4">
|
||||
<v-card class="auth-card pa-4 pt-7" max-width="448">
|
||||
<v-card-item class="justify-center">
|
||||
<v-card-title class="d-grid font-weight-semibold text-2xl">
|
||||
<v-img alt="logo" class="login-page-logo" src="/img/ezbookkeeping-192.png" :width="96" />
|
||||
<p class="mt-4 font-weight-bold">{{ $t('global.app.title') }}</p>
|
||||
</v-card-title>
|
||||
</v-card-item>
|
||||
|
||||
<v-card-text>
|
||||
<v-form>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
type="text"
|
||||
autocomplete="username"
|
||||
clearable
|
||||
:disabled="show2faInput"
|
||||
:label="$t('Username')"
|
||||
:placeholder="$t('Your username or email')"
|
||||
v-model="username"
|
||||
@input="tempToken = ''"
|
||||
@keyup.enter="$refs.passwordInput.focus()"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
autocomplete="current-password"
|
||||
clearable
|
||||
ref="passwordInput"
|
||||
:type="isPasswordVisible ? 'text' : 'password'"
|
||||
:disabled="show2faInput"
|
||||
:label="$t('Password')"
|
||||
:placeholder="$t('Your password')"
|
||||
:append-inner-icon="isPasswordVisible ? icons.eyeSlash : icons.eye"
|
||||
v-model="password"
|
||||
@input="tempToken = ''"
|
||||
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
||||
@keyup.enter="login"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" v-show="show2faInput">
|
||||
<v-text-field
|
||||
type="number"
|
||||
autocomplete="one-time-code"
|
||||
clearable
|
||||
ref="passcodeInput"
|
||||
:label="$t('Passcode')"
|
||||
:placeholder="$t('Passcode')"
|
||||
:append-inner-icon="icons.backupCode"
|
||||
v-model="passcode"
|
||||
@click:append-inner="twoFAVerifyType = 'backupcode'"
|
||||
@keyup.enter="verify"
|
||||
v-if="twoFAVerifyType === 'passcode'"
|
||||
/>
|
||||
<v-text-field
|
||||
type="text"
|
||||
clearable
|
||||
:label="$t('Backup Code')"
|
||||
:placeholder="$t('Backup Code')"
|
||||
:append-inner-icon="icons.passcode"
|
||||
v-model="backupCode"
|
||||
@click:append-inner="twoFAVerifyType = 'passcode'"
|
||||
@keyup.enter="verify"
|
||||
v-if="twoFAVerifyType === 'backupcode'"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-btn block :class="{ 'disabled': inputIsEmpty || logining }"
|
||||
@click="login" v-if="!show2faInput">
|
||||
{{ $t('Log In') }}
|
||||
</v-btn>
|
||||
<v-btn block :class="{ 'disabled': twoFAInputIsEmpty || verifying }"
|
||||
@click="verify" v-else-if="show2faInput">
|
||||
{{ $t('Continue') }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" class="text-center text-base">
|
||||
<span>{{ $t('Don\'t have an account?') }}</span>
|
||||
<router-link class="text-primary ms-2" to="/signup">
|
||||
{{ $t('Create an account') }}
|
||||
</router-link>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" class="text-center">
|
||||
<v-menu location="bottom">
|
||||
<template #activator="{ props }">
|
||||
<v-btn variant="text" v-bind="props">{{ currentLanguageName }}</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item v-for="(lang, locale) in allLanguages" :key="locale">
|
||||
<v-list-item-title
|
||||
class="cursor-pointer"
|
||||
@click="changeLanguage(locale)">
|
||||
{{ lang.displayName }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" class="d-flex align-center">
|
||||
<VDivider />
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" class="text-center text-sm">
|
||||
<span>Powered by </span>
|
||||
<a href="https://github.com/mayswind/ezbookkeeping" target="_blank">ezBookkeeping</a> <span>{{ version }}</span>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-snackbar v-model="showSnackbar">
|
||||
{{ snackbarMessage }}
|
||||
|
||||
<template #actions>
|
||||
<v-btn color="primary" variant="text" @click="showSnackbar = false">{{ $t('Close') }}</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
|
||||
<v-overlay class="justify-center align-center" :persistent="true" v-model="logining">
|
||||
<v-progress-circular indeterminate></v-progress-circular>
|
||||
</v-overlay>
|
||||
|
||||
<v-overlay class="justify-center align-center" :persistent="true" v-model="verifying">
|
||||
<v-progress-circular indeterminate></v-progress-circular>
|
||||
</v-overlay>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapStores } from 'pinia';
|
||||
import { useRootStore } from '@/stores/index.js';
|
||||
import { useSettingsStore } from '@/stores/setting.js';
|
||||
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
|
||||
|
||||
import {
|
||||
mdiEyeOutline,
|
||||
mdiEyeOffOutline,
|
||||
mdiOnepassword,
|
||||
mdiHelpCircleOutline
|
||||
} from '@mdi/js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
username: '',
|
||||
password: '',
|
||||
passcode: '',
|
||||
backupCode: '',
|
||||
tempToken: '',
|
||||
isPasswordVisible: false,
|
||||
logining: false,
|
||||
verifying: false,
|
||||
show2faInput: false,
|
||||
twoFAVerifyType: 'passcode',
|
||||
showSnackbar: false,
|
||||
snackbarMessage: '',
|
||||
icons: {
|
||||
eye: mdiEyeOutline,
|
||||
eyeSlash: mdiEyeOffOutline,
|
||||
passcode: mdiOnepassword,
|
||||
backupCode: mdiHelpCircleOutline
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapStores(useRootStore, useSettingsStore, useExchangeRatesStore),
|
||||
version() {
|
||||
return 'v' + this.$version;
|
||||
},
|
||||
allLanguages() {
|
||||
return this.$locale.getAllLanguageInfos();
|
||||
},
|
||||
isUserRegistrationEnabled() {
|
||||
return this.$settings.isUserRegistrationEnabled();
|
||||
},
|
||||
inputIsEmpty() {
|
||||
return !this.username || !this.password;
|
||||
},
|
||||
twoFAInputIsEmpty() {
|
||||
if (this.twoFAVerifyType === 'backupcode') {
|
||||
return !this.backupCode;
|
||||
} else {
|
||||
return !this.passcode;
|
||||
}
|
||||
},
|
||||
currentLanguageName() {
|
||||
const currentLocale = this.$i18n.locale;
|
||||
let lang = this.$locale.getLanguageInfo(currentLocale);
|
||||
|
||||
if (!lang) {
|
||||
lang = this.$locale.getLanguageInfo(this.$locale.getDefaultLanguage());
|
||||
}
|
||||
|
||||
return lang.displayName;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
login() {
|
||||
const self = this;
|
||||
|
||||
if (!self.username) {
|
||||
self.showSnackbarMessage(self.$t('Username cannot be empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.password) {
|
||||
self.showSnackbarMessage(self.$t('Password cannot be empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.tempToken) {
|
||||
self.show2faInput = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.logining) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.isPasswordVisible = false;
|
||||
self.logining = true;
|
||||
|
||||
self.rootStore.authorize({
|
||||
loginName: self.username,
|
||||
password: self.password
|
||||
}).then(authResponse => {
|
||||
self.logining = false;
|
||||
|
||||
if (authResponse.need2FA) {
|
||||
self.tempToken = authResponse.token;
|
||||
self.show2faInput = true;
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (self.$refs.passcodeInput) {
|
||||
self.$refs.passcodeInput.focus();
|
||||
self.$refs.passcodeInput.select();
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (authResponse.user && authResponse.user.language) {
|
||||
const localeDefaultSettings = self.$locale.setLanguage(authResponse.user.language);
|
||||
self.settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
||||
}
|
||||
|
||||
if (self.$settings.isAutoUpdateExchangeRatesData()) {
|
||||
self.exchangeRatesStore.getLatestExchangeRates({silent: true, force: false});
|
||||
}
|
||||
|
||||
this.$router.replace('/');
|
||||
}).catch(error => {
|
||||
self.logining = false;
|
||||
|
||||
if (!error.processed) {
|
||||
self.showSnackbarMessage(self.$tError(error.message || error));
|
||||
}
|
||||
});
|
||||
},
|
||||
verify() {
|
||||
const self = this;
|
||||
|
||||
if (self.twoFAInputIsEmpty || self.verifying) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.twoFAVerifyType === 'passcode' && !this.passcode) {
|
||||
self.showSnackbarMessage(self.$t('Passcode cannot be empty'));
|
||||
return;
|
||||
} else if (this.twoFAVerifyType === 'backupcode' && !this.backupCode) {
|
||||
self.showSnackbarMessage(self.$t('Backup code cannot be empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
self.verifying = true;
|
||||
|
||||
self.rootStore.authorize2FA({
|
||||
token: self.tempToken,
|
||||
passcode: self.twoFAVerifyType === 'passcode' ? self.passcode : null,
|
||||
recoveryCode: self.twoFAVerifyType === 'backupcode' ? self.backupCode : null
|
||||
}).then(authResponse => {
|
||||
self.verifying = false;
|
||||
|
||||
if (authResponse.user && authResponse.user.language) {
|
||||
const localeDefaultSettings = self.$locale.setLanguage(authResponse.user.language);
|
||||
self.settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
||||
}
|
||||
|
||||
if (self.$settings.isAutoUpdateExchangeRatesData()) {
|
||||
self.exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false });
|
||||
}
|
||||
|
||||
this.$router.replace('/');
|
||||
}).catch(error => {
|
||||
self.verifying = false;
|
||||
|
||||
if (!error.processed) {
|
||||
self.showSnackbarMessage(self.$tError(error.message || error));
|
||||
}
|
||||
});
|
||||
},
|
||||
switch2FAVerifyType() {
|
||||
if (this.twoFAVerifyType === 'passcode') {
|
||||
this.twoFAVerifyType = 'backupcode';
|
||||
} else {
|
||||
this.twoFAVerifyType = 'passcode';
|
||||
}
|
||||
},
|
||||
changeLanguage(locale) {
|
||||
const localeDefaultSettings = this.$locale.setLanguage(locale);
|
||||
this.settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
||||
},
|
||||
showSnackbarMessage(message) {
|
||||
this.showSnackbar = true;
|
||||
this.snackbarMessage = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.auth-wrapper {
|
||||
min-block-size: calc(var(--vh, 1vh) * 100);
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
z-index: 1 !important
|
||||
}
|
||||
|
||||
.login-page-logo {
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<div class="layout-wrapper layout-nav-type-vertical layout-navbar-static layout-footer-static layout-content-width-fluid"
|
||||
:class="{ 'layout-overlay-nav': mdAndDown }">
|
||||
<div class="layout-vertical-nav" :class="{'visible': showVerticalOverlayMenu, 'scrolled': isVerticalNavScrolled, 'overlay-nav': mdAndDown}">
|
||||
<div class="nav-header">
|
||||
<router-link to="/" class="app-logo d-flex align-center gap-x-3 app-title-wrapper">
|
||||
<div class="d-flex">
|
||||
<v-img alt="logo" class="main-logo" src="/img/ezbookkeeping-192.png" />
|
||||
</div>
|
||||
<h1 class="font-weight-medium text-xl">{{ $t('global.app.title') }}</h1>
|
||||
</router-link>
|
||||
</div>
|
||||
<perfect-scrollbar
|
||||
tag="ul" class="nav-items"
|
||||
:options="{ wheelPropagation: false }"
|
||||
@ps-scroll-y="handleNavScroll"
|
||||
>
|
||||
<li class="nav-link">
|
||||
<RouterLink to="/">
|
||||
<v-icon class="nav-item-icon" :icon="icons.overview"/>
|
||||
<span class="nav-item-title">{{ $t('Overview') }}</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
<li class="nav-section-title">
|
||||
<div class="title-wrapper">
|
||||
<span class="title-text">{{ $t('Transaction Data') }}</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-link">
|
||||
<RouterLink to="/transactions">
|
||||
<v-icon class="nav-item-icon" :icon="icons.transactions"/>
|
||||
<span class="nav-item-title">{{ $t('Transaction List') }}</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
<li class="nav-link">
|
||||
<RouterLink to="/statistics/transaction">
|
||||
<v-icon class="nav-item-icon" :icon="icons.statistics"/>
|
||||
<span class="nav-item-title">{{ $t('Statistics Data') }}</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
<li class="nav-section-title">
|
||||
<div class="title-wrapper">
|
||||
<span class="title-text">{{ $t('Data Management') }}</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-link">
|
||||
<RouterLink to="/accounts">
|
||||
<v-icon class="nav-item-icon" :icon="icons.accounts"/>
|
||||
<span class="nav-item-title">{{ $t('Account List') }}</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
<li class="nav-link">
|
||||
<RouterLink to="/categories">
|
||||
<v-icon class="nav-item-icon" :icon="icons.categories"/>
|
||||
<span class="nav-item-title">{{ $t('Transaction Categories') }}</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
<li class="nav-link">
|
||||
<RouterLink to="/tags">
|
||||
<v-icon class="nav-item-icon" :icon="icons.tags"/>
|
||||
<span class="nav-item-title">{{ $t('Transaction Tags') }}</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
<li class="nav-section-title">
|
||||
<div class="title-wrapper">
|
||||
<span class="title-text">{{ $t('Other') }}</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-link">
|
||||
<RouterLink to="/exchange_rates">
|
||||
<v-icon class="nav-item-icon" :icon="icons.exchangeRates"/>
|
||||
<span class="nav-item-title">{{ $t('Exchange Rates Data') }}</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
<li class="nav-link">
|
||||
<RouterLink to="/about">
|
||||
<v-icon class="nav-item-icon" :icon="icons.about"/>
|
||||
<span class="nav-item-title">{{ $t('About') }}</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
</perfect-scrollbar>
|
||||
</div>
|
||||
|
||||
<div class="layout-content-wrapper">
|
||||
<div class="layout-navbar navbar-blur">
|
||||
<div class="navbar-content-container">
|
||||
<div class="d-flex h-100 align-center">
|
||||
<v-btn class="ms-n3 d-lg-none" color="default" variant="text"
|
||||
:icon="true" @click="showVerticalOverlayMenu = true">
|
||||
<v-icon :icon="icons.menu" size="24" />
|
||||
</v-btn>
|
||||
<div class="app-logo d-flex align-center gap-x-3 app-title-wrapper" v-if="mdAndDown">
|
||||
<div class="d-flex">
|
||||
<v-img alt="logo" class="main-logo" src="/img/ezbookkeeping-192.png" />
|
||||
</div>
|
||||
<h1 class="font-weight-medium text-xl">{{ $t('global.app.title') }}</h1>
|
||||
</div>
|
||||
<v-spacer />
|
||||
<v-avatar class="cursor-pointer" color="primary" variant="tonal">
|
||||
<v-icon :icon="icons.user"/>
|
||||
<v-menu activator="parent" width="230" location="bottom end" offset="14px">
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-list-item-action start>
|
||||
<v-avatar color="primary" variant="tonal">
|
||||
<v-icon :icon="icons.user"/>
|
||||
</v-avatar>
|
||||
</v-list-item-action>
|
||||
</template>
|
||||
<v-list-item-title class="font-weight-semibold">
|
||||
{{ currentNickName }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-divider class="my-2"/>
|
||||
<v-list-item to="/user/settings">
|
||||
<template #prepend>
|
||||
<v-icon class="me-2" :icon="icons.profile" size="22"/>
|
||||
</template>
|
||||
<v-list-item-title>{{ $t('User Profile') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item to="/app/settings">
|
||||
<template #prepend>
|
||||
<v-icon class="me-2" :icon="icons.settings" size="22"/>
|
||||
</template>
|
||||
<v-list-item-title>{{ $t('Application Settings') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-divider class="my-2"/>
|
||||
<v-list-item :class="{ 'disabled': logouting }" @click="logout">
|
||||
<template #prepend>
|
||||
<v-icon class="me-2" :icon="icons.logout" size="22"/>
|
||||
</template>
|
||||
<v-list-item-title>{{ $t('Log Out') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-avatar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-page-content">
|
||||
<div class="page-content-container">
|
||||
<router-view/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layout-overlay" :class="{ 'visible': showVerticalOverlayMenu }" @click="showVerticalOverlayMenu = false"></div>
|
||||
|
||||
<v-overlay class="justify-center align-center" :persistent="true" v-model="showLoading">
|
||||
<v-progress-circular indeterminate></v-progress-circular>
|
||||
</v-overlay>
|
||||
|
||||
<v-snackbar :timeout="2000" v-model="showSnackbar">
|
||||
{{ snackbarMessage }}
|
||||
|
||||
<template #actions>
|
||||
<v-btn color="red" variant="text" @click="showSnackbar = false">{{ $t('Close') }}</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useDisplay } 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 {
|
||||
mdiMenu,
|
||||
mdiHomeOutline,
|
||||
mdiListBoxOutline,
|
||||
mdiCreditCardOutline,
|
||||
mdiViewDashboardOutline,
|
||||
mdiTagOutline,
|
||||
mdiChartPieOutline,
|
||||
mdiSwapHorizontal,
|
||||
mdiCogOutline,
|
||||
mdiInformationOutline,
|
||||
mdiAccount,
|
||||
mdiAccountOutline,
|
||||
mdiLogout
|
||||
} from '@mdi/js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
logouting: false,
|
||||
isVerticalNavScrolled: false,
|
||||
showVerticalOverlayMenu: false,
|
||||
showLoading: false,
|
||||
showSnackbar: false,
|
||||
snackbarMessage: '',
|
||||
icons: {
|
||||
menu: mdiMenu,
|
||||
overview: mdiHomeOutline,
|
||||
transactions: mdiListBoxOutline,
|
||||
accounts: mdiCreditCardOutline,
|
||||
categories: mdiViewDashboardOutline,
|
||||
tags: mdiTagOutline,
|
||||
statistics: mdiChartPieOutline,
|
||||
exchangeRates: mdiSwapHorizontal,
|
||||
settings: mdiCogOutline,
|
||||
about: mdiInformationOutline,
|
||||
user: mdiAccount,
|
||||
profile: mdiAccountOutline,
|
||||
logout: mdiLogout
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useRootStore, useSettingsStore, useUserStore),
|
||||
mdAndDown() {
|
||||
const { mdAndDown } = useDisplay();
|
||||
return mdAndDown.value;
|
||||
},
|
||||
currentNickName() {
|
||||
return this.userStore.currentUserNickname || this.$t('User');
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
handleNavScroll(e) {
|
||||
this.isVerticalNavScrolled = e.target.scrollTop > 0;
|
||||
},
|
||||
logout() {
|
||||
const self = this;
|
||||
|
||||
self.logouting = true;
|
||||
self.showLoading = true;
|
||||
|
||||
self.rootStore.logout().then(() => {
|
||||
self.logouting = false;
|
||||
self.showLoading = false;
|
||||
|
||||
self.$settings.clearSettings();
|
||||
|
||||
const localeDefaultSettings = self.$locale.initLocale(self.userStore.currentUserLanguage);
|
||||
self.settingsStore.updateLocalizedDefaultSettings(localeDefaultSettings);
|
||||
|
||||
this.$router.replace('/login');
|
||||
}).catch(error => {
|
||||
self.logouting = false;
|
||||
self.showLoading = false;
|
||||
|
||||
if (!error.processed) {
|
||||
self.showSnackbarMessage(self.$tError(error.message || error));
|
||||
}
|
||||
});
|
||||
},
|
||||
showSnackbarMessage(message) {
|
||||
this.showSnackbar = true;
|
||||
this.snackbarMessage = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.main-logo {
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div class="auth-wrapper d-flex align-center justify-center pa-4">
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<v-row class="match-height">
|
||||
categories
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<v-row class="match-height">
|
||||
tags
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<v-row class="match-height">
|
||||
transactions
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div class="auth-wrapper d-flex align-center justify-center pa-4">
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<v-row class="match-height">
|
||||
app settings
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<v-row class="match-height">
|
||||
statistics
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<v-row class="match-height">
|
||||
user settings
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -93,12 +93,24 @@
|
||||
"url": "https://github.com/vuejs/pinia",
|
||||
"licenseUrl": "https://github.com/vuejs/pinia/blob/pinia%402.1.4/LICENSE"
|
||||
},
|
||||
{
|
||||
"name": "vue-router",
|
||||
"copyright": "Copyright (c) 2019-present Eduardo San Martin Morote",
|
||||
"url": "https://github.com/vuejs/router",
|
||||
"licenseUrl": "https://github.com/vuejs/router/blob/v4.2.2/LICENSE"
|
||||
},
|
||||
{
|
||||
"name": "vue-i18n",
|
||||
"copyright": "Copyright (c) 2016 kazuya kawaguchi",
|
||||
"url": "https://github.com/intlify/vue-i18n-next",
|
||||
"licenseUrl": "https://github.com/intlify/vue-i18n-next/blob/v9.2.2/LICENSE"
|
||||
},
|
||||
{
|
||||
"name": "vuetify",
|
||||
"copyright": "Copyright (c) 2016-2023 John Jeremy Leider",
|
||||
"url": "https://vuetifyjs.com",
|
||||
"licenseUrl": "https://github.com/vuetifyjs/vuetify/blob/v3.3.5/LICENSE.md"
|
||||
},
|
||||
{
|
||||
"name": "register-service-worker",
|
||||
"copyright": "Copyright (c) 2013-present, Yuxi (Evan) You",
|
||||
@@ -141,6 +153,12 @@
|
||||
"url": "https://github.com/nolimits4web/skeleton-elements",
|
||||
"licenseUrl": "https://github.com/nolimits4web/skeleton-elements/blob/v4.0.1/LICENSE"
|
||||
},
|
||||
{
|
||||
"name": "vue3-perfect-scrollbar",
|
||||
"copyright": "Copyright (c) 2018 Adam",
|
||||
"url": "https://github.com/mercs600/vue3-perfect-scrollbar",
|
||||
"licenseUrl": "https://github.com/mercs600/vue3-perfect-scrollbar/blob/1.6.1/LICENSE"
|
||||
},
|
||||
{
|
||||
"name": "@vuepic/vue-datepicker",
|
||||
"copyright": "Copyright (c) 2021-present Vuepic",
|
||||
@@ -201,9 +219,20 @@
|
||||
"url": "https://github.com/faisalman/ua-parser-js",
|
||||
"licenseUrl": "https://github.com/faisalman/ua-parser-js/blob/1.0.35/license.md"
|
||||
},
|
||||
{
|
||||
"name": "Materio - Vuetify VueJS 3 Free Admin Template",
|
||||
"copyright": "Copyright (c) 2022 ThemeSelection",
|
||||
"url": "https://github.com/themeselection/materio-vuetify-vuejs-admin-template-free",
|
||||
"licenseUrl": "https://github.com/themeselection/materio-vuetify-vuejs-admin-template-free/blob/v2.1.0/LICENSE"
|
||||
},
|
||||
{
|
||||
"name": "Icons8 Line Awesome",
|
||||
"url": "https://icons8.com/line-awesome",
|
||||
"licenseUrl": "https://github.com/icons8/line-awesome/blob/master/LICENSE.md"
|
||||
},
|
||||
{
|
||||
"name": "Material Design Icons for JS/TypeScript",
|
||||
"url": "https://materialdesignicons.com",
|
||||
"licenseUrl": "https://github.com/Templarian/MaterialDesign-JS/blob/v7.2.96/LICENSE"
|
||||
}
|
||||
]
|
||||
|
||||
+11
-1
@@ -110,10 +110,20 @@ export default defineConfig(async () => {
|
||||
manualChunks: function (id) {
|
||||
if (/[\\/]node_modules[\\/]leaflet[\\/]/i.test(id)) {
|
||||
return 'leaflet';
|
||||
} else if (/[\\/]node_modules[\\/](moment|moment-timezone)[\\/]/i.test(id)) {
|
||||
return 'moment';
|
||||
} else if (/[\\/]node_modules[\\/](dom7|framework7.*|skeleton-elements|swiper)[\\/]/i.test(id)) {
|
||||
return 'vendor-mobile';
|
||||
} else if (/[\\/]node_modules[\\/](vuetify|vue-router|vue3-perfect-scrollbar|@mdi.*)[\\/]/i.test(id)) {
|
||||
return 'vendor-desktop';
|
||||
} else if (/[\\/]node_modules[\\/]/i.test(id)) {
|
||||
return 'vendor';
|
||||
return 'vendor-common';
|
||||
} else if (/[\\/]src[\\/]locales[\\/]/i.test(id)) {
|
||||
return 'locales';
|
||||
} else if (/[\\/]src[\\/](consts|stores)[\\/]/i.test(id)) {
|
||||
return 'common';
|
||||
} else if (/[\\/]src[\\/]lib[\\/](map[\\/]|[a-zA-Z0-9-_]+\.js)/i.test(id)) {
|
||||
return 'common';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user