移除对账功能 后期从长计议
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Failing after 1m57s
Docker Build & Deploy / Deploy to Production (push) Has been skipped
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-01-27 15:29:25 +08:00
parent bade93ad57
commit 4aa7e82429
29 changed files with 716 additions and 328 deletions

View File

@@ -38,30 +38,58 @@
/>
<!-- 交易类型 -->
<van-field name="type" label="类型">
<van-field
name="type"
label="类型"
>
<template #input>
<van-radio-group v-model="form.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
v-model="form.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 name="category" label="分类">
<van-field
name="category"
label="分类"
>
<template #input>
<span v-if="!categoryName" style="color: var(--van-text-color-3)">请选择分类</span>
<span
v-if="!categoryName"
style="color: var(--van-text-color-3)"
>请选择分类</span>
<span v-else>{{ categoryName }}</span>
</template>
</van-field>
<!-- 分类选择组件 -->
<ClassifySelector v-model="categoryName" :type="form.type" />
<ClassifySelector
v-model="categoryName"
:type="form.type"
/>
</van-cell-group>
<div class="actions">
<van-button round block type="primary" native-type="submit" :loading="loading">
<van-button
round
block
type="primary"
native-type="submit"
:loading="loading"
>
{{ submitText }}
</van-button>
<slot name="actions" />
@@ -69,7 +97,12 @@
</van-form>
<!-- 日期选择弹窗 -->
<van-popup v-model:show="showDatePicker" position="bottom" round teleport="body">
<van-popup
v-model:show="showDatePicker"
position="bottom"
round
teleport="body"
>
<van-date-picker
v-model="currentDate"
title="选择日期"
@@ -79,7 +112,12 @@
</van-popup>
<!-- 时间选择弹窗 -->
<van-popup v-model:show="showTimePicker" position="bottom" round teleport="body">
<van-popup
v-model:show="showTimePicker"
position="bottom"
round
teleport="body"
>
<van-time-picker
v-model="currentTime"
title="选择时间"

View File

@@ -1,6 +1,10 @@
<template>
<div class="manual-bill-add">
<BillForm ref="billFormRef" :loading="saving" @submit="handleSave" />
<BillForm
ref="billFormRef"
:loading="saving"
@submit="handleSave"
/>
</div>
</template>

View File

@@ -1,6 +1,10 @@
<template>
<div>
<div v-if="!parseResult" class="input-section" style="margin: 12px 12px 0 16px">
<div
v-if="!parseResult"
class="input-section"
style="margin: 12px 12px 0 16px"
>
<van-field
v-model="text"
type="textarea"
@@ -23,7 +27,10 @@
</div>
</div>
<div v-if="parseResult" class="result-section">
<div
v-if="parseResult"
class="result-section"
>
<BillForm
:initial-data="parseResult"
:loading="saving"
@@ -31,7 +38,13 @@
@submit="handleSave"
>
<template #actions>
<van-button plain round block class="mt-2" @click="parseResult = null">
<van-button
plain
round
block
class="mt-2"
@click="parseResult = null"
>
重新输入
</van-button>
</template>

View File

@@ -1,21 +1,35 @@
<template>
<div class="summary-container">
<transition :name="transitionName" mode="out-in">
<transition
:name="transitionName"
mode="out-in"
>
<div
v-if="stats && (stats.month || stats.year)"
:key="dateKey"
class="summary-card common-card"
>
<!-- 左切换按钮 -->
<div class="nav-arrow left" @click.stop="changeMonth(-1)">
<div
class="nav-arrow left"
@click.stop="changeMonth(-1)"
>
<van-icon name="arrow-left" />
</div>
<div class="summary-content">
<template v-for="(config, key) in periodConfigs" :key="key">
<template
v-for="(config, key) in periodConfigs"
:key="key"
>
<div class="summary-item">
<div class="label">{{ config.label }}{{ title }}</div>
<div class="value" :class="getValueClass(stats[key]?.rate || '0.0')">
<div class="label">
{{ config.label }}{{ title }}
</div>
<div
class="value"
:class="getValueClass(stats[key]?.rate || '0.0')"
>
{{ stats[key]?.rate || '0.0' }}<span class="unit">%</span>
</div>
<div class="sub-info">
@@ -24,7 +38,10 @@
<span class="amount">¥{{ formatMoney(stats[key]?.limit || 0) }}</span>
</div>
</div>
<div v-if="config.showDivider" class="divider" />
<div
v-if="config.showDivider"
class="divider"
/>
</template>
</div>
@@ -38,7 +55,10 @@
</div>
<!-- 非本月时显示的日期标识 -->
<div v-if="!isCurrentMonth" class="date-tag">
<div
v-if="!isCurrentMonth"
class="date-tag"
>
{{ props.date.getFullYear() }}{{ props.date.getMonth() + 1 }}
</div>
</div>

