feat: 交互优化与代码质量改进
- SvgIcon 改为 PNG 图标方案,组件重命名为 Icon - 修复 TransactionItem tap 事件名冲突,改为 item-tap - 修复 Stats 页面切换 tab 时 overview 不更新 - 修复 CategoryIcon bgColor 必填问题 - 修复 ChartWrapper import 顺序 - 修复 budget onDelete 空字符串问题 - 清理 profile 页面未使用 CSS - 统一所有页面 header 为 sticky 定位 - 修复导航栏返回按钮不统一 - 添加导出功能 loading 状态 - 优化 scroll-into-view 延迟
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<view class="header">
|
||||
<view class="close" @tap="goBack">
|
||||
<SvgIcon name="close" :size="36" color="#8B7E7E" />
|
||||
<view class="header-fixed">
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<view class="header">
|
||||
<view class="nav-back" @tap="goBack">
|
||||
<Icon name="arrowLeft" :size="36" color="#2D1B1B" />
|
||||
</view>
|
||||
<text class="title">{{ editId ? '编辑记录' : '记一笔' }}</text>
|
||||
<view style="width: 88rpx;"></view>
|
||||
</view>
|
||||
<text class="title">{{ editId ? '编辑记录' : '记一笔' }}</text>
|
||||
<view style="width: 64rpx;"></view>
|
||||
</view>
|
||||
|
||||
<view class="type-toggle-wrap">
|
||||
@@ -34,12 +36,12 @@
|
||||
<view class="extra-fields">
|
||||
<picker mode="date" :value="selectedDate" :end="todayStr" @change="onDateChange">
|
||||
<view class="extra-row">
|
||||
<SvgIcon name="calendar" :size="28" color="#8B7E7E" />
|
||||
<Icon name="calendar" :size="28" color="#8B7E7E" />
|
||||
<text class="extra-text">{{ dateLabel }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<view class="extra-row">
|
||||
<SvgIcon name="edit" :size="28" color="#8B7E7E" />
|
||||
<Icon name="edit" :size="28" color="#8B7E7E" />
|
||||
<input class="extra-input" v-model="note" placeholder="添加备注..." placeholder-class="placeholder" maxlength="50" />
|
||||
</view>
|
||||
</view>
|
||||
@@ -51,12 +53,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { ref, computed, watch, onMounted, nextTick } from 'vue'
|
||||
import { useCategoryStore } from '@/stores/category'
|
||||
import { useTransactionStore } from '@/stores/transaction'
|
||||
import Numpad from '@/components/Numpad/Numpad.vue'
|
||||
import CategoryIcon from '@/components/CategoryIcon/CategoryIcon.vue'
|
||||
import SvgIcon from '@/components/SvgIcon/SvgIcon.vue'
|
||||
import Icon from '@/components/Icon/Icon.vue'
|
||||
import SaveSuccess from '@/components/SaveSuccess/SaveSuccess.vue'
|
||||
import { statusBarHeight } from '@/utils/system'
|
||||
|
||||
@@ -131,8 +133,10 @@ onMounted(async () => {
|
||||
lastCatByType[existing.type] = existing.category_id
|
||||
note.value = existing.note || ''
|
||||
selectedDate.value = existing.date.slice(0, 10)
|
||||
// Scroll to selected category
|
||||
setTimeout(() => { scrollToCat.value = 'cat-' + existing.category_id }, 100)
|
||||
// 等待分类列表渲染完成后滚动到选中项
|
||||
nextTick(() => {
|
||||
setTimeout(() => { scrollToCat.value = 'cat-' + existing.category_id }, 200)
|
||||
})
|
||||
} else {
|
||||
uni.showToast({ title: '记录不存在', icon: 'none' })
|
||||
setTimeout(() => uni.navigateBack(), 1500)
|
||||
@@ -233,6 +237,13 @@ function goBack() {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-fixed {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: #FFF8F0;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
background: #FFF8F0;
|
||||
}
|
||||
@@ -244,12 +255,15 @@ function goBack() {
|
||||
padding: 16rpx 40rpx;
|
||||
}
|
||||
|
||||
.close {
|
||||
.nav-back {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
|
||||
&:active { background: #F0E0D6; }
|
||||
}
|
||||
|
||||
.title { font-size: 32rpx; font-weight: 500; color: #2D1B1B; }
|
||||
@@ -317,13 +331,12 @@ function goBack() {
|
||||
|
||||
.category-scroll {
|
||||
max-height: 320rpx;
|
||||
padding: 0 40rpx;
|
||||
}
|
||||
|
||||
.category-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20rpx;
|
||||
gap: 16rpx;
|
||||
padding: 8rpx 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<text class="page-title">全部账单</text>
|
||||
<view class="header-fixed">
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<text class="page-title">全部账单</text>
|
||||
</view>
|
||||
|
||||
<view class="tabs-wrap">
|
||||
<view class="tabs">
|
||||
@@ -25,7 +27,7 @@
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:hideDate="true"
|
||||
@tap="onEdit(item)"
|
||||
@item-tap="onEdit(item)"
|
||||
@delete="onDelete(item)"
|
||||
/>
|
||||
</view>
|
||||
@@ -57,7 +59,7 @@
|
||||
</view>
|
||||
|
||||
<view class="state-box" v-if="loadError">
|
||||
<SvgIcon name="alert-circle" :size="48" color="#BFB3B3" />
|
||||
<Icon name="alert-circle" :size="48" color="#BFB3B3" />
|
||||
<text class="state-text">加载失败,下拉刷新重试</text>
|
||||
</view>
|
||||
|
||||
@@ -72,7 +74,7 @@ import { ref, computed, onMounted } from 'vue'
|
||||
import { onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { useTransactionStore } from '@/stores/transaction'
|
||||
import TransactionItem from '@/components/TransactionItem/TransactionItem.vue'
|
||||
import SvgIcon from '@/components/SvgIcon/SvgIcon.vue'
|
||||
import Icon from '@/components/Icon/Icon.vue'
|
||||
import Skeleton from '@/components/Skeleton/Skeleton.vue'
|
||||
import { statusBarHeight } from '@/utils/system'
|
||||
import { formatAmount, formatDate } from '@/utils/format'
|
||||
@@ -180,6 +182,13 @@ onPullDownRefresh(() => {
|
||||
padding: 0 0 180rpx;
|
||||
}
|
||||
|
||||
.header-fixed {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: #FFF8F0;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
background: #FFF8F0;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<view class="nav-bar">
|
||||
<view class="nav-back" @tap="goBack">
|
||||
<SvgIcon name="chevronLeft" :size="40" color="#2D1B1B" />
|
||||
<view class="header-fixed">
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<view class="nav-bar">
|
||||
<view class="nav-back" @tap="goBack">
|
||||
<Icon name="arrowLeft" :size="36" color="#2D1B1B" />
|
||||
</view>
|
||||
<text class="nav-title">预算设置</text>
|
||||
<view class="nav-placeholder"></view>
|
||||
</view>
|
||||
<text class="nav-title">预算设置</text>
|
||||
<view class="nav-placeholder"></view>
|
||||
</view>
|
||||
|
||||
<view class="content">
|
||||
@@ -64,14 +66,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useBudgetStore } from '@/stores/budget'
|
||||
import { useStatsStore } from '@/stores/stats'
|
||||
import { formatAmount, getCurrentMonth } from '@/utils/format'
|
||||
import { statusBarHeight } from '@/utils/system'
|
||||
import BudgetBar from '@/components/BudgetBar/BudgetBar.vue'
|
||||
import Numpad from '@/components/Numpad/Numpad.vue'
|
||||
import SvgIcon from '@/components/SvgIcon/SvgIcon.vue'
|
||||
import Icon from '@/components/Icon/Icon.vue'
|
||||
|
||||
const budgetStore = useBudgetStore()
|
||||
const statsStore = useStatsStore()
|
||||
@@ -130,7 +132,11 @@ function onInput(key: string) {
|
||||
}
|
||||
|
||||
function onDelete() {
|
||||
amountStr.value = amountStr.value.slice(0, -1)
|
||||
if (amountStr.value.length > 1) {
|
||||
amountStr.value = amountStr.value.slice(0, -1)
|
||||
} else {
|
||||
amountStr.value = '0'
|
||||
}
|
||||
}
|
||||
|
||||
async function onConfirm() {
|
||||
@@ -164,6 +170,13 @@ function goBack() {
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.header-fixed {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: #FFF8F0;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
background: #FFF8F0;
|
||||
}
|
||||
@@ -175,11 +188,14 @@ function goBack() {
|
||||
}
|
||||
|
||||
.nav-back {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
|
||||
&:active { background: #F0E0D6; }
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<view class="header">
|
||||
<view class="back" @tap="uni.navigateBack()">
|
||||
<SvgIcon name="arrowLeft" :size="36" color="#2D1B1B" />
|
||||
<view class="header-fixed">
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<view class="header">
|
||||
<view class="nav-back" @tap="goBack">
|
||||
<Icon name="arrowLeft" :size="36" color="#2D1B1B" />
|
||||
</view>
|
||||
<text class="title">分类管理</text>
|
||||
<view style="width: 88rpx;"></view>
|
||||
</view>
|
||||
<text class="title">分类管理</text>
|
||||
<view style="width: 88rpx;"></view>
|
||||
</view>
|
||||
|
||||
<view class="tabs-wrap">
|
||||
@@ -24,7 +26,7 @@
|
||||
<text class="cat-badge" v-if="cat.is_custom">自定义</text>
|
||||
</view>
|
||||
<view class="cat-actions" v-if="cat.is_custom" @tap="onDelete(cat)">
|
||||
<SvgIcon name="trash" :size="28" color="#FF6B6B" />
|
||||
<Icon name="trash" :size="28" color="#FF6B6B" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -52,7 +54,7 @@
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useCategoryStore } from '@/stores/category'
|
||||
import CategoryIcon from '@/components/CategoryIcon/CategoryIcon.vue'
|
||||
import SvgIcon from '@/components/SvgIcon/SvgIcon.vue'
|
||||
import Icon from '@/components/Icon/Icon.vue'
|
||||
import { statusBarHeight } from '@/utils/system'
|
||||
|
||||
const catStore = useCategoryStore()
|
||||
@@ -65,6 +67,10 @@ const currentCategories = computed(() => catStore.getByType(tabType.value))
|
||||
|
||||
onMounted(() => catStore.fetchCategories())
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
async function onAdd() {
|
||||
const name = newName.value.trim()
|
||||
if (!name) return
|
||||
@@ -132,6 +138,13 @@ async function onDelete(cat: any) {
|
||||
padding: 0 0 180rpx;
|
||||
}
|
||||
|
||||
.header-fixed {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: #FFF8F0;
|
||||
}
|
||||
|
||||
.status-bar { background: #FFF8F0; }
|
||||
|
||||
.header {
|
||||
@@ -141,12 +154,15 @@ async function onDelete(cat: any) {
|
||||
padding: 16rpx 40rpx;
|
||||
}
|
||||
|
||||
.back {
|
||||
.nav-back {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
|
||||
&:active { background: #F0E0D6; }
|
||||
}
|
||||
|
||||
.title { font-size: 32rpx; font-weight: 600; color: #2D1B1B; }
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<view class="header">
|
||||
<view class="avatar-wrap">
|
||||
<SvgIcon name="user" :size="36" color="#8B7E7E" />
|
||||
</view>
|
||||
<text class="greeting">{{ greeting }},小菜</text>
|
||||
<view class="bell" @tap="uni.showToast({ title: '暂无通知', icon: 'none' })">
|
||||
<SvgIcon name="bell" :size="40" color="#8B7E7E" />
|
||||
<view class="header-fixed">
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<view class="header">
|
||||
<view class="avatar-wrap">
|
||||
<Icon name="user" :size="36" color="#8B7E7E" />
|
||||
</view>
|
||||
<text class="greeting">{{ greeting }},小菜</text>
|
||||
<view class="bell" @tap="showNotification">
|
||||
<Icon name="bell" :size="40" color="#8B7E7E" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -68,11 +70,11 @@
|
||||
|
||||
<view class="quick-actions">
|
||||
<view class="quick-btn" @tap="goAdd('expense')">
|
||||
<SvgIcon name="trend-down" :size="48" color="#FF6B6B" />
|
||||
<Icon name="trend-down" :size="48" color="#FF6B6B" />
|
||||
<text class="quick-text">记支出</text>
|
||||
</view>
|
||||
<view class="quick-btn" @tap="goAdd('income')">
|
||||
<SvgIcon name="trend-up" :size="48" color="#7BC67E" />
|
||||
<Icon name="trend-up" :size="48" color="#7BC67E" />
|
||||
<text class="quick-text">记收入</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -83,7 +85,7 @@
|
||||
</view>
|
||||
|
||||
<view class="tx-list" v-if="!loading && !loadError && recentTx.length > 0">
|
||||
<TransactionItem v-for="item in recentTx" :key="item.id" :item="item" @tap="onTxTap(item)" />
|
||||
<TransactionItem v-for="item in recentTx" :key="item.id" :item="item" :disableSwipe="true" @item-tap="onTxTap(item)" />
|
||||
</view>
|
||||
|
||||
<view class="tx-list" v-if="loading">
|
||||
@@ -98,12 +100,12 @@
|
||||
</view>
|
||||
|
||||
<view class="state-box" v-if="loadError">
|
||||
<SvgIcon name="alert-circle" :size="48" color="#BFB3B3" />
|
||||
<Icon name="alert-circle" :size="48" color="#BFB3B3" />
|
||||
<text class="state-text">加载失败,下拉刷新重试</text>
|
||||
</view>
|
||||
|
||||
<view class="state-box" v-if="!loading && !loadError && recentTx.length === 0">
|
||||
<SvgIcon name="edit" :size="48" color="#BFB3B3" />
|
||||
<Icon name="edit" :size="48" color="#BFB3B3" />
|
||||
<text class="state-text">还没有记录,快去记一笔吧</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -119,7 +121,7 @@ import { formatAmount, formatAmountRaw } from '@/utils/format'
|
||||
import { statusBarHeight } from '@/utils/system'
|
||||
import BudgetBar from '@/components/BudgetBar/BudgetBar.vue'
|
||||
import TransactionItem from '@/components/TransactionItem/TransactionItem.vue'
|
||||
import SvgIcon from '@/components/SvgIcon/SvgIcon.vue'
|
||||
import Icon from '@/components/Icon/Icon.vue'
|
||||
import Skeleton from '@/components/Skeleton/Skeleton.vue'
|
||||
|
||||
const txStore = useTransactionStore()
|
||||
@@ -201,6 +203,10 @@ function goAdd(type: string) {
|
||||
uni.navigateTo({ url: `/pages/add/index?type=${type}` })
|
||||
}
|
||||
|
||||
function showNotification() {
|
||||
uni.showToast({ title: '暂无通知', icon: 'none' })
|
||||
}
|
||||
|
||||
function onTxTap(item: any) {
|
||||
uni.navigateTo({ url: `/pages/add/index?editId=${item.id}` })
|
||||
}
|
||||
@@ -217,6 +223,13 @@ function goBills() {
|
||||
padding: 0 0 180rpx;
|
||||
}
|
||||
|
||||
.header-fixed {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: #FFF8F0;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
background: #FFF8F0;
|
||||
}
|
||||
@@ -301,7 +314,8 @@ function goBills() {
|
||||
|
||||
.mini-stat {
|
||||
flex: 1;
|
||||
padding: 24rpx;
|
||||
min-width: 0;
|
||||
padding: 24rpx 12rpx;
|
||||
border-radius: 24rpx;
|
||||
background: #FFF8F0;
|
||||
text-align: center;
|
||||
@@ -316,8 +330,11 @@ function goBills() {
|
||||
|
||||
.mini-value {
|
||||
font-family: 'Fredoka', sans-serif;
|
||||
font-size: 36rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&.income { color: #7BC67E; }
|
||||
&.expense { color: #FF6B6B; }
|
||||
@@ -413,6 +430,9 @@ function goBills() {
|
||||
.section-more {
|
||||
font-size: 24rpx;
|
||||
color: #FF8C69;
|
||||
padding: 8rpx 0;
|
||||
|
||||
&:active { opacity: 0.6; }
|
||||
}
|
||||
|
||||
.tx-list {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<text class="page-title">我的</text>
|
||||
<view class="header-fixed">
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<text class="page-title">我的</text>
|
||||
</view>
|
||||
|
||||
<view class="profile-card">
|
||||
<view class="p-avatar">
|
||||
<SvgIcon name="user" :size="64" color="#FFFFFF" />
|
||||
<Icon name="user" :size="64" color="#FFFFFF" />
|
||||
</view>
|
||||
<view class="p-info">
|
||||
<text class="p-name">小菜</text>
|
||||
@@ -31,22 +33,22 @@
|
||||
<view class="menu-item" @tap="goBudget">
|
||||
<text class="mi-label">预算设置</text>
|
||||
<text class="mi-extra" v-if="budget">{{ formatAmount(budget.amount) }}</text>
|
||||
<SvgIcon name="chevronRight" :size="24" color="#BFB3B3" />
|
||||
<Icon name="chevronRight" :size="24" color="#BFB3B3" />
|
||||
</view>
|
||||
<view class="menu-item" @tap="goCategoryManage">
|
||||
<text class="mi-label">分类管理</text>
|
||||
<SvgIcon name="chevronRight" :size="24" color="#BFB3B3" />
|
||||
<Icon name="chevronRight" :size="24" color="#BFB3B3" />
|
||||
</view>
|
||||
<view class="menu-item" @tap="exportData">
|
||||
<text class="mi-label">数据导出</text>
|
||||
<SvgIcon name="chevronRight" :size="24" color="#BFB3B3" />
|
||||
<Icon name="chevronRight" :size="24" color="#BFB3B3" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-card mt">
|
||||
<view class="menu-item" @tap="showAbout = true">
|
||||
<text class="mi-label">关于小菜</text>
|
||||
<SvgIcon name="chevronRight" :size="24" color="#BFB3B3" />
|
||||
<Icon name="chevronRight" :size="24" color="#BFB3B3" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -55,7 +57,7 @@
|
||||
<view class="modal" @tap.stop>
|
||||
<text class="about-title">小菜记账</text>
|
||||
<text class="about-version">v1.0.0</text>
|
||||
<text class="about-desc">可爱 x 高级的个人记账小程序</text>
|
||||
<text class="about-desc">温暖 x 轻松的个人记账小程序</text>
|
||||
<text class="about-desc">让记账从负担变成享受</text>
|
||||
<view class="modal-btns" style="margin-top: 40rpx;">
|
||||
<view class="modal-btn confirm" @tap="showAbout = false">知道了</view>
|
||||
@@ -73,7 +75,7 @@ import { useBudgetStore } from '@/stores/budget'
|
||||
import { formatAmount } from '@/utils/format'
|
||||
import { statusBarHeight } from '@/utils/system'
|
||||
import { request } from '@/utils/request'
|
||||
import SvgIcon from '@/components/SvgIcon/SvgIcon.vue'
|
||||
import Icon from '@/components/Icon/Icon.vue'
|
||||
|
||||
const txStore = useTransactionStore()
|
||||
const statsStore = useStatsStore()
|
||||
@@ -115,6 +117,7 @@ function getLocalDateStr(): string {
|
||||
}
|
||||
|
||||
async function exportData() {
|
||||
uni.showLoading({ title: '导出中...' })
|
||||
try {
|
||||
const data = await request<any>({ url: '/transactions', data: { page: 1, pageSize: 9999 } })
|
||||
const list = data.list
|
||||
@@ -149,6 +152,7 @@ async function exportData() {
|
||||
a.download = `小菜记账_${getLocalDateStr()}.csv`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '导出成功', icon: 'success' })
|
||||
// #endif
|
||||
|
||||
@@ -156,6 +160,7 @@ async function exportData() {
|
||||
const fs = uni.getFileSystemManager()
|
||||
const filePath = `${wx.env.USER_DATA_PATH}/小菜记账_${getLocalDateStr()}.csv`
|
||||
fs.writeFileSync(filePath, csv, 'utf-8')
|
||||
uni.hideLoading()
|
||||
uni.openDocument({
|
||||
filePath,
|
||||
fileType: 'csv',
|
||||
@@ -164,6 +169,7 @@ async function exportData() {
|
||||
})
|
||||
// #endif
|
||||
} catch {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '导出失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
@@ -176,6 +182,13 @@ async function exportData() {
|
||||
padding: 0 0 180rpx;
|
||||
}
|
||||
|
||||
.header-fixed {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: #FFF8F0;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
background: #FFF8F0;
|
||||
}
|
||||
@@ -346,24 +359,6 @@ async function exportData() {
|
||||
padding: 48rpx;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #2D1B1B;
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.modal-input {
|
||||
height: 96rpx;
|
||||
background: #FFF8F0;
|
||||
border-radius: 24rpx;
|
||||
padding: 0 32rpx;
|
||||
font-size: 28rpx;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.modal-btns {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
@@ -379,11 +374,6 @@ async function exportData() {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
|
||||
&.cancel {
|
||||
background: #FFF8F0;
|
||||
color: #8B7E7E;
|
||||
}
|
||||
|
||||
&.confirm {
|
||||
background: linear-gradient(135deg, #FF8C69, #E67355);
|
||||
color: #FFFFFF;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<text class="page-title">统计分析</text>
|
||||
<view class="header-fixed">
|
||||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<text class="page-title">统计分析</text>
|
||||
</view>
|
||||
|
||||
<view class="month-picker">
|
||||
<view class="arrow" @tap="prevMonth">
|
||||
<SvgIcon name="arrowLeft" :size="32" color="#8B7E7E" />
|
||||
<Icon name="arrowLeft" :size="32" color="#8B7E7E" />
|
||||
</view>
|
||||
<text class="month-text">{{ formatMonth(currentMonth) }}</text>
|
||||
<view class="arrow" :class="{ disabled: isCurrentMonth }" @tap="nextMonth">
|
||||
<SvgIcon name="arrowRight" :size="32" :color="isCurrentMonth ? '#E0D6D0' : '#8B7E7E'" />
|
||||
<Icon name="arrowRight" :size="32" :color="isCurrentMonth ? '#E0D6D0' : '#8B7E7E'" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -74,7 +76,7 @@
|
||||
</view>
|
||||
|
||||
<view class="state-box" v-if="loadError">
|
||||
<SvgIcon name="alert-circle" :size="48" color="#BFB3B3" />
|
||||
<Icon name="alert-circle" :size="48" color="#BFB3B3" />
|
||||
<text class="state-text">加载失败,请稍后重试</text>
|
||||
</view>
|
||||
|
||||
@@ -121,7 +123,7 @@ import { onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { useStatsStore } from '@/stores/stats'
|
||||
import { formatAmount, formatMonth, getCurrentMonth } from '@/utils/format'
|
||||
import { statusBarHeight } from '@/utils/system'
|
||||
import SvgIcon from '@/components/SvgIcon/SvgIcon.vue'
|
||||
import Icon from '@/components/Icon/Icon.vue'
|
||||
import Skeleton from '@/components/Skeleton/Skeleton.vue'
|
||||
import ChartWrapper from '@/components/ChartWrapper/ChartWrapper.vue'
|
||||
|
||||
@@ -235,15 +237,17 @@ async function loadData() {
|
||||
}
|
||||
}
|
||||
|
||||
// Only re-fetch category/trend data (not overview) when tab switches
|
||||
// Re-fetch all data when tab switches
|
||||
async function loadTabData() {
|
||||
try {
|
||||
await Promise.all([
|
||||
statsStore.fetchOverview(currentMonth.value),
|
||||
statsStore.fetchCategoryStats(currentMonth.value, tabType.value),
|
||||
statsStore.fetchTrend(currentMonth.value, tabType.value),
|
||||
fetchMaxSingle(),
|
||||
fetchPrevMonthData()
|
||||
])
|
||||
overview.value = statsStore.overview
|
||||
categoryStats.value = statsStore.categoryStats
|
||||
trendData.value = statsStore.trendData
|
||||
} catch (e) {
|
||||
@@ -311,6 +315,13 @@ function getBarWidth(amount: number) {
|
||||
padding: 0 0 180rpx;
|
||||
}
|
||||
|
||||
.header-fixed {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: #FFF8F0;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
background: #FFF8F0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user