feat: 添加待确认分类功能,支持获取和确认未分类交易记录;优化相关组件和服务
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 24s
Docker Build & Deploy / Deploy to Production (push) Successful in 10s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 24s
Docker Build & Deploy / Deploy to Production (push) Successful in 10s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
>
|
||||
<template #right>
|
||||
<van-icon
|
||||
name="question-o"
|
||||
name="setting-o"
|
||||
size="20"
|
||||
style="cursor: pointer; padding-right: 12px;"
|
||||
@click="onClickPrompt"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/>
|
||||
<van-icon
|
||||
v-else
|
||||
name="info-o"
|
||||
name="setting-o"
|
||||
size="20"
|
||||
@click="savingsConfigRef.open()"
|
||||
/>
|
||||
|
||||
@@ -195,21 +195,21 @@ const viewDetail = async (transaction) => {
|
||||
|
||||
// 详情保存后的回调
|
||||
const onDetailSave = async (saveData) => {
|
||||
// 重新加载当前日期的交易列表
|
||||
if (saveData && dateTransactions.value) {
|
||||
var updatedIndex = dateTransactions.value.findIndex(tx => tx.id === saveData.id);
|
||||
if (updatedIndex !== -1) {
|
||||
// 更新已有记录
|
||||
dateTransactions.value[updatedIndex].amount = saveData.amount;
|
||||
dateTransactions.value[updatedIndex].balance = saveData.balance;
|
||||
dateTransactions.value[updatedIndex].type = saveData.type;
|
||||
dateTransactions.value[updatedIndex].upsetedType = '';
|
||||
dateTransactions.value[updatedIndex].classify = saveData.classify;
|
||||
dateTransactions.value[updatedIndex].upsetedClassify = '';
|
||||
dateTransactions.value[updatedIndex].reason = saveData.reason;
|
||||
}
|
||||
var item = dateTransactions.value.find(tx => tx.id === saveData.id);
|
||||
if(!item) return
|
||||
|
||||
// 如果分类发生了变化 移除智能分类的内容,防止被智能分类覆盖
|
||||
if(item.classify !== saveData.classify) {
|
||||
// 通知智能分类按钮组件移除指定项
|
||||
smartClassifyButtonRef.value?.removeClassifiedTransaction(saveData.id)
|
||||
item.upsetedClassify = ''
|
||||
}
|
||||
|
||||
// 更新当前日期交易列表中的数据
|
||||
Object.assign(item, saveData);
|
||||
|
||||
|
||||
|
||||
// 重新加载当前月份的统计数据
|
||||
const now = selectedDate.value || new Date();
|
||||
fetchDailyStatistics(now.getFullYear(), now.getMonth() + 1);
|
||||
|
||||
@@ -53,18 +53,25 @@
|
||||
<div v-else class="detail-content">
|
||||
{{ currentMessage.content }}
|
||||
</div>
|
||||
<div v-if="currentMessage.url" class="detail-footer" style="padding: 16px;">
|
||||
<van-button type="primary" block @click="handleUrlJump(currentMessage.url)">
|
||||
查看详情
|
||||
</van-button>
|
||||
</div>
|
||||
</PopupContainer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
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';
|
||||
|
||||
const messageStore = useMessageStore();
|
||||
const router = useRouter();
|
||||
const list = ref([]);
|
||||
const loading = ref(false);
|
||||
const finished = ref(false);
|
||||
@@ -138,11 +145,7 @@ const viewDetail = async (item) => {
|
||||
}
|
||||
|
||||
if (item.messageType === 1) {
|
||||
if (item.content.startsWith('http')) {
|
||||
window.open(item.content, '_blank');
|
||||
} else {
|
||||
showToast('无效的URL');
|
||||
}
|
||||
handleUrlJump(item.url || item.content);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -150,6 +153,19 @@ const viewDetail = async (item) => {
|
||||
detailVisible.value = true;
|
||||
};
|
||||
|
||||
const handleUrlJump = (targetUrl) => {
|
||||
if (!targetUrl) return;
|
||||
|
||||
if (targetUrl.startsWith('http')) {
|
||||
window.open(targetUrl, '_blank');
|
||||
} else if (targetUrl.startsWith('/')) {
|
||||
router.push(targetUrl);
|
||||
detailVisible.value = false;
|
||||
} else {
|
||||
showToast('无效的URL');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (item) => {
|
||||
showDialog({
|
||||
title: '提示',
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<p>分类</p>
|
||||
</div>
|
||||
<van-cell-group inset>
|
||||
<van-cell title="待确认分类" is-link @click="handleUnconfirmedClassification" />
|
||||
<van-cell title="编辑分类" is-link @click="handleEditClassification" />
|
||||
<van-cell title="批量分类" is-link @click="handleBatchClassification" />
|
||||
<van-cell title="智能分类" is-link @click="handleSmartClassification" />
|
||||
@@ -267,6 +268,10 @@ const handleLogView = () => {
|
||||
router.push({ name: 'log' })
|
||||
}
|
||||
|
||||
const handleUnconfirmedClassification = () => {
|
||||
router.push({ name: 'unconfirmed-classification' })
|
||||
}
|
||||
|
||||
const handleReloadFromNetwork = async () => {
|
||||
try {
|
||||
await showConfirmDialog({
|
||||
|
||||
@@ -295,6 +295,7 @@
|
||||
</template>
|
||||
|
||||
<TransactionList
|
||||
ref="transactionListRef"
|
||||
:transactions="categoryBills"
|
||||
:loading="billListLoading"
|
||||
:finished="billListFinished"
|
||||
@@ -683,6 +684,7 @@ const goToTypeOverviewBills = (type) => {
|
||||
}
|
||||
|
||||
const smartClassifyButtonRef = ref(null)
|
||||
const transactionListRef = ref(null)
|
||||
// 加载分类账单数据
|
||||
const loadCategoryBills = async (customIndex = null, customSize = null) => {
|
||||
if (billListLoading.value || billListFinished.value) return
|
||||
@@ -756,16 +758,26 @@ const handleCategoryBillsDelete = (deletedId) => {
|
||||
}
|
||||
|
||||
// 账单保存后的回调
|
||||
const onBillSave = async () => {
|
||||
const onBillSave = async (updatedTransaction) => {
|
||||
// 刷新统计数据
|
||||
await fetchStatistics()
|
||||
|
||||
// 刷新账单列表
|
||||
categoryBills.value = []
|
||||
billPageIndex.value = 1
|
||||
billListFinished.value = false
|
||||
await loadCategoryBills()
|
||||
|
||||
// 只刷新列表中指定的账单项
|
||||
const item = categoryBills.value.find(t => t.id === updatedTransaction.id)
|
||||
if(!item) return
|
||||
|
||||
// 如果分类发生了变化
|
||||
if(item.classify !== updatedTransaction.classify) {
|
||||
// 从列表中移除该项
|
||||
categoryBills.value = categoryBills.value.filter(t => t.id !== updatedTransaction.id)
|
||||
categoryBillsTotal.value--
|
||||
// 通知智能分类按钮组件移除指定项
|
||||
smartClassifyButtonRef.value?.removeClassifiedTransaction(updatedTransaction.id)
|
||||
return
|
||||
}
|
||||
|
||||
Object.assign(item, updatedTransaction)
|
||||
|
||||
showToast('保存成功')
|
||||
}
|
||||
|
||||
@@ -798,12 +810,15 @@ const onSmartClassifySave = async () => {
|
||||
const handleNotifiedTransactionId = async (transactionId) => {
|
||||
console.log('收到已处理交易ID通知:', transactionId)
|
||||
// 滚动到指定的交易项
|
||||
const index = categoryBills.value.findIndex(item => item.id === transactionId)
|
||||
const index = categoryBills.value.findIndex(item => String(item.id) === String(transactionId))
|
||||
if (index !== -1) {
|
||||
// 等待 DOM 更新
|
||||
await nextTick()
|
||||
|
||||
const listElement = document.querySelector('.transaction-list')
|
||||
// 允许一丁点延迟让浏览器响应渲染
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
|
||||
const listElement = transactionListRef.value?.$el
|
||||
if (listElement) {
|
||||
const items = listElement.querySelectorAll('.transaction-item')
|
||||
const itemElement = items[index]
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
<template>
|
||||
<div class="page-container-flex">
|
||||
<!-- 顶部固定搜索框 -->
|
||||
<div class="top-search-bar">
|
||||
<van-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索交易摘要、来源、卡号、分类"
|
||||
shape="round"
|
||||
@update:model-value="onSearchChange"
|
||||
@clear="onSearchClear"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 下拉刷新区域 -->
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<!-- 加载提示 -->
|
||||
@@ -31,21 +42,6 @@
|
||||
:transaction="currentTransaction"
|
||||
@save="onDetailSave"
|
||||
/>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- 底部浮动搜索框 -->
|
||||
<div class="floating-search">
|
||||
<van-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索交易摘要、来源、卡号、分类"
|
||||
shape="round"
|
||||
@update:model-value="onSearchChange"
|
||||
@clear="onSearchClear"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -230,22 +226,16 @@ onBeforeUnmount(() => {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.floating-search {
|
||||
position: fixed;
|
||||
bottom: 90px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
padding: 8px 16px;
|
||||
background: transparent;
|
||||
pointer-events: none;
|
||||
.top-search-bar {
|
||||
background: var(--van-background-2);
|
||||
padding: 4px 12px;
|
||||
z-index: 100;
|
||||
border-bottom: 1px solid var(--van-border-color);
|
||||
}
|
||||
|
||||
.floating-search :deep(.van-search) {
|
||||
pointer-events: auto;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 20px;
|
||||
border: none;
|
||||
.top-search-bar :deep(.van-search) {
|
||||
padding: 4px 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
148
Web/src/views/UnconfirmedClassification.vue
Normal file
148
Web/src/views/UnconfirmedClassification.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div class="page-container-flex unconfirmed-classification">
|
||||
<van-nav-bar
|
||||
title="待确认分类"
|
||||
left-text="返回"
|
||||
left-arrow
|
||||
@click-left="onClickLeft"
|
||||
>
|
||||
<template #right>
|
||||
<van-button
|
||||
v-if="transactions.length > 0"
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="confirming"
|
||||
@click="handleConfirmAll"
|
||||
>
|
||||
全部确认
|
||||
</van-button>
|
||||
</template>
|
||||
</van-nav-bar>
|
||||
|
||||
<div class="scroll-content">
|
||||
<div v-if="loading && transactions.length === 0" class="loading-container">
|
||||
<van-loading vertical>加载中...</van-loading>
|
||||
</div>
|
||||
|
||||
<TransactionList
|
||||
v-else
|
||||
:transactions="displayTransactions"
|
||||
:loading="loading"
|
||||
:finished="true"
|
||||
@click="handleTransactionClick"
|
||||
@delete="handleTransactionDeleted"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 交易详情弹窗 -->
|
||||
<TransactionDetail
|
||||
v-model:show="showDetail"
|
||||
:transaction="currentTransaction"
|
||||
@save="handleDetailSave"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showToast, showConfirmDialog } from 'vant'
|
||||
import { getUnconfirmedTransactionList, confirmAllUnconfirmed } from '@/api/transactionRecord'
|
||||
import TransactionList from '@/components/TransactionList.vue'
|
||||
import TransactionDetail from '@/components/TransactionDetail.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const confirming = ref(false)
|
||||
const transactions = ref([])
|
||||
const showDetail = ref(false)
|
||||
const currentTransaction = ref(null)
|
||||
|
||||
const onClickLeft = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
const handleConfirmAll = async () => {
|
||||
try {
|
||||
await showConfirmDialog({
|
||||
title: '提示',
|
||||
message: `确定要将这 ${transactions.value.length} 条记录的所有建议分类转为正式分类吗?`
|
||||
})
|
||||
|
||||
confirming.value = true
|
||||
const response = await confirmAllUnconfirmed()
|
||||
if (response && response.success) {
|
||||
showToast(`成功确认 ${response.data} 条记录`)
|
||||
loadData()
|
||||
} else {
|
||||
showToast(response.message || '确认失败')
|
||||
}
|
||||
} catch (err) {
|
||||
if (err !== 'cancel') {
|
||||
console.error('批量确认出错:', err)
|
||||
}
|
||||
} finally {
|
||||
confirming.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 转换数据格式以适配 TransactionList 组件
|
||||
const displayTransactions = computed(() => {
|
||||
return transactions.value.map(t => ({
|
||||
...t,
|
||||
upsetedClassify: t.unconfirmedClassify,
|
||||
upsetedType: t.unconfirmedType
|
||||
}))
|
||||
})
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await getUnconfirmedTransactionList()
|
||||
if (response && response.success) {
|
||||
transactions.value = response.data || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取待确认列表失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleTransactionClick = (transaction) => {
|
||||
currentTransaction.value = transaction
|
||||
showDetail.value = true
|
||||
}
|
||||
|
||||
const handleTransactionDeleted = (id) => {
|
||||
transactions.value = transactions.value.filter(t => t.id !== id)
|
||||
}
|
||||
|
||||
const handleDetailSave = () => {
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.unconfirmed-classification {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.scroll-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user