View File

@@ -1,12 +1,20 @@
<template>
<PopupContainer v-model="visible" title="设置存款分类" height="60%">
<PopupContainer
v-model="visible"
title="设置存款分类"
height="60%"
>
<div class="savings-config-content">
<div class="config-header">
<p class="subtitle">这些分类的统计值将计入存款</p>
<p class="subtitle">
这些分类的统计值将计入存款
</p>
</div>
<div class="category-section">
<div class="section-title">可多选分类</div>
<div class="section-title">
可多选分类
</div>
<ClassifySelector
v-model="selectedCategories"
:type="2"
@@ -18,7 +26,14 @@
</div>
<template #footer>
<van-button block round type="primary" @click="onSubmit"> 保存配置 </van-button>
<van-button
block
round
type="primary"
@click="onSubmit"
>
保存配置
</van-button>
</template>
</PopupContainer>
</template>

View File

@@ -1,18 +1,40 @@
<template>
<div class="global-add-bill">
<!-- Floating Add Bill Button -->
<div class="floating-add" @click="openAddBill">
<div
class="floating-add"
@click="openAddBill"
>
<van-icon name="plus" />
</div>
<!-- Add Bill Modal -->
<PopupContainer v-model="showAddBill" title="记一笔" height="75%">
<van-tabs v-model:active="activeTab" shrink>
<van-tab title="一句话录账" name="one">
<OneLineBillAdd :key="componentKey" @success="handleSuccess" />
<PopupContainer
v-model="showAddBill"
title="记一笔"
height="75%"
>
<van-tabs
v-model:active="activeTab"
shrink
>
<van-tab
title="一句话录账"
name="one"
>
<OneLineBillAdd
:key="componentKey"
@success="handleSuccess"
/>
</van-tab>
<van-tab title="手动录账" name="manual">
<ManualBillAdd :key="componentKey" @success="handleSuccess" />
<van-tab
title="手动录账"
name="manual"
>
<ManualBillAdd
:key="componentKey"
@success="handleSuccess"
/>
</van-tab>
</van-tabs>
</PopupContainer>

View File

@@ -12,21 +12,36 @@
<!-- 头部区域 -->
<div class="popup-header-fixed">
<!-- 标题行 (无子标题且有操作时使用 Grid 布局) -->
<div class="header-title-row" :class="{ 'has-actions': !subtitle && hasActions }">
<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">
<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" />
<div
v-if="subtitle"
class="header-stats"
>
<span
class="stats-text"
v-html="subtitle"
/>
<!-- 额外操作插槽 -->
<slot v-if="hasActions" name="header-actions" />
<slot
v-if="hasActions"
name="header-actions"
/>
</div>
</div>
@@ -36,7 +51,10 @@
</div>
<!-- 底部页脚固定不可滚动 -->
<div v-if="slots.footer" class="popup-footer-fixed">
<div
v-if="slots.footer"
class="popup-footer-fixed"
>
<slot name="footer" />
</div>
</div>

View File

