fix
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 16s
Docker Build & Deploy / Deploy to Production (push) Successful in 6s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s

This commit is contained in:
SunCheng
2026-02-20 14:57:19 +08:00
parent 6e95568906
commit 32d5ed62d0
27 changed files with 1520 additions and 1114 deletions

View File

@@ -1,30 +1,45 @@
<template>
<PopupContainer
<PopupContainerV2
v-model:show="show"
title="新增交易分类"
show-cancel-button
show-confirm-button
confirm-text="确认"
cancel-text="取消"
@confirm="handleConfirm"
@cancel="resetAddForm"
:height="'auto'"
>
<van-form ref="addFormRef">
<van-field
v-model="classifyName"
name="name"
label="分类名称"
placeholder="请输入分类名称"
:rules="[{ required: true, message: '请输入分类名称' }]"
/>
</van-form>
</PopupContainer>
<div style="padding: 16px">
<van-form ref="addFormRef">
<van-field
v-model="classifyName"
name="name"
label="分类名称"
placeholder="请输入分类名称"
:rules="[{ required: true, message: '请输入分类名称' }]"
/>
</van-form>
</div>
<template #footer>
<div style="display: flex; gap: 12px">
<van-button
plain
style="flex: 1"
@click="resetAddForm"
>
取消
</van-button>
<van-button
type="primary"
style="flex: 1"
@click="handleConfirm"
>
确认
</van-button>
</div>
</template>
</PopupContainerV2>
</template>
<script setup>
import { ref } from 'vue'
import { showToast } from 'vant'
import PopupContainer from './PopupContainer.vue'
import PopupContainerV2 from './PopupContainerV2.vue'
const emit = defineEmits(['confirm'])

View File

@@ -209,10 +209,10 @@
</div>
<!-- 关联账单列表弹窗 -->
<PopupContainer
v-model="showBillListModal"
<PopupContainerV2
v-model:show="showBillListModal"
title="关联账单列表"
height="75%"
:height="'75%'"
>
<BillListComponent
data-source="custom"
@@ -225,7 +225,7 @@
@click="handleBillClick"
@delete="handleBillDelete"
/>
</PopupContainer>
</PopupContainerV2>
</div>
<!-- 不记额预算卡片 -->
@@ -406,10 +406,10 @@
</div>
<!-- 关联账单列表弹窗 -->
<PopupContainer
v-model="showBillListModal"
<PopupContainerV2
v-model:show="showBillListModal"
title="关联账单列表"
height="75%"
:height="'75%'"
>
<BillListComponent
data-source="custom"
@@ -422,14 +422,14 @@
@click="handleBillClick"
@delete="handleBillDelete"
/>
</PopupContainer>
</PopupContainerV2>
</div>
</template>
<script setup>
import { computed, ref } from 'vue'
import { BudgetPeriodType } from '@/constants/enums'
import PopupContainer from '@/components/PopupContainer.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.vue'
import BillListComponent from '@/components/Bill/BillListComponent.vue'
import { getTransactionList } from '@/api/transactionRecord'

View File

@@ -187,13 +187,14 @@
</div>
<!-- 详细描述弹窗 -->
<PopupContainer
v-model="showDescriptionPopup"
<PopupContainerV2
v-model:show="showDescriptionPopup"
:title="activeDescTab === 'month' ? '预算额度/实际详情(月度)' : '预算额度/实际详情(年度)'"
height="70%"
:height="'70%'"
>
<div
class="rich-html-content popup-content-padding"
class="rich-html-content"
style="padding: 16px"
v-html="
activeDescTab === 'month'
? overallStats.month?.description ||
@@ -202,14 +203,14 @@
'<p style=\'text-align:center;color:var(--van-text-color-3)\'>暂无数据</p>'
"
/>
</PopupContainer>
</PopupContainerV2>
</template>
<script setup>
import { ref, computed } from 'vue'
import { BudgetCategory, BudgetPeriodType } from '@/constants/enums'
import { getCssVar } from '@/utils/theme'
import PopupContainer from '@/components/PopupContainer.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.vue'
import BaseChart from '@/components/Charts/BaseChart.vue'
import { useChartTheme } from '@/composables/useChartTheme'
import { chartjsGaugePlugin } from '@/plugins/chartjs-gauge-plugin'

View File

@@ -1,18 +1,18 @@
<template>
<PopupContainer
v-model="visible"
<PopupContainerV2
v-model:show="visible"
:title="
isEdit
? `编辑${getCategoryName(form.category)}预算`
: `新增${getCategoryName(form.category)}预算`
"
height="75%"
:height="'75%'"
>
<div class="add-budget-form">
<van-form>
<van-cell-group inset>
<van-field
v-model="form.name"
v-model:show="form.name"
name="name"
label="预算名称"
placeholder="例如:每月餐饮、年度奖金"
@@ -22,7 +22,7 @@
<van-field label="不记额预算">
<template #input>
<van-checkbox
v-model="form.noLimit"
v-model:show="form.noLimit"
@update:model-value="onNoLimitChange"
>
不记额预算
@@ -34,7 +34,7 @@
<template #input>
<div class="mandatory-wrapper">
<van-checkbox
v-model="form.isMandatoryExpense"
v-model:show="form.isMandatoryExpense"
:disabled="form.noLimit"
>
{{ form.category === BudgetCategory.Expense ? '硬性消费' : '硬性收入' }}
@@ -49,7 +49,7 @@
>
<template #input>
<van-radio-group
v-model="form.type"
v-model:show="form.type"
direction="horizontal"
:disabled="isEdit || form.noLimit"
>
@@ -65,7 +65,7 @@
<!-- 仅当未选中"不记额预算"时显示预算金额 -->
<van-field
v-if="!form.noLimit"
v-model="form.limit"
v-model:show="form.limit"
type="number"
name="limit"
label="预算金额"
@@ -95,7 +95,7 @@
</template>
</van-field>
<ClassifySelector
v-model="form.selectedCategories"
v-model:show="form.selectedCategories"
:type="budgetType"
multiple
:show-add="false"
@@ -114,7 +114,7 @@
保存预算
</van-button>
</template>
</PopupContainer>
</PopupContainerV2>
</template>
<script setup>
@@ -122,7 +122,7 @@ import { ref, reactive, computed } from 'vue'
import { showToast } from 'vant'
import { createBudget, updateBudget } from '@/api/budget'
import { BudgetPeriodType, BudgetCategory } from '@/constants/enums'
import PopupContainer from '@/components/PopupContainer.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.vue'
import ClassifySelector from '@/components/ClassifySelector.vue'
const emit = defineEmits(['success'])

View File

