feat: 登录显示加载遮罩并将统计面板图表改为异步加载

- 登录请求期间显示全屏遮罩与「正在登录」提示,并等待路由跳转完成再隐藏
- 统计面板 echarts 改为 defineAsyncComponent 按需加载,避免阻塞登录后首屏
This commit is contained in:
2026-09-21 21:24:12 +08:00
parent 09ca5b43fb
commit 06fd80d55f
3 changed files with 52 additions and 10 deletions
+1
View File
@@ -56,6 +56,7 @@ export default {
submit: { zh: '登录', en: 'Sign In', ja: 'ログイン' }, submit: { zh: '登录', en: 'Sign In', ja: 'ログイン' },
toRegister: { zh: '没有账号?去注册', en: "Don't have an account? Sign up", ja: 'アカウントをお持ちでない方は新規登録' }, toRegister: { zh: '没有账号?去注册', en: "Don't have an account? Sign up", ja: 'アカウントをお持ちでない方は新規登録' },
fillRequired: { zh: '请填写用户名和密码', en: 'Please enter username and password', ja: 'ユーザー名とパスワードを入力してください' }, fillRequired: { zh: '请填写用户名和密码', en: 'Please enter username and password', ja: 'ユーザー名とパスワードを入力してください' },
loggingIn: { zh: '正在登录,请稍候…', en: 'Signing in, please wait…', ja: 'ログイン中です。少々お待ちください…' },
success: { zh: '登录成功', en: 'Signed in successfully', ja: 'ログインしました' }, success: { zh: '登录成功', en: 'Signed in successfully', ja: 'ログインしました' },
failed: { zh: '登录失败', en: 'Sign in failed', ja: 'ログインに失敗しました' }, failed: { zh: '登录失败', en: 'Sign in failed', ja: 'ログインに失敗しました' },
}, },
+23 -8
View File
@@ -1,15 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
// 统计面板组件:展示指定时间范围的收支汇总、概览指标、趋势、分类占比与交易渠道分布 // 统计面板组件:展示指定时间范围的收支汇总、概览指标、趋势、分类占比与交易渠道分布
import { ref, computed, onMounted, watch, nextTick, unref } from 'vue' import { ref, computed, onMounted, watch, nextTick, unref, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { NCard, NGrid, NGridItem, NStatistic, NSpace, NSelect, NDatePicker, NInput, NInputNumber, NButton, NAutoComplete, NModal, NAlert, NDropdown, NIcon, useMessage } from 'naive-ui' import { NCard, NGrid, NGridItem, NStatistic, NSpace, NSelect, NDatePicker, NInput, NInputNumber, NButton, NAutoComplete, NModal, NAlert, NDropdown, NIcon, useMessage } from 'naive-ui'
import { SparklesOutline, BulbOutline } from '@vicons/ionicons5' import { SparklesOutline, BulbOutline } from '@vicons/ionicons5'
import VChart from 'vue-echarts'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { LineChart, PieChart, BarChart } from 'echarts/charts'
import { TitleComponent, TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'
import { getStatisticsSummary, getStatisticsGroup } from '../api/statistics' import { getStatisticsSummary, getStatisticsGroup } from '../api/statistics'
import { getCategoryList } from '../api/category' import { getCategoryList } from '../api/category'
import { getTagList } from '../api/tag' import { getTagList } from '../api/tag'
@@ -20,8 +15,28 @@ import { useKeywordHistory } from '../composables/useKeywordHistory'
import { getChannelLabel } from '../utils/channel' import { getChannelLabel } from '../utils/channel'
import { renderCategoryLabel } from '../utils/render' import { renderCategoryLabel } from '../utils/render'
// 注册 ECharts 所需组件 // 图表组件异步加载:echarts 体积较大,登录后进入面板时先渲染页面骨架,
use([CanvasRenderer, LineChart, PieChart, BarChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent]) // echarts 在后台按需下载并注册,避免阻塞目标页首屏
const VChart = defineAsyncComponent(async () => {
const [vueEcharts, echartsCore, renderers, charts, components] = await Promise.all([
import('vue-echarts'),
import('echarts/core'),
import('echarts/renderers'),
import('echarts/charts'),
import('echarts/components'),
])
echartsCore.use([
renderers.CanvasRenderer,
charts.LineChart,
charts.PieChart,
charts.BarChart,
components.TitleComponent,
components.TooltipComponent,
components.LegendComponent,
components.GridComponent,
])
return vueEcharts.default
})
const { t, locale } = useI18n() // 国际化 const { t, locale } = useI18n() // 国际化
const message = useMessage() // 消息提示 const message = useMessage() // 消息提示
+28 -2
View File
@@ -3,7 +3,7 @@
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useMessage, NCard, NForm, NFormItem, NInput, NButton, NSpace } from 'naive-ui' import { useMessage, NCard, NForm, NFormItem, NInput, NButton, NSpace, NSpin } from 'naive-ui'
import { login } from '../api/user' import { login } from '../api/user'
import { useUserStore } from '../store/user' import { useUserStore } from '../store/user'
import { useRegistrationStore } from '../store/registration' import { useRegistrationStore } from '../store/registration'
@@ -54,7 +54,8 @@ async function handleLogin() {
userStore.setToken(res.data.token) // 保存 token userStore.setToken(res.data.token) // 保存 token
userStore.setUserInfo(res.data.user.nickname, res.data.user.id) // 保存用户信息 userStore.setUserInfo(res.data.user.nickname, res.data.user.id) // 保存用户信息
message.success(t('login.success')) message.success(t('login.success'))
router.push(resolveRedirect()) // 跳转到来源页或统计面板 // 等待目标页加载完成再隐藏遮罩,避免跳转过程中的空白等待
await router.push(resolveRedirect()) // 跳转到来源页或统计面板
} catch (e: any) { } catch (e: any) {
message.error(e.message || t('login.failed')) message.error(e.message || t('login.failed'))
} finally { } finally {
@@ -82,6 +83,11 @@ async function handleLogin() {
</NSpace> </NSpace>
</NForm> </NForm>
</NCard> </NCard>
<!-- 登录中遮罩:登录请求已发起,提示用户等待,同时覆盖跳转过程中的加载 -->
<div v-if="loading" class="login-loading">
<NSpin size="large" stroke="#ffffff" />
<span class="login-loading__text">{{ t('login.loggingIn') }}</span>
</div>
</div> </div>
</template> </template>
@@ -93,4 +99,24 @@ async function handleLogin() {
height: 100vh; height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
} }
/* 登录中遮罩:覆盖整个视口,居中显示加载动画与提示文案 */
.login-loading {
position: fixed;
inset: 0;
z-index: 10;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
background: rgba(0, 0, 0, 0.35);
backdrop-filter: blur(2px);
}
.login-loading__text {
color: #fff;
font-size: 14px;
letter-spacing: 1px;
}
</style> </style>