@@ -1,15 +1,22 @@
<template>
<PopupContainer v-model="visible" title="交易详情" height="75%" :closeable="false">
<template #header-actions>
<van-button size="small" type="primary" plain @click="handleOffsetClick"> 抵账 </van-button>
</template>
<template>
<PopupContainer
v-model="visible"
title="交易详情"
height="75%"
:closeable="false"
>
<van-form style="margin-top: 12px">
<van-cell-group inset>
<van-cell title="记录时间" :value="formatDate(transaction.createTime)" />
<van-cell
title="记录时间"
:value="formatDate(transaction.createTime)"
/>
</van-cell-group>
<van-cell-group inset title="交易明细">
<van-cell-group
inset
title="交易明细"
>
<van-field
v-model="occurredAtLabel"
name="occurredAt"
@@ -48,50 +55,68 @@
:rules="[{ required: true, message: '请输入交易后余额' }]"
/>
<van-field name="type" label="交易类型">
<van-field
name="type"
label="交易类型"
>
<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 :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="交易分类">
<van-field
name="classify"
label="交易分类"
>
<template #input>
<div style="flex: 1">
<div
v-if="
transaction &&
transaction.unconfirmedClassify &&
transaction.unconfirmedClassify !== editForm.classify
transaction.unconfirmedClassify &&
transaction.unconfirmedClassify !== editForm.classify
"
class="suggestion-tip"
@click="applySuggestion"
>
<van-icon name="bulb-o" class="suggestion-icon" />
<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
transaction.unconfirmedType !== undefined &&
transaction.unconfirmedType !== editForm.type
"
>
({{ getTypeName(transaction.unconfirmedType) }})
</span>
</span>
<div class="suggestion-apply">应用</div>
<div class="suggestion-apply">
应用
</div>
</div>
<span v-else-if="!editForm.classify" style="color: var(--van-gray-5)"
>请选择交易分类</span
>
<span
v-else-if="!editForm.classify"
style="color: var(--van-gray-5)"
>请选择交易分类</span>
<span v-else>{{ editForm.classify }}</span>
</div>
</template>
@@ -106,30 +131,25 @@
</van-form>
<template #footer>
<van-button round block type="primary" :loading="submitting" @click="onSubmit">
<van-button
round
block
type="primary"
:loading="submitting"
@click="onSubmit"
>
保存修改
</van-button>
</template>
</PopupContainer>
<!-- 抵账候选列表弹窗 -->
<PopupContainer v-model="showOffsetPopup" title="选择抵账交易" height="75%">
<van-list>
<van-cell
v-for="item in offsetCandidates"
:key="item.id"
:title="item.reason"
:label="formatDate(item.occurredAt)"
:value="item.amount"
is-link
@click="handleCandidateSelect(item)"
/>
<van-empty v-if="offsetCandidates.length === 0" description="暂无匹配的抵账交易" />
</van-list>
</PopupContainer>
<!-- 日期选择弹窗 -->
<van-popup v-model:show="showDatePicker" position="bottom" round teleport="body">
<van-popup
v-model:show="showDatePicker"
position="bottom"
round
teleport="body"
>
<van-date-picker
v-model="currentDate"
title="选择日期"
@@ -139,7 +159,12 @@
</van-popup>
<!-- 时间选择弹窗 -->
<van-popup v-model:show="showTimePicker" position="bottom" round teleport="body">
<van-popup
v-model:show="showTimePicker"
position="bottom"
round
teleport="body"
>
<van-time-picker
v-model="currentTime"
title="选择时间"
@@ -151,14 +176,12 @@
<script setup>
import { ref, reactive, watch, defineProps, defineEmits, computed, nextTick } from 'vue'
import { showToast, showConfirmDialog } from 'vant'
import { showToast } from 'vant'
import dayjs from 'dayjs'
import PopupContainer from '@/components/PopupContainer.vue'
import ClassifySelector from '@/components/ClassifySelector.vue'
import {
updateTransaction,
getCandidatesForOffset,
offsetTransactions
updateTransaction
} from '@/api/transactionRecord'
const props = defineProps({
@@ -341,50 +364,6 @@ const formatDate = (dateString) => {
})
}
// 抵账相关
const showOffsetPopup = ref(false)
const offsetCandidates = ref([])
const handleOffsetClick = async () => {
try {
const res = await getCandidatesForOffset(editForm.id)
if (res.success) {
offsetCandidates.value = res.data || []
showOffsetPopup.value = true
} else {
showToast(res.message || '获取抵账列表失败')
}
} catch (error) {
console.error('获取抵账列表出错:', error)
showToast('获取抵账列表失败')
}
}
const handleCandidateSelect = (candidate) => {
showConfirmDialog({
title: '确认抵账',
message: `确认将当前交易与 "${candidate.reason}" (${candidate.amount}) 互相抵消吗\n抵消后两笔交易将被删除`
})
.then(async () => {
try {
const res = await offsetTransactions(editForm.id, candidate.id)
if (res.success) {
showToast('抵账成功')
showOffsetPopup.value = false
visible.value = false
emit('save') // 触发列表刷新
} else {
showToast(res.message || '抵账失败')
}
} catch (error) {
console.error('抵账出错:', error)
showToast('抵账失败')
}
})
.catch(() => {
// on cancel
})
}
</script>
<style scoped>