@@ -1,8 +1,8 @@
<template>
<PopupContainer
v-model="visible"
<PopupContainerV2
v-model:show="visible"
title="设置存款分类"
height="60%"
:height="'60%'"
>
<div class="savings-config-content">
<div class="config-header">
@@ -16,7 +16,7 @@
可多选分类
</div>
<ClassifySelector
v-model="selectedCategories"
v-model:show="selectedCategories"
:type="2"
multiple
:show-add="false"
@@ -35,14 +35,14 @@
保存配置
</van-button>
</template>
</PopupContainer>
</PopupContainerV2>
</template>
<script setup>
import { ref } from 'vue'
import { showToast, showLoadingToast, closeToast } from 'vant'
import { getConfig, setConfig } from '@/api/config'
import PopupContainer from '@/components/PopupContainer.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.vue'
import ClassifySelector from '@/components/ClassifySelector.vue'
const emit = defineEmits(['success'])

View File

@@ -1,111 +1,120 @@
<template>
<PopupContainer
<PopupContainerV2
v-model:show="visible"
:title="title"
:subtitle="total > 0 ? `共 ${total} 笔交易` : ''"
:closeable="true"
:height="'80%'"
>
<!-- 交易列表 -->
<div class="transactions">
<!-- 加载状态 -->
<van-loading
v-if="loading && transactions.length === 0"
class="txn-loading"
size="24px"
vertical
>
加载中...
</van-loading>
<!-- 空状态 -->
<div style="padding: 0">
<!-- Subtitle 作为内容区域顶部 -->
<div
v-else-if="transactions.length === 0"
class="txn-empty"
v-if="total > 0"
style="padding: 12px 16px; text-align: center; color: #999; font-size: 14px; border-bottom: 1px solid var(--van-border-color)"
>
<div class="empty-icon">
<van-icon
name="balance-list-o"
size="48"
/>
</div>
<div class="empty-text">
暂无交易记录
</div>
{{ total }} 笔交易
</div>
<!-- 交易列表 -->
<div
v-else
class="txn-list"
>
<div
v-for="txn in transactions"
:key="txn.id"
class="txn-card"
@click="onTransactionClick(txn)"
<div class="transactions">
<!-- 加载状态 -->
<van-loading
v-if="loading && transactions.length === 0"
class="txn-loading"
size="24px"
vertical
>
<div
class="txn-icon"
:style="{ backgroundColor: txn.iconBg }"
>
加载中...
</van-loading>
<!-- 空状态 -->
<div
v-else-if="transactions.length === 0"
class="txn-empty"
>
<div class="empty-icon">
<van-icon
:name="txn.icon"
:color="txn.iconColor"
name="balance-list-o"
size="48"
/>
</div>
<div class="txn-content">
<div class="txn-name">
{{ txn.reason }}
</div>
<div class="txn-footer">
<div class="txn-time">
{{ formatDateTime(txn.occurredAt) }}
</div>
<span
v-if="txn.classify"
class="txn-classify-tag"
:class="txn.type === 1 ? 'tag-income' : 'tag-expense'"
>
{{ txn.classify }}
</span>
</div>
</div>
<div class="txn-amount">
{{ formatAmount(txn.amount, txn.type) }}
<div class="empty-text">
暂无交易记录
</div>
</div>
<!-- 加载更多 -->
<div
v-if="!finished"
class="load-more"
>
<van-loading
v-if="loading"
size="20px"
>
加载中...
</van-loading>
<van-button
v-else
type="primary"
size="small"
@click="loadMore"
>
加载更多
</van-button>
</div>
<!-- 已加载全部 -->
<!-- 交易列表 -->
<div
v-else
class="finished-text"
class="txn-list"
>
已加载全部
<div
v-for="txn in transactions"
:key="txn.id"
class="txn-card"
@click="onTransactionClick(txn)"
>
<div
class="txn-icon"
:style="{ backgroundColor: txn.iconBg }"
>
<van-icon
:name="txn.icon"
:color="txn.iconColor"
/>
</div>
<div class="txn-content">
<div class="txn-name">
{{ txn.reason }}
</div>
<div class="txn-footer">
<div class="txn-time">
{{ formatDateTime(txn.occurredAt) }}
</div>
<span
v-if="txn.classify"
class="txn-classify-tag"
:class="txn.type === 1 ? 'tag-income' : 'tag-expense'"
>
{{ txn.classify }}
</span>
</div>
</div>
<div class="txn-amount">
{{ formatAmount(txn.amount, txn.type) }}
</div>
</div>
<!-- 加载更多 -->
<div
v-if="!finished"
class="load-more"
>
<van-loading
v-if="loading"
size="20px"
>
加载中...
</van-loading>
<van-button
v-else
type="primary"
size="small"
@click="loadMore"
>
加载更多
</van-button>
</div>
<!-- 已加载全部 -->
<div
v-else
class="finished-text"
>
已加载全部
</div>
</div>
</div>
</div>
</PopupContainer>
</PopupContainerV2>
<!-- 交易详情弹窗 -->
<TransactionDetailSheet
@@ -120,7 +129,7 @@
import { ref, computed, watch } from 'vue'
import { showToast } from 'vant'
import TransactionDetailSheet from '@/components/Transaction/TransactionDetailSheet.vue'
import PopupContainer from '@/components/PopupContainer.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.vue'
import { getTransactionList } from '@/api/transactionRecord'
const props = defineProps({

View File

@@ -9,41 +9,43 @@
</div>
<!-- Add Bill Modal -->
<PopupContainer
v-model="showAddBill"
<PopupContainerV2
v-model:show="showAddBill"
title="记一笔"
height="75%"
:height="'75%'"
>
<van-tabs
v-model:active="activeTab"
shrink
>
<van-tab
title="一句话录账"
name="one"
<div style="padding: 0">
<van-tabs
v-model:active="activeTab"
shrink
>
<OneLineBillAdd
:key="componentKey"
@success="handleSuccess"
/>
</van-tab>
<van-tab
title="手动录账"
name="manual"
>
<ManualBillAdd
:key="componentKey"
@success="handleSuccess"
/>
</van-tab>
</van-tabs>
</PopupContainer>
<van-tab
title="一句话录账"
name="one"
>
<OneLineBillAdd
:key="componentKey"
@success="handleSuccess"
/>
</van-tab>
<van-tab
title="手动录账"
name="manual"
>
<ManualBillAdd
:key="componentKey"
@success="handleSuccess"
/>
</van-tab>
</van-tabs>
</div>
</PopupContainerV2>
</div>
</template>
<script setup>
import { ref, defineEmits } from 'vue'
import PopupContainer from '@/components/PopupContainer.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.vue'
import OneLineBillAdd from '@/components/Bill/OneLineBillAdd.vue'
import ManualBillAdd from '@/components/Bill/ManualBillAdd.vue'

View File

@@ -1,14 +1,9 @@
<template>
<PopupContainer
<PopupContainerV2
:show="show"
:title="title"
show-cancel-button
show-confirm-button
confirm-text="选择"
cancel-text="取消"
:height="'80%'"
@update:show="emit('update:show', $event)"
@confirm="handleConfirm"
@cancel="handleCancel"
>
<div class="icon-selector">
<!-- 搜索框 -->
@@ -56,14 +51,32 @@
@change="handlePageChange"
/>
</div>
</PopupContainer>
<template #footer>
<div style="display: flex; gap: 12px">
<van-button
plain
style="flex: 1"
@click="handleCancel"
>
取消
</van-button>
<van-button
type="primary"
style="flex: 1"
@click="handleConfirm"
>
选择
</van-button>
</div>
</template>
</PopupContainerV2>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { showToast } from 'vant'
import Icon from './Icon.vue'
import PopupContainer from './PopupContainer.vue'
import PopupContainerV2 from './PopupContainerV2.vue'
const props = defineProps({
show: {

View File

@@ -1,277 +0,0 @@
<!--
统一弹窗组件
## 基础用法
<PopupContainer v-model:show="show" title="标题">
内容
</PopupContainer>
## 确认对话框用法
<PopupContainer
v-model:show="showConfirm"
title="确认操作"
show-confirm-button
show-cancel-button
confirm-text="确定"
cancel-text="取消"
@confirm="handleConfirm"
@cancel="handleCancel"
>
确定要执行此操作吗
</PopupContainer>
## 带副标题和页脚
<PopupContainer
v-model:show="show"
title="分类详情"
subtitle="共 10 笔交易"
>
内容区域
<template #footer>
<van-button type="primary">提交</van-button>
</template>
</PopupContainer>
-->
<!-- eslint-disable vue/no-v-html -->
<template>
<van-popup
v-model:show="visible"
position="bottom"
:style="{ height: height }"
round
:closeable="closeable"
teleport="body"
>
<div class="popup-container">
<!-- 头部区域 -->
<div class="popup-header-fixed">
<!-- 标题行 (无子标题且有操作时使用 Grid 布局) -->
<div
class="header-title-row"
:class="{ 'has-actions': !subtitle && hasActions }"
>
<h3 class="popup-title">
{{ title }}
</h3>
<!-- 无子标题时操作按钮与标题同行 -->
<div
v-if="!subtitle && hasActions"
class="header-actions-inline"
>
<slot name="header-actions" />
</div>
</div>
<!-- 子标题/统计信息 -->
<div
v-if="subtitle"
class="header-stats"
>
<span
class="stats-text"
v-html="subtitle"
/>
<!-- 额外操作插槽 -->
<slot
v-if="hasActions"
name="header-actions"
/>
</div>
</div>
<!-- 内容区域可滚动 -->
<div class="popup-scroll-content">
<slot />
</div>
<!-- 底部页脚固定不可滚动 -->
<div
v-if="slots.footer || showConfirmButton || showCancelButton"
class="popup-footer-fixed"
>
<!-- 用户自定义页脚插槽 -->
<slot name="footer">
<!-- 默认确认/取消按钮 -->
<div class="footer-buttons">
<van-button
v-if="showCancelButton"
plain
@click="handleCancel"
>
{{ cancelText }}
</van-button>
<van-button
v-if="showConfirmButton"
type="primary"
@click="handleConfirm"
>
{{ confirmText }}
</van-button>
</div>
</slot>
</div>
</div>
</van-popup>
</template>
<script setup>
import { computed, useSlots } from 'vue'
const props = defineProps({
modelValue: {
type: Boolean,
required: true
},
title: {
type: String,
default: ''
},
subtitle: {
type: String,
default: ''
},
height: {
type: String,
default: '80%'
},
closeable: {
type: Boolean,
default: true
},
showConfirmButton: {
type: Boolean,
default: false
},
showCancelButton: {
type: Boolean,
default: false
},
confirmText: {
type: String,
default: '确认'
},
cancelText: {
type: String,
default: '取消'
}
})
const emit = defineEmits(['update:modelValue', 'confirm', 'cancel'])
const slots = useSlots()
// 双向绑定
const visible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
// 判断是否有操作按钮
const hasActions = computed(() => !!slots['header-actions'])
// 确认按钮点击
const handleConfirm = () => {
emit('confirm')
}
// 取消按钮点击
const handleCancel = () => {
emit('cancel')
}
</script>
<style scoped>
.popup-container {
height: 100%;
display: flex;
flex-direction: column;
}
.popup-header-fixed {
flex-shrink: 0;
padding: 16px;
background: linear-gradient(180deg, var(--van-background) 0%, var(--van-background-2) 100%);
border-bottom: 1px solid var(--van-border-color);
position: sticky;
top: 0;
z-index: 10;
}
.header-title-row.has-actions {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
}
.header-title-row.has-actions .popup-title {
grid-column: 2;
min-width: 0;
}
.header-actions-inline {
grid-column: 3;
justify-self: end;
display: flex;
align-items: center;
}
.popup-title {
font-size: 16px;
font-weight: 600;
margin: 0;
text-align: center;
color: var(--van-text-color);
letter-spacing: -0.02em;
/*超出长度*/
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.header-stats {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
gap: 12px;
margin-top: 12px;
}
.stats-text {
margin: 0;
font-size: 14px;
color: var(--van-text-color-2);
grid-column: 2;
text-align: center;
}
/* 按钮区域放在右侧 */
.header-stats :deep(> :last-child:not(.stats-text)) {
grid-column: 3;
justify-self: end;
}
.popup-scroll-content {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
.popup-footer-fixed {
flex-shrink: 0;
border-top: 1px solid var(--van-border-color);
background-color: var(--van-background-2);
padding: 12px 16px;
}
.footer-buttons {
display: flex;
gap: 12px;
justify-content: flex-end;
}
.footer-buttons .van-button {
flex: 1;
max-width: 120px;
}
</style>

View File

@@ -14,7 +14,7 @@ PopupContainer V2 - 通用底部弹窗组件(采用 TransactionDetailSheet 样
</PopupContainerV2>
## Props
- modelValue (Boolean, required): 控制弹窗显示/隐藏
- show (Boolean, required): 控制弹窗显示/隐藏
- title (String, required): 标题文本
- height (String, default: 'auto'): 弹窗高度支持 'auto', '80%', '500px'
- maxHeight (String, default: '85%'): 最大高度
@@ -24,7 +24,7 @@ PopupContainer V2 - 通用底部弹窗组件(采用 TransactionDetailSheet 样
- footer: 固定底部区域操作按钮等
## Events
- update:modelValue: 弹窗显示/隐藏状态变更
- update:show: 弹窗显示/隐藏状态变更
-->
<template>
<van-popup
@@ -71,7 +71,7 @@ PopupContainer V2 - 通用底部弹窗组件(采用 TransactionDetailSheet 样
import { computed, useSlots } from 'vue'
const props = defineProps({
modelValue: {
show: {
type: Boolean,
required: true
},
@@ -89,14 +89,14 @@ const props = defineProps({
}
})
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits(['update:show'])
const slots = useSlots()
// 双向绑定
const visible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
get: () => props.show,
set: (value) => emit('update:show', value)
})
// 判断是否有 footer 插槽

View File

@@ -61,34 +61,42 @@
</van-cell-group>
<!-- 账单列表弹窗 -->
<PopupContainer
v-model="showTransactionList"
<PopupContainerV2
v-model:show="showTransactionList"
:title="selectedGroup?.reason || '交易记录'"
:subtitle="groupTransactionsTotal ? `共 ${groupTransactionsTotal} 笔交易` : ''"
height="75%"
:height="'75%'"
>
<template #header-actions>
<van-button
type="primary"
size="small"
class="batch-classify-btn"
@click.stop="handleBatchClassify(selectedGroup)"
>
批量分类
</van-button>
</template>
<div style="padding: 0">
<!-- Subtitle 和操作按钮 -->
<div style="padding: 12px 16px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--van-border-color)">
<span
v-if="groupTransactionsTotal"
style="color: #999; font-size: 14px"
>
{{ groupTransactionsTotal }} 笔交易
</span>
<van-button
type="primary"
size="small"
class="batch-classify-btn"
@click.stop="handleBatchClassify(selectedGroup)"
>
批量分类
</van-button>
</div>
<BillListComponent
data-source="custom"
:transactions="groupTransactions"
:loading="transactionLoading"
:finished="transactionFinished"
:enable-filter="false"
@load="loadGroupTransactions"
@click="handleTransactionClick"
@delete="handleGroupTransactionDelete"
/>
</PopupContainer>
<BillListComponent
data-source="custom"
:transactions="groupTransactions"
:loading="transactionLoading"
:finished="transactionFinished"
:enable-filter="false"
@load="loadGroupTransactions"
@click="handleTransactionClick"
@delete="handleGroupTransactionDelete"
/>
</div>
</PopupContainerV2>
<!-- 账单详情弹窗 -->
<TransactionDetail
@@ -98,76 +106,78 @@
/>
<!-- 批量设置对话框 -->
<PopupContainer
v-model="showBatchDialog"
<PopupContainerV2
v-model:show="showBatchDialog"
title="批量设置分类"
height="60%"
:height="'60%'"
>
<van-form
ref="batchFormRef"
class="setting-form"
>
<van-cell-group inset>
<!-- 显示选中的摘要 -->
<van-field
:model-value="batchGroup?.reason"
label="交易摘要"
readonly
input-align="left"
/>
<div style="padding: 0">
<van-form
ref="batchFormRef"
class="setting-form"
>
<van-cell-group inset>
<!-- 显示选中的摘要 -->
<van-field
:model-value="batchGroup?.reason"
label="交易摘要"
readonly
input-align="left"
/>
<!-- 显示记录数量 -->
<van-field
:model-value="`${batchGroup?.count || 0} `"
label="记录数量"
readonly
input-align="left"
/>
<!-- 显示记录数量 -->
<van-field
:model-value="`${batchGroup?.count || 0} `"
label="记录数量"
readonly
input-align="left"
/>
<!-- 交易类型 -->
<van-field
name="type"
label="交易类型"
>
<template #input>
<van-radio-group
v-model="batchForm.type"
direction="horizontal"
>
<van-radio :name="0">
支出
</van-radio>
<van-radio :name="1">
收入
</van-radio>
<van-radio :name="2">
不计
</van-radio>
</van-radio-group>
</template>
</van-field>
<!-- 交易类型 -->
<van-field
name="type"
label="交易类型"
>
<template #input>
<van-radio-group
v-model="batchForm.type"
direction="horizontal"
>
<van-radio :name="0">
支出
</van-radio>
<van-radio :name="1">
收入
</van-radio>
<van-radio :name="2">
不计
</van-radio>
</van-radio-group>
</template>
</van-field>
<!-- 分类选择 -->
<van-field
name="classify"
label="分类"
>
<template #input>
<span
v-if="!batchForm.classify"
style="opacity: 0.4"
>请选择分类</span>
<span v-else>{{ batchForm.classify }}</span>
</template>
</van-field>
<!-- 分类选择 -->
<van-field
name="classify"
label="分类"
>
<template #input>
<span
v-if="!batchForm.classify"
style="opacity: 0.4"
>请选择分类</span>
<span v-else>{{ batchForm.classify }}</span>
</template>
</van-field>
<!-- 分类选择组件 -->
<ClassifySelector
v-model="batchForm.classify"
:type="batchForm.type"
/>
</van-cell-group>
</van-form>
<!-- 分类选择组件 -->
<ClassifySelector
v-model="batchForm.classify"
:type="batchForm.type"
/>
</van-cell-group>
</van-form>
</div>
<template #footer>
<van-button
round
@@ -178,7 +188,7 @@
确定
</van-button>
</template>
</PopupContainer>
</PopupContainerV2>
</div>
</template>
@@ -189,7 +199,7 @@ import { getReasonGroups, batchUpdateByReason, getTransactionList } from '@/api/
import ClassifySelector from './ClassifySelector.vue'
import BillListComponent from './Bill/BillListComponent.vue'
import TransactionDetail from './TransactionDetail.vue'
import PopupContainer from './PopupContainer.vue'
import PopupContainerV2 from './PopupContainerV2.vue'
const props = defineProps({
// 是否支持多选

View File

@@ -1,134 +1,135 @@
<template>
<PopupContainer
v-model="visible"
<PopupContainerV2
v-model:show="visible"
title="交易详情"
height="75%"
:closeable="false"
:height="'75%'"
>
<van-form style="margin-top: 12px">
<van-cell-group inset>
<van-cell
title="记录时间"
:value="formatDate(transaction.createTime)"
/>
</van-cell-group>
<div style="padding: 0">
<van-form style="margin-top: 12px">
<van-cell-group inset>
<van-cell
title="记录时间"
:value="formatDate(transaction.createTime)"
/>
</van-cell-group>
<van-cell-group
inset
title="交易明细"
>
<van-field
v-model="occurredAtLabel"
name="occurredAt"
label="交易时间"
readonly
is-link
placeholder="请选择交易时间"
:rules="[{ required: true, message: '请选择交易时间' }]"
@click="showDatePicker = true"
/>
<van-field
v-model="editForm.reason"
name="reason"
label="交易摘要"
placeholder="请输入交易摘要"
type="textarea"
rows="2"
autosize
maxlength="200"
show-word-limit
/>
<van-field
v-model="editForm.amount"
name="amount"
label="交易金额"
placeholder="请输入交易金额"
type="number"
:rules="[{ required: true, message: '请输入交易金额' }]"
/>
<van-field
v-model="editForm.balance"
name="balance"
label="交易后余额"
placeholder="请输入交易后余额"
type="number"
:rules="[{ required: true, message: '请输入交易后余额' }]"
/>
<van-field
name="type"
label="交易类型"
<van-cell-group
inset
title="交易明细"
>
<template #input>
<van-radio-group
v-model="editForm.type"
direction="horizontal"
@change="handleTypeChange"
>
<van-radio :name="0">
支出
</van-radio>
<van-radio :name="1">
收入
</van-radio>
<van-radio :name="2">
不计
</van-radio>
</van-radio-group>
</template>
</van-field>
<van-field
v-model="occurredAtLabel"
name="occurredAt"
label="交易时间"
readonly
is-link
placeholder="请选择交易时间"
:rules="[{ required: true, message: '请选择交易时间' }]"
@click="showDatePicker = true"
/>
<van-field
v-model="editForm.reason"
name="reason"
label="交易摘要"
placeholder="请输入交易摘要"
type="textarea"
rows="2"
autosize
maxlength="200"
show-word-limit
/>
<van-field
v-model="editForm.amount"
name="amount"
label="交易金额"
placeholder="请输入交易金额"
type="number"
:rules="[{ required: true, message: '请输入交易金额' }]"
/>
<van-field
v-model="editForm.balance"
name="balance"
label="交易后余额"
placeholder="请输入交易后余额"
type="number"
:rules="[{ required: true, message: '请输入交易后余额' }]"
/>
<van-field
name="classify"
label="交易类"
>
<template #input>
<div style="flex: 1">
<div
v-if="
transaction &&
transaction.unconfirmedClassify &&
transaction.unconfirmedClassify !== editForm.classify
"
class="suggestion-tip"
@click="applySuggestion"
<van-field
name="type"
label="交易类"
>
<template #input>
<van-radio-group
v-model="editForm.type"
direction="horizontal"
@change="handleTypeChange"
>
<van-icon
name="bulb-o"
class="suggestion-icon"
/>
<span class="suggestion-text">
建议: {{ transaction.unconfirmedClassify }}
<span
v-if="
transaction.unconfirmedType !== null &&
transaction.unconfirmedType !== undefined &&
transaction.unconfirmedType !== editForm.type
"
>
({{ getTypeName(transaction.unconfirmedType) }})
</span>
</span>
<div class="suggestion-apply">
应用
</div>
</div>
<span
v-else-if="!editForm.classify"
style="color: var(--van-gray-5)"
>请选择交易分类</span>
<span v-else>{{ editForm.classify }}</span>
</div>
</template>
</van-field>
<van-radio :name="0">
支出
</van-radio>
<van-radio :name="1">
收入
</van-radio>
<van-radio :name="2">
不计
</van-radio>
</van-radio-group>
</template>
</van-field>
<ClassifySelector
v-model="editForm.classify"
:type="editForm.type"
@change="handleClassifyChange"
/>
</van-cell-group>
</van-form>
<van-field
name="classify"
label="交易分类"
>
<template #input>
<div style="flex: 1">
<div
v-if="
transaction &&
transaction.unconfirmedClassify &&
transaction.unconfirmedClassify !== editForm.classify
"
class="suggestion-tip"
@click="applySuggestion"
>
<van-icon
name="bulb-o"
class="suggestion-icon"
/>
<span class="suggestion-text">
建议: {{ transaction.unconfirmedClassify }}
<span
v-if="
transaction.unconfirmedType !== null &&
transaction.unconfirmedType !== undefined &&
transaction.unconfirmedType !== editForm.type
"
>
({{ getTypeName(transaction.unconfirmedType) }})
</span>
</span>
<div class="suggestion-apply">
应用
</div>
</div>
<span
v-else-if="!editForm.classify"
style="color: var(--van-gray-5)"
>请选择交易分类</span>
<span v-else>{{ editForm.classify }}</span>
</div>
</template>
</van-field>
<ClassifySelector
v-model="editForm.classify"
:type="editForm.type"
@change="handleClassifyChange"
/>
</van-cell-group>
</van-form>
</div>
<template #footer>
<van-button
@@ -141,7 +142,7 @@
保存修改
</van-button>
</template>
</PopupContainer>
</PopupContainerV2>
<!-- 日期选择弹窗 -->
<van-popup
@@ -178,7 +179,7 @@
import { ref, reactive, watch, defineProps, defineEmits, computed, nextTick } from 'vue'
import { showToast } from 'vant'
import dayjs from 'dayjs'
import PopupContainer from '@/components/PopupContainer.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.vue'
import ClassifySelector from '@/components/ClassifySelector.vue'
import { updateTransaction } from '@/api/transactionRecord'

View File

@@ -94,26 +94,41 @@
</div>
<!-- 提示词设置弹窗 -->
<PopupContainer
<PopupContainerV2
v-model:show="showPromptDialog"
title="编辑分析提示词"
show-cancel-button
show-confirm-button
confirm-text="保存"
cancel-text="取消"
@confirm="confirmPrompt"
@cancel="showPromptDialog = false"
:height="'75%'"
>
<van-field
v-model="promptValue"
rows="4"
autosize
type="textarea"
maxlength="2000"
placeholder="输入自定义的分析提示词..."
show-word-limit
/>
</PopupContainer>
<div style="padding: 16px">
<van-field
v-model="promptValue"
rows="4"
autosize
type="textarea"
maxlength="2000"
placeholder="输入自定义的分析提示词..."
show-word-limit
/>
</div>
<template #footer>
<div style="display: flex; gap: 12px">
<van-button
plain
style="flex: 1"
@click="showPromptDialog = false"
>
取消
</van-button>
<van-button
type="primary"
style="flex: 1"
@click="confirmPrompt"
>
保存
</van-button>
</div>
</template>
</PopupContainerV2>
</div>
</template>
@@ -122,7 +137,7 @@ import { ref, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { showToast, showLoadingToast, closeToast } from 'vant'
import { getConfig, setConfig } from '@/api/config'
import PopupContainer from '@/components/PopupContainer.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.vue'
const router = useRouter()
const userInput = ref('')

View File

@@ -112,64 +112,107 @@
</div>
<!-- 新增分类对话框 -->
<PopupContainer
<PopupContainerV2
v-model:show="showAddDialog"
title="新增分类"
show-cancel-button
show-confirm-button
confirm-text="确认"
cancel-text="取消"
@confirm="handleConfirmAdd"
@cancel="resetAddForm"
:height="'auto'"
>
<van-form ref="addFormRef">
<van-field
v-model="addForm.name"
name="name"
label="分类名称"
placeholder="请输入分类名称"
:rules="[{ required: true, message: '请输入分类名称' }]"
/>
</van-form>
</PopupContainer>
<div style="padding: 16px">
<van-form ref="addFormRef">
<van-field
v-model="addForm.name"
name="name"
label="分类名称"
placeholder="请输入分类名称"
:rules="[{ required: true, message: '请输入分类名称' }]"
/>
</van-form>
</div>
<template #footer>
<div style="display: flex; gap: 12px">
<van-button
plain
style="flex: 1"
@click="resetAddForm"
>
取消
</van-button>
<van-button
type="primary"
style="flex: 1"
@click="handleConfirmAdd"
>
确认
</van-button>
</div>
</template>
</PopupContainerV2>
<!-- 编辑分类对话框 -->
<PopupContainer
<PopupContainerV2
v-model:show="showEditDialog"
title="编辑分类"
show-cancel-button
show-confirm-button
confirm-text="保存"
cancel-text="取消"
@confirm="handleConfirmEdit"
@cancel="showEditDialog = false"
:height="'auto'"
>
<van-form ref="editFormRef">
<van-field
v-model="editForm.name"
name="name"
label="分类名称"
placeholder="请输入分类名称"
:rules="[{ required: true, message: '请输入分类名称' }]"
/>
</van-form>
</PopupContainer>
<div style="padding: 16px">
<van-form ref="editFormRef">
<van-field
v-model="editForm.name"
name="name"
label="分类名称"
placeholder="请输入分类名称"
:rules="[{ required: true, message: '请输入分类名称' }]"
/>
</van-form>
</div>
<template #footer>
<div style="display: flex; gap: 12px">
<van-button
plain
style="flex: 1"
@click="showEditDialog = false"
>
取消
</van-button>
<van-button
type="primary"
style="flex: 1"
@click="handleConfirmEdit"
>
保存
</van-button>
</div>
</template>
</PopupContainerV2>
<!-- 删除确认对话框 -->
<PopupContainer
<PopupContainerV2
v-model:show="showDeleteConfirm"
title="删除分类"
show-cancel-button
show-confirm-button
confirm-text="确定"
cancel-text="取消"
@confirm="handleConfirmDelete"
@cancel="showDeleteConfirm = false"
:height="'auto'"
>
<p style="text-align: center; padding: 20px; color: var(--van-text-color-2)">
删除后无法恢复确定要删除吗
</p>
</PopupContainer>
<template #footer>
<div style="display: flex; gap: 12px">
<van-button
plain
style="flex: 1"
@click="showDeleteConfirm = false"
>
取消
</van-button>
<van-button
type="primary"
style="flex: 1"
@click="handleConfirmDelete"
>
确定
</van-button>
</div>
</template>
</PopupContainerV2>
<!-- 图标选择对话框 -->
<IconSelector
@@ -189,7 +232,7 @@ import { useRouter } from 'vue-router'
import { showSuccessToast, showToast, showLoadingToast, closeToast } from 'vant'
import Icon from '@/components/Icon.vue'
import IconSelector from '@/components/IconSelector.vue'
import PopupContainer from '@/components/PopupContainer.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.vue'
import {
getCategoryList,
createCategory,

View File

@@ -71,12 +71,12 @@
/>
<!-- 记录列表弹窗 -->
<PopupContainer
v-model="showRecordsList"
<PopupContainerV2
v-model:show="showRecordsList"
title="交易记录列表"
height="75%"
:height="'75%'"
>
<div style="background: var(--van-background)">
<div style="background: var(--van-background); padding: 0">
<!-- 批量操作按钮 -->
<div class="batch-actions">
<van-button
@@ -122,7 +122,7 @@
/>
</div>
</div>
</PopupContainer>
</PopupContainerV2>
</div>
</template>
@@ -133,7 +133,7 @@ import { showToast, showConfirmDialog } from 'vant'
import { nlpAnalysis, batchUpdateClassify } from '@/api/transactionRecord'
import BillListComponent from '@/components/Bill/BillListComponent.vue'
import TransactionDetail from '@/components/TransactionDetail.vue'
import PopupContainer from '@/components/PopupContainer.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.vue'
const router = useRouter()
const userInput = ref('')

View File

@@ -73,23 +73,24 @@
</van-pull-refresh>
<!-- 详情弹出层 -->
<PopupContainer
v-model="detailVisible"
<PopupContainerV2
v-model:show="detailVisible"
:title="currentEmail ? currentEmail.Subject || currentEmail.subject || '(无主题)' : ''"
height="75%"
:height="'75%'"
>
<template #header-actions>
<van-button
size="small"
type="primary"
:loading="refreshingAnalysis"
@click="handleRefreshAnalysis"
>
重新分析
</van-button>
</template>
<div v-if="currentEmail">
<!-- 操作按钮栏 -->
<div style="padding: 12px 16px; text-align: right; border-bottom: 1px solid var(--van-border-color)">
<van-button
size="small"
type="primary"
:loading="refreshingAnalysis"
@click="handleRefreshAnalysis"
>
重新分析
</van-button>
</div>
<van-cell-group
inset
style="margin-top: 12px"
@@ -140,13 +141,13 @@
</div>
</div>
</div>
</PopupContainer>
</PopupContainerV2>
<!-- 账单列表弹出层 -->
<PopupContainer
v-model="transactionListVisible"
<PopupContainerV2
v-model:show="transactionListVisible"
title="关联账单列表"
height="75%"
:height="'75%'"
>
<BillListComponent
data-source="custom"
@@ -158,7 +159,7 @@
@click="handleTransactionClick"
@delete="handleTransactionDelete"
/>
</PopupContainer>
</PopupContainerV2>
<!-- 账单详情编辑弹出层 -->
<TransactionDetail
@@ -184,7 +185,7 @@ import {
import { getTransactionDetail } from '@/api/transactionRecord'
import BillListComponent from '@/components/Bill/BillListComponent.vue'
import TransactionDetail from '@/components/TransactionDetail.vue'
import PopupContainer from '@/components/PopupContainer.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.vue'
const emailList = ref([])
const loading = ref(false)

View File

@@ -71,22 +71,27 @@
</van-pull-refresh>
<!-- 详情弹出层 -->
<PopupContainer
v-model="detailVisible"
<PopupContainerV2
v-model:show="detailVisible"
:title="currentMessage.title"
:subtitle="currentMessage.createTime"
height="75%"
:height="'75%'"
>
<div
v-if="currentMessage.messageType === 2"
class="detail-content rich-html-content"
v-html="currentMessage.content"
/>
<div
v-else
class="detail-content"
>
{{ currentMessage.content }}
<div style="padding: 16px">
<p style="color: #999; font-size: 14px; margin-bottom: 12px; margin-top: 0">
{{ currentMessage.createTime }}
</p>
<div
v-if="currentMessage.messageType === 2"
class="rich-html-content"
style="font-size: 14px; line-height: 1.6"
v-html="currentMessage.content"
/>
<div
v-else
style="font-size: 14px; line-height: 1.6; white-space: pre-wrap"
>
{{ currentMessage.content }}
</div>
</div>
<template
v-if="currentMessage.url && currentMessage.messageType === 1"
@@ -101,7 +106,7 @@
查看详情
</van-button>
</template>
</PopupContainer>
</PopupContainerV2>
</div>
</template>
@@ -111,7 +116,7 @@ import { useRouter } from 'vue-router'
import { showToast, showDialog } from 'vant'
import { getMessageList, markAsRead, deleteMessage, markAllAsRead } from '@/api/message'
import { useMessageStore } from '@/stores/message'
import PopupContainer from '@/components/PopupContainer.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.vue'
const messageStore = useMessageStore()
const router = useRouter()
@@ -325,22 +330,6 @@ defineExpose({
height: 100%;
}
.detail-time {
color: var(--van-text-color-2);
font-size: 14px;
}
.detail-content {
padding: 16px;
font-size: 14px;
line-height: 1.6;
color: var(--van-text-color);
}
.detail-content:not(.rich-html-content) {
white-space: pre-wrap;
}
:deep(.van-pull-refresh) {
flex: 1;
overflow-y: auto;

View File

@@ -107,141 +107,143 @@
</div>
<!-- 新增/编辑弹窗 -->
<PopupContainer
v-model="dialogVisible"
<PopupContainerV2
v-model:show="dialogVisible"
:title="isEdit ? '编辑周期账单' : '新增周期账单'"
height="75%"
:height="'75%'"
>
<van-form>
<van-cell-group
inset
title="周期设置"
>
<van-field
v-model="form.periodicTypeText"
is-link
readonly
name="periodicType"
label="周期"
placeholder="请选择周期类型"
:rules="[{ required: true, message: '请选择周期类型' }]"
@click="showPeriodicTypePicker = true"
/>
<!-- 每周配置 -->
<van-field
v-if="form.periodicType === 1"
v-model="form.weekdaysText"
is-link
readonly
name="weekdays"
label="星期"
placeholder="请选择星期几"
:rules="[{ required: true, message: '请选择星期几' }]"
@click="showWeekdaysPicker = true"
/>
<!-- 每月配置 -->
<van-field
v-if="form.periodicType === 2"
v-model="form.monthDaysText"
is-link
readonly
name="monthDays"
label="日期"
placeholder="请选择每月的日期"
:rules="[{ required: true, message: '请选择日期' }]"
@click="showMonthDaysPicker = true"
/>
<!-- 每季度配置 -->
<van-field
v-if="form.periodicType === 3"
v-model="form.quarterDay"
name="quarterDay"
label="季度第几天"
placeholder="请输入季度开始后第几天"
type="number"
:rules="[{ required: true, message: '请输入季度开始后第几天' }]"
/>
<!-- 每年配置 -->
<van-field
v-if="form.periodicType === 4"
v-model="form.yearDay"
name="yearDay"
label="年第几天"
placeholder="请输入年开始后第几天"
type="number"
:rules="[{ required: true, message: '请输入年开始后第几天' }]"
/>
</van-cell-group>
<van-cell-group
inset
title="基本信息"
>
<van-field
v-model="form.reason"
name="reason"
label="摘要"
placeholder="请输入交易摘要"
type="textarea"
rows="2"
autosize
maxlength="200"
show-word-limit
/>
<van-field
v-model="form.amount"
name="amount"
label="金额"
placeholder="请输入金额"
type="number"
:rules="[{ required: true, message: '请输入金额' }]"
/>
<van-field
v-model="form.type"
name="type"
label="类型"
<div style="padding: 0">
<van-form>
<van-cell-group
inset
title="周期设置"
>
<template #input>
<van-radio-group
v-model="form.type"
direction="horizontal"
>
<van-radio :value="0">
支出
</van-radio>
<van-radio :value="1">
收入
</van-radio>
<van-radio :value="2">
不计
</van-radio>
</van-radio-group>
</template>
</van-field>
<van-field
name="classify"
label="分类"
>
<template #input>
<span
v-if="!form.classify"
style="color: var(--van-gray-5)"
>请选择交易分类</span>
<span v-else>{{ form.classify }}</span>
</template>
</van-field>
<van-field
v-model="form.periodicTypeText"
is-link
readonly
name="periodicType"
label="周期"
placeholder="请选择周期类型"
:rules="[{ required: true, message: '请选择周期类型' }]"
@click="showPeriodicTypePicker = true"
/>
<!-- 分类选择组件 -->
<ClassifySelector
v-model="form.classify"
:type="form.type"
/>
</van-cell-group>
</van-form>
<!-- 每周配置 -->
<van-field
v-if="form.periodicType === 1"
v-model="form.weekdaysText"
is-link
readonly
name="weekdays"
label="星期"
placeholder="请选择星期几"
:rules="[{ required: true, message: '请选择星期几' }]"
@click="showWeekdaysPicker = true"
/>
<!-- 每月配置 -->
<van-field
v-if="form.periodicType === 2"
v-model="form.monthDaysText"
is-link
readonly
name="monthDays"
label="日期"
placeholder="请选择每月的日期"
:rules="[{ required: true, message: '请选择日期' }]"
@click="showMonthDaysPicker = true"
/>
<!-- 每季度配置 -->
<van-field
v-if="form.periodicType === 3"
v-model="form.quarterDay"
name="quarterDay"
label="季度第几天"
placeholder="请输入季度开始后第几天"
type="number"
:rules="[{ required: true, message: '请输入季度开始后第几天' }]"
/>
<!-- 每年配置 -->
<van-field
v-if="form.periodicType === 4"
v-model="form.yearDay"
name="yearDay"
label="年第几天"
placeholder="请输入年开始后第几天"
type="number"
:rules="[{ required: true, message: '请输入年开始后第几天' }]"
/>
</van-cell-group>
<van-cell-group
inset
title="基本信息"
>
<van-field
v-model="form.reason"
name="reason"
label="摘要"
placeholder="请输入交易摘要"
type="textarea"
rows="2"
autosize
maxlength="200"
show-word-limit
/>
<van-field
v-model="form.amount"
name="amount"
label="金额"
placeholder="请输入金额"
type="number"
:rules="[{ required: true, message: '请输入金额' }]"
/>
<van-field
v-model="form.type"
name="type"
label="类型"
>
<template #input>
<van-radio-group
v-model="form.type"
direction="horizontal"
>
<van-radio :value="0">
支出
</van-radio>
<van-radio :value="1">
收入
</van-radio>
<van-radio :value="2">
不计
</van-radio>
</van-radio-group>
</template>
</van-field>
<van-field
name="classify"
label="分类"
>
<template #input>
<span
v-if="!form.classify"
style="color: var(--van-gray-5)"
>请选择交易分类</span>
<span v-else>{{ form.classify }}</span>
</template>
</van-field>
<!-- 分类选择组件 -->
<ClassifySelector
v-model="form.classify"
:type="form.type"
/>
</van-cell-group>
</van-form>
</div>
<template #footer>
<van-button
round
@@ -253,7 +255,7 @@
{{ isEdit ? '更新' : '确认添加' }}
</van-button>
</template>
</PopupContainer>
</PopupContainerV2>
<!-- 周期类型选择器 -->
<van-popup
@@ -310,7 +312,7 @@ import {
createPeriodic,
updatePeriodic
} from '@/api/transactionPeriodic'
import PopupContainer from '@/components/PopupContainer.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.vue'
import ClassifySelector from '@/components/ClassifySelector.vue'
import dayjs from 'dayjs'

View File

@@ -151,183 +151,43 @@
<!-- 储蓄配置弹窗 -->
<SavingsConfigPopup
ref="savingsConfigRef"
@success="loadBudgetData"
@change="loadBudgetData"
/>
<!-- 预算明细列表弹窗 -->
<PopupContainer
v-model="showListPopup"
:title="popupTitle"
height="80%"
>
<template #header-actions>
<van-icon
name="plus"
size="20"
title="添加预算"
@click="budgetEditRef.open({ category: activeTab })"
/>
</template>
<van-pull-refresh
v-model="refreshing"
style="min-height: 100%"
@refresh="onRefresh"
>
<div class="budget-list">
<!-- 支出列表 -->
<template v-if="activeTab === BudgetCategory.Expense && expenseBudgets?.length > 0">
<van-swipe-cell
v-for="budget in expenseBudgets"
:key="budget.id"
>
<BudgetCard
:budget="budget"
:progress-color="getProgressColor(budget)"
:percent-class="{ warning: budget.current / budget.limit > 0.8 }"
:period-label="getPeriodLabel(budget.type)"
@click="handleEdit(budget)"
>
<template #amount-info>
<div class="info-item">
<div class="label">
已支出
</div>
<div class="value expense">
¥{{ formatMoney(budget.current) }}
</div>
</div>
<div class="info-item">
<div class="label">
预算
</div>
<div class="value">
¥{{ formatMoney(budget.limit) }}
</div>
</div>
<div class="info-item">
<div class="label">
余额
</div>
<div
class="value"
:class="budget.limit - budget.current >= 0 ? 'income' : 'expense'"
>
¥{{ formatMoney(budget.limit - budget.current) }}
</div>
</div>
</template>
</BudgetCard>
<template #right>
<van-button
square
text="删除"
type="danger"
class="delete-button"
@click="handleDelete(budget)"
/>
</template>
</van-swipe-cell>
</template>
<!-- 收入列表 -->
<template v-if="activeTab === BudgetCategory.Income && incomeBudgets?.length > 0">
<van-swipe-cell
v-for="budget in incomeBudgets"
:key="budget.id"
>
<BudgetCard
:budget="budget"
:progress-color="getProgressColor(budget)"
:percent-class="{ income: budget.current / budget.limit >= 1 }"
:period-label="getPeriodLabel(budget.type)"
@click="handleEdit(budget)"
>
<template #amount-info>
<div class="info-item">
<div class="label">
已收入
</div>
<div class="value income">
¥{{ formatMoney(budget.current) }}
</div>
</div>
<div class="info-item">
<div class="label">
目标
</div>
<div class="value">
¥{{ formatMoney(budget.limit) }}
</div>
</div>
<div class="info-item">
<div class="label">
差额
</div>
<div
class="value"
:class="budget.current >= budget.limit ? 'income' : 'expense'"
>
¥{{ formatMoney(Math.abs(budget.limit - budget.current)) }}
</div>
</div>
</template>
</BudgetCard>
<template #right>
<van-button
square
text="删除"
type="danger"
class="delete-button"
@click="handleDelete(budget)"
/>
</template>
</van-swipe-cell>
</template>
<!-- 空状态 -->
<van-empty
v-if="
activeTab !== BudgetCategory.Savings &&
!loading &&
!hasError &&
((activeTab === BudgetCategory.Expense && expenseBudgets?.length === 0) ||
(activeTab === BudgetCategory.Income && incomeBudgets?.length === 0))
"
:description="`暂无${activeTab === BudgetCategory.Expense ? '支出' : '收入'}预算`"
/>
</div>
<div style="height: calc(95px + env(safe-area-inset-bottom, 0px))" />
</van-pull-refresh>
</PopupContainer>
<!-- 未覆盖分类弹窗 -->
<PopupContainer
v-model="showUncoveredDetails"
<PopupContainerV2
v-model:show="showUncoveredDetails"
title="未覆盖预算的分类"
:subtitle="`本月共 <b style='color:var(--van-primary-color)'>${uncoveredCategories.length}</b> 个分类未设置预算`"
height="60%"
:height="'60%'"
>
<div class="uncovered-list">
<div style="padding: 0">
<!-- subtitle 作为内容区域顶部 -->
<div
v-for="item in uncoveredCategories"
:key="item.category"
class="uncovered-item"
>
<div class="item-left">
<div class="category-name">
{{ item.category }}
style="padding: 12px 16px; text-align: center; color: #999; font-size: 14px; border-bottom: 1px solid var(--van-border-color)"
v-html="`本月共 <b style='color:var(--van-primary-color)'>${uncoveredCategories.length}</b> 个分类未设置预算`"
/>
<div class="uncovered-list">
<div
v-for="item in uncoveredCategories"
:key="item.category"
class="uncovered-item"
>
<div class="item-left">
<div class="category-name">
{{ item.category }}
</div>
<div class="transaction-count">
{{ item.transactionCount }} 笔记录
</div>
</div>
<div class="transaction-count">
{{ item.transactionCount }} 笔记录
</div>
</div>
<div class="item-right">
<div
class="item-amount"
:class="activeTab === BudgetCategory.Expense ? 'expense' : 'income'"
>
¥{{ formatMoney(item.totalAmount) }}
<div class="item-right">
<div
class="item-amount"
:class="activeTab === BudgetCategory.Expense ? 'expense' : 'income'"
>
¥{{ formatMoney(item.totalAmount) }}
</div>
</div>
</div>
</div>
@@ -343,25 +203,31 @@
我知道了
</van-button>
</template>
</PopupContainer>
</PopupContainerV2>
<!-- 归档总结弹窗 -->
<PopupContainer
v-model="showSummaryPopup"
<PopupContainerV2
v-model:show="showSummaryPopup"
title="月份归档总结"
:subtitle="`${selectedDate.getFullYear()}年${selectedDate.getMonth() + 1}月`"
height="70%"
:height="'70%'"
>
<div style="padding: 16px">
<div
class="rich-html-content"
v-html="
archiveSummary ||
'<p style=\'text-align:center;color:var(--van-text-color-3)\'>暂无总结</p>'
"
/>
<div style="padding: 0">
<!-- subtitle -->
<div style="padding: 12px 16px; text-align: center; color: #999; font-size: 14px; border-bottom: 1px solid var(--van-border-color)">
{{ selectedDate.getFullYear() }}年{{ selectedDate.getMonth() + 1 }}月
</div>
<div style="padding: 16px">
<div
class="rich-html-content"
v-html="
archiveSummary ||
'<p style=\'text-align:center;color:var(--van-text-color-3)\'>暂无总结</p>'
"
/>
</div>
</div>
</PopupContainer>
</PopupContainerV2>
<!-- 日期选择器 -->
<van-popup
@@ -401,7 +267,7 @@ import BudgetTypeTabs from '@/components/BudgetTypeTabs.vue'
import BudgetCard from '@/components/Budget/BudgetCard.vue'
import BudgetEditPopup from '@/components/Budget/BudgetEditPopup.vue'
import SavingsConfigPopup from '@/components/Budget/SavingsConfigPopup.vue'
import PopupContainer from '@/components/PopupContainer.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.vue'
import ExpenseBudgetContent from './modules/ExpenseBudgetContent.vue'
import IncomeBudgetContent from './modules/IncomeBudgetContent.vue'
import SavingsBudgetContent from './modules/SavingsBudgetContent.vue'

View File

@@ -71,10 +71,10 @@
</div>
<!-- 计划存款明细弹窗 -->
<PopupContainer
v-model="showDetailPopup"
<PopupContainerV2
v-model:show="showDetailPopup"
title="计划存款明细"
height="80%"
:height="'80%'"
>
<div class="popup-body">
<div
@@ -169,14 +169,14 @@
</div>
</div>
</div>
</PopupContainer>
</PopupContainerV2>
</template>
<script setup>
import { ref, computed } from 'vue'
import BudgetCard from '@/components/Budget/BudgetCard.vue'
import { BudgetPeriodType } from '@/constants/enums'
import PopupContainer from '@/components/PopupContainer.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.vue'
// Props
const props = defineProps({