View File

@@ -1,7 +1,16 @@
<template>
<template>
<div class="transaction-list-container transaction-list">
<van-list :loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
<van-cell-group v-if="transactions && transactions.length" inset style="margin-top: 10px">
<van-list
:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<van-cell-group
v-if="transactions && transactions.length"
inset
style="margin-top: 10px"
>
<van-swipe-cell
v-for="transaction in transactions"
:key="transaction.id"
@@ -14,7 +23,10 @@
class="checkbox-col"
@update:model-value="toggleSelection(transaction)"
/>
<div class="transaction-card" @click="handleClick(transaction)">
<div
class="transaction-card"
@click="handleClick(transaction)"
>
<div class="card-left">
<div class="transaction-title">
<span class="reason">{{ transaction.reason || '(无摘要)' }}</span>
@@ -26,28 +38,36 @@
<span
v-if="
transaction.upsetedClassify &&
transaction.upsetedClassify !== transaction.classify
transaction.upsetedClassify !== transaction.classify
"
style="color: var(--van-warning-color)"
>
→ {{ transaction.upsetedClassify }}
</span>
</div>
<div v-if="transaction.importFrom">来源: {{ transaction.importFrom }}</div>
<div v-if="transaction.importFrom">
来源: {{ transaction.importFrom }}
</div>
</div>
</div>
<div class="card-middle">
<van-tag :type="getTypeTagType(transaction.type)" size="medium">
<van-tag
:type="getTypeTagType(transaction.type)"
size="medium"
>
{{ getTypeName(transaction.type) }}
</van-tag>
<template
v-if="
Number.isFinite(transaction.upsetedType) &&
transaction.upsetedType !== transaction.type
transaction.upsetedType !== transaction.type
"
>
<van-tag :type="getTypeTagType(transaction.upsetedType)" size="medium">
<van-tag
:type="getTypeTagType(transaction.upsetedType)"
size="medium"
>
{{ getTypeName(transaction.upsetedType) }}
</van-tag>
</template>
@@ -57,21 +77,25 @@
<div :class="['amount', getAmountClass(transaction.type)]">
{{ formatAmount(transaction.amount, transaction.type) }}
</div>
<div v-if="transaction.balance && transaction.balance > 0" class="balance">
余额: {{ formatMoney(transaction.balance) }}
</div>
<div
v-if="transaction.refundAmount && transaction.refundAmount > 0"
v-if="transaction.balance && transaction.balance > 0"
class="balance"
>
退款: {{ formatMoney(transaction.refundAmount) }}
余额: {{ formatMoney(transaction.balance) }}
</div>
</div>
<van-icon name="arrow" size="16" color="var(--van-gray-5)" />
<van-icon
name="arrow"
size="16"
color="var(--van-gray-5)"
/>
</div>
</div>
</div>
<template v-if="showDelete" #right>
<template
v-if="showDelete"
#right
>
<van-button
square
type="danger"