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:
@@ -43,4 +43,9 @@ public class MessageRecord : BaseEntity
|
|||||||
/// 是否已读
|
/// 是否已读
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsRead { get; set; } = false;
|
public bool IsRead { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 跳转URL
|
||||||
|
/// </summary>
|
||||||
|
public string? Url { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,16 @@ public class TransactionRecord : BaseEntity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Classify { get; set; } = string.Empty;
|
public string Classify { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 待确认的分类(AI或规则建议,但尚未正式确认)
|
||||||
|
/// </summary>
|
||||||
|
public string? UnconfirmedClassify { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 待确认的类型
|
||||||
|
/// </summary>
|
||||||
|
public TransactionType? UnconfirmedType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 导入编号
|
/// 导入编号
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -178,6 +178,18 @@ public interface ITransactionRecordRepository : IBaseRepository<TransactionRecor
|
|||||||
/// <returns>候选交易列表</returns>
|
/// <returns>候选交易列表</returns>
|
||||||
Task<List<TransactionRecord>> GetCandidatesForOffsetAsync(long currentId, decimal amount, TransactionType currentType);
|
Task<List<TransactionRecord>> GetCandidatesForOffsetAsync(long currentId, decimal amount, TransactionType currentType);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取待确认分类的账单列表
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>待确认账单列表</returns>
|
||||||
|
Task<List<TransactionRecord>> GetUnconfirmedRecordsAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 全部确认待确认的分类
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>影响行数</returns>
|
||||||
|
Task<int> ConfirmAllUnconfirmedAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 更新分类名称
|
/// 更新分类名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -677,6 +689,25 @@ public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository<Tran
|
|||||||
.Where(a => a.Classify == oldName && a.Type == type)
|
.Where(a => a.Classify == oldName && a.Type == type)
|
||||||
.ExecuteAffrowsAsync();
|
.ExecuteAffrowsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<TransactionRecord>> GetUnconfirmedRecordsAsync()
|
||||||
|
{
|
||||||
|
return await FreeSql.Select<TransactionRecord>()
|
||||||
|
.Where(t => t.UnconfirmedClassify != null && t.UnconfirmedClassify != "")
|
||||||
|
.OrderByDescending(t => t.OccurredAt)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> ConfirmAllUnconfirmedAsync()
|
||||||
|
{
|
||||||
|
return await FreeSql.Update<TransactionRecord>()
|
||||||
|
.Set(t => t.Classify == t.UnconfirmedClassify)
|
||||||
|
.Set(t => t.Type == (t.UnconfirmedType ?? t.Type))
|
||||||
|
.Set(t => t.UnconfirmedClassify, null)
|
||||||
|
.Set(t => t.UnconfirmedType, null)
|
||||||
|
.Where(t => t.UnconfirmedClassify != null && t.UnconfirmedClassify != "")
|
||||||
|
.ExecuteAffrowsAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -102,9 +102,7 @@ public class EmailHandleService(
|
|||||||
records.Add(record);
|
records.Add(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
// var analysisResult = await AnalyzeClassifyAsync(records.ToArray());
|
_ = AutoClassifyAsync(records.ToArray());
|
||||||
// TODO 不应该直接保存 应该保存在备用字段上,前端确认后再更新到正式字段
|
|
||||||
|
|
||||||
|
|
||||||
return allSuccess;
|
return allSuccess;
|
||||||
}
|
}
|
||||||
@@ -173,11 +171,34 @@ public class EmailHandleService(
|
|||||||
records.Add(record);
|
records.Add(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = await AnalyzeClassifyAsync(records.ToArray());
|
_ = AutoClassifyAsync(records.ToArray());
|
||||||
|
|
||||||
return allSuccess;
|
return allSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task AutoClassifyAsync(TransactionRecord[] records)
|
||||||
|
{
|
||||||
|
await AnalyzeClassifyAsync(records.ToArray());
|
||||||
|
|
||||||
|
foreach (var record in records)
|
||||||
|
{
|
||||||
|
record.UnconfirmedClassify = record.Classify;
|
||||||
|
record.UnconfirmedType = record.Type;
|
||||||
|
|
||||||
|
record.Classify = ""; // 重置为未分类,等待手动确认
|
||||||
|
}
|
||||||
|
|
||||||
|
await trxRepo.UpdateRangeAsync(records);
|
||||||
|
|
||||||
|
// 消息
|
||||||
|
await messageRecordService.AddAsync(
|
||||||
|
"交易记录待确认分类",
|
||||||
|
$"共有 {records.Length} 条交易记录待确认分类,请点击前往确认。",
|
||||||
|
MessageType.Url,
|
||||||
|
"/unconfirmed-classification"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private string GetEmailByName(string to)
|
private string GetEmailByName(string to)
|
||||||
{
|
{
|
||||||
return emailSettings.Value.SmtpList.FirstOrDefault(s => s.Email == to)?.Name ?? to;
|
return emailSettings.Value.SmtpList.FirstOrDefault(s => s.Email == to)?.Name ?? to;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ public interface IMessageRecordService
|
|||||||
Task<(IEnumerable<MessageRecord> List, long Total)> GetPagedListAsync(int pageIndex, int pageSize);
|
Task<(IEnumerable<MessageRecord> List, long Total)> GetPagedListAsync(int pageIndex, int pageSize);
|
||||||
Task<MessageRecord?> GetByIdAsync(long id);
|
Task<MessageRecord?> GetByIdAsync(long id);
|
||||||
Task<bool> AddAsync(MessageRecord message);
|
Task<bool> AddAsync(MessageRecord message);
|
||||||
Task<bool> AddAsync(string title, string content, MessageType type = MessageType.Text);
|
Task<bool> AddAsync(string title, string content, MessageType type = MessageType.Text, string? url = null);
|
||||||
Task<bool> MarkAsReadAsync(long id);
|
Task<bool> MarkAsReadAsync(long id);
|
||||||
Task<bool> MarkAllAsReadAsync();
|
Task<bool> MarkAllAsReadAsync();
|
||||||
Task<bool> DeleteAsync(long id);
|
Task<bool> DeleteAsync(long id);
|
||||||
@@ -29,19 +29,20 @@ public class MessageRecordService(IMessageRecordRepository messageRepo, INotific
|
|||||||
return await messageRepo.AddAsync(message);
|
return await messageRepo.AddAsync(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> AddAsync(string title, string content, MessageType type = MessageType.Text)
|
public async Task<bool> AddAsync(string title, string content, MessageType type = MessageType.Text, string? url = null)
|
||||||
{
|
{
|
||||||
var message = new MessageRecord
|
var message = new MessageRecord
|
||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
Content = content,
|
Content = content,
|
||||||
MessageType = type,
|
MessageType = type,
|
||||||
|
Url = url,
|
||||||
IsRead = false
|
IsRead = false
|
||||||
};
|
};
|
||||||
var result = await messageRepo.AddAsync(message);
|
var result = await messageRepo.AddAsync(message);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
await notificationService.SendNotificationAsync(title);
|
await notificationService.SendNotificationAsync(title, url);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,28 @@ export const getTransactionList = (params = {}) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待确认分类的交易记录列表
|
||||||
|
* @returns {Promise<{success: boolean, data: Array}>}
|
||||||
|
*/
|
||||||
|
export const getUnconfirmedTransactionList = () => {
|
||||||
|
return request({
|
||||||
|
url: '/TransactionRecord/GetUnconfirmedList',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全部确认待确认的交易分类
|
||||||
|
* @returns {Promise<{success: boolean, data: number}>}
|
||||||
|
*/
|
||||||
|
export const confirmAllUnconfirmed = () => {
|
||||||
|
return request({
|
||||||
|
url: '/TransactionRecord/ConfirmAllUnconfirmed',
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据ID获取交易记录详情
|
* 根据ID获取交易记录详情
|
||||||
* @param {number} id - 交易记录ID
|
* @param {number} id - 交易记录ID
|
||||||
|
|||||||
@@ -59,15 +59,14 @@
|
|||||||
<van-button
|
<van-button
|
||||||
v-if="budget.description"
|
v-if="budget.description"
|
||||||
:icon="showDescription ? 'info' : 'info-o'"
|
:icon="showDescription ? 'info' : 'info-o'"
|
||||||
size="mini"
|
size="small"
|
||||||
:type="showDescription ? 'primary' : 'default'"
|
:type="showDescription ? 'primary' : 'default'"
|
||||||
plain
|
plain
|
||||||
round
|
|
||||||
@click.stop="showDescription = !showDescription"
|
@click.stop="showDescription = !showDescription"
|
||||||
/>
|
/>
|
||||||
<van-button
|
<van-button
|
||||||
icon="orders-o"
|
icon="orders-o"
|
||||||
size="mini"
|
size="small"
|
||||||
plain
|
plain
|
||||||
title="查询关联账单"
|
title="查询关联账单"
|
||||||
@click.stop="handleQueryBills"
|
@click.stop="handleQueryBills"
|
||||||
@@ -75,13 +74,13 @@
|
|||||||
<template v-if="budget.category !== 2">
|
<template v-if="budget.category !== 2">
|
||||||
<van-button
|
<van-button
|
||||||
icon="edit"
|
icon="edit"
|
||||||
size="mini"
|
size="small"
|
||||||
plain
|
plain
|
||||||
@click.stop="$emit('click', budget)"
|
@click.stop="$emit('click', budget)"
|
||||||
/>
|
/>
|
||||||
<van-button
|
<van-button
|
||||||
:icon="budget.isStopped ? 'play' : 'pause'"
|
:icon="budget.isStopped ? 'play' : 'pause'"
|
||||||
size="mini"
|
size="small"
|
||||||
plain
|
plain
|
||||||
@click.stop="$emit('toggle-stop', budget)"
|
@click.stop="$emit('toggle-stop', budget)"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -333,6 +333,11 @@ const handleSmartClassify = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const removeClassifiedTransaction = (transactionId) => {
|
||||||
|
// 从已分类结果中移除指定ID的项
|
||||||
|
classifiedResults.value = classifiedResults.value.filter(item => item.id !== transactionId)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置组件状态
|
* 重置组件状态
|
||||||
*/
|
*/
|
||||||
@@ -344,7 +349,8 @@ const reset = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
reset
|
reset,
|
||||||
|
removeClassifiedTransaction
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -11,11 +11,21 @@
|
|||||||
|
|
||||||
<van-form style="margin-top: 12px;">
|
<van-form style="margin-top: 12px;">
|
||||||
<van-cell-group inset>
|
<van-cell-group inset>
|
||||||
<van-cell title="交易时间" :value="formatDate(transaction.occurredAt)" />
|
|
||||||
<van-cell title="记录时间" :value="formatDate(transaction.createTime)" />
|
<van-cell title="记录时间" :value="formatDate(transaction.createTime)" />
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
|
|
||||||
<van-cell-group inset title="交易明细">
|
<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
|
<van-field
|
||||||
v-model="editForm.reason"
|
v-model="editForm.reason"
|
||||||
name="reason"
|
name="reason"
|
||||||
@@ -56,12 +66,27 @@
|
|||||||
|
|
||||||
<van-field name="classify" label="交易分类">
|
<van-field name="classify" label="交易分类">
|
||||||
<template #input>
|
<template #input>
|
||||||
<span v-if="!editForm.classify" style="color: #c8c9cc;">请选择交易分类</span>
|
<div style="flex: 1;">
|
||||||
<span v-else>{{ editForm.classify }}</span>
|
<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: #c8c9cc;">请选择交易分类</span>
|
||||||
|
<span v-else>{{ editForm.classify }}</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
|
|
||||||
<!-- 分类选择组件 -->
|
|
||||||
<ClassifySelector
|
<ClassifySelector
|
||||||
v-model="editForm.classify"
|
v-model="editForm.classify"
|
||||||
:type="editForm.type"
|
:type="editForm.type"
|
||||||
@@ -102,11 +127,32 @@
|
|||||||
<van-empty v-if="offsetCandidates.length === 0" description="暂无匹配的抵账交易" />
|
<van-empty v-if="offsetCandidates.length === 0" description="暂无匹配的抵账交易" />
|
||||||
</van-list>
|
</van-list>
|
||||||
</PopupContainer>
|
</PopupContainer>
|
||||||
|
|
||||||
|
<!-- 日期选择弹窗 -->
|
||||||
|
<van-popup v-model:show="showDatePicker" position="bottom" round teleport="body">
|
||||||
|
<van-date-picker
|
||||||
|
v-model="currentDate"
|
||||||
|
title="选择日期"
|
||||||
|
@confirm="onConfirmDate"
|
||||||
|
@cancel="showDatePicker = false"
|
||||||
|
/>
|
||||||
|
</van-popup>
|
||||||
|
|
||||||
|
<!-- 时间选择弹窗 -->
|
||||||
|
<van-popup v-model:show="showTimePicker" position="bottom" round teleport="body">
|
||||||
|
<van-time-picker
|
||||||
|
v-model="currentTime"
|
||||||
|
title="选择时间"
|
||||||
|
@confirm="onConfirmTime"
|
||||||
|
@cancel="showTimePicker = false"
|
||||||
|
/>
|
||||||
|
</van-popup>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, watch, defineProps, defineEmits } from 'vue'
|
import { ref, reactive, watch, defineProps, defineEmits, computed } from 'vue'
|
||||||
import { showToast, showConfirmDialog } from 'vant'
|
import { showToast, showConfirmDialog } from 'vant'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
import PopupContainer from '@/components/PopupContainer.vue'
|
import PopupContainer from '@/components/PopupContainer.vue'
|
||||||
import ClassifySelector from '@/components/ClassifySelector.vue'
|
import ClassifySelector from '@/components/ClassifySelector.vue'
|
||||||
import { updateTransaction, getCandidatesForOffset, offsetTransactions } from '@/api/transactionRecord'
|
import { updateTransaction, getCandidatesForOffset, offsetTransactions } from '@/api/transactionRecord'
|
||||||
@@ -127,6 +173,12 @@ const emit = defineEmits(['update:show', 'save'])
|
|||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
|
|
||||||
|
// 日期选择相关
|
||||||
|
const showDatePicker = ref(false)
|
||||||
|
const showTimePicker = ref(false)
|
||||||
|
const currentDate = ref([])
|
||||||
|
const currentTime = ref([])
|
||||||
|
|
||||||
// 编辑表单
|
// 编辑表单
|
||||||
const editForm = reactive({
|
const editForm = reactive({
|
||||||
id: 0,
|
id: 0,
|
||||||
@@ -134,7 +186,13 @@ const editForm = reactive({
|
|||||||
amount: '',
|
amount: '',
|
||||||
balance: '',
|
balance: '',
|
||||||
type: 0,
|
type: 0,
|
||||||
classify: ''
|
classify: '',
|
||||||
|
occurredAt: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 显示用的日期格式化
|
||||||
|
const occurredAtLabel = computed(() => {
|
||||||
|
return formatDate(editForm.occurredAt)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听props变化
|
// 监听props变化
|
||||||
@@ -151,6 +209,14 @@ watch(() => props.transaction, (newVal) => {
|
|||||||
editForm.balance = String(newVal.balance)
|
editForm.balance = String(newVal.balance)
|
||||||
editForm.type = newVal.type
|
editForm.type = newVal.type
|
||||||
editForm.classify = newVal.classify || ''
|
editForm.classify = newVal.classify || ''
|
||||||
|
|
||||||
|
// 初始化日期时间
|
||||||
|
if (newVal.occurredAt) {
|
||||||
|
editForm.occurredAt = newVal.occurredAt
|
||||||
|
const dt = dayjs(newVal.occurredAt)
|
||||||
|
currentDate.value = dt.format('YYYY-MM-DD').split('-')
|
||||||
|
currentTime.value = dt.format('HH:mm').split(':')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -158,12 +224,48 @@ watch(visible, (newVal) => {
|
|||||||
emit('update:show', newVal)
|
emit('update:show', newVal)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 处理日期确认
|
||||||
|
const onConfirmDate = ({ selectedValues }) => {
|
||||||
|
const dateStr = selectedValues.join('-')
|
||||||
|
const timeStr = currentTime.value.join(':')
|
||||||
|
editForm.occurredAt = dayjs(`${dateStr} ${timeStr}`).toISOString()
|
||||||
|
showDatePicker.value = false
|
||||||
|
// 接着选时间
|
||||||
|
showTimePicker.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const onConfirmTime = ({ selectedValues }) => {
|
||||||
|
currentTime.value = selectedValues
|
||||||
|
const dateStr = currentDate.value.join('-')
|
||||||
|
const timeStr = selectedValues.join(':')
|
||||||
|
editForm.occurredAt = dayjs(`${dateStr} ${timeStr}`).toISOString()
|
||||||
|
showTimePicker.value = false
|
||||||
|
}
|
||||||
|
|
||||||
// 监听交易类型变化,重新加载分类
|
// 监听交易类型变化,重新加载分类
|
||||||
watch(() => editForm.type, (newVal) => {
|
watch(() => editForm.type, (newVal) => {
|
||||||
// 清空已选的分类
|
// 清空已选的分类
|
||||||
editForm.classify = ''
|
editForm.classify = ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const applySuggestion = () => {
|
||||||
|
if (props.transaction.unconfirmedClassify) {
|
||||||
|
editForm.classify = props.transaction.unconfirmedClassify
|
||||||
|
if (props.transaction.unconfirmedType !== null && props.transaction.unconfirmedType !== undefined) {
|
||||||
|
editForm.type = props.transaction.unconfirmedType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTypeName = (type) => {
|
||||||
|
const typeMap = {
|
||||||
|
0: '支出',
|
||||||
|
1: '收入',
|
||||||
|
2: '不计'
|
||||||
|
}
|
||||||
|
return typeMap[type] || '未知'
|
||||||
|
}
|
||||||
|
|
||||||
// 提交编辑
|
// 提交编辑
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -175,7 +277,8 @@ const onSubmit = async () => {
|
|||||||
amount: parseFloat(editForm.amount),
|
amount: parseFloat(editForm.amount),
|
||||||
balance: parseFloat(editForm.balance),
|
balance: parseFloat(editForm.balance),
|
||||||
type: editForm.type,
|
type: editForm.type,
|
||||||
classify: editForm.classify
|
classify: editForm.classify,
|
||||||
|
occurredAt: editForm.occurredAt
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await updateTransaction(data)
|
const response = await updateTransaction(data)
|
||||||
@@ -262,4 +365,53 @@ const handleCandidateSelect = (candidate) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.suggestion-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: #ecf9ff;
|
||||||
|
color: #1989fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
border: 1px solid rgba(25, 137, 250, 0.1);
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-tip:active {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-text {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-apply {
|
||||||
|
margin-left: 8px;
|
||||||
|
padding: 0 6px;
|
||||||
|
background: #1989fa;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 10px;
|
||||||
|
height: 18px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.suggestion-tip {
|
||||||
|
background: rgba(25, 137, 250, 0.15);
|
||||||
|
border-color: rgba(25, 137, 250, 0.2);
|
||||||
|
color: #58a6ff;
|
||||||
|
}
|
||||||
|
.suggestion-apply {
|
||||||
|
background: #58a6ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="transaction-list-container">
|
<div class="transaction-list-container transaction-list">
|
||||||
<van-list
|
<van-list
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:finished="finished"
|
:finished="finished"
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
<van-swipe-cell
|
<van-swipe-cell
|
||||||
v-for="transaction in transactions"
|
v-for="transaction in transactions"
|
||||||
:key="transaction.id"
|
:key="transaction.id"
|
||||||
|
class="transaction-item"
|
||||||
>
|
>
|
||||||
<div class="transaction-row">
|
<div class="transaction-row">
|
||||||
<van-checkbox
|
<van-checkbox
|
||||||
|
|||||||
@@ -99,6 +99,13 @@ const router = createRouter({
|
|||||||
name: 'scheduled-tasks',
|
name: 'scheduled-tasks',
|
||||||
component: () => import('../views/ScheduledTasksView.vue'),
|
component: () => import('../views/ScheduledTasksView.vue'),
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 待确认的分类项
|
||||||
|
path: '/unconfirmed-classification',
|
||||||
|
name: 'unconfirmed-classification',
|
||||||
|
component: () => import('../views/UnconfirmedClassification.vue'),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
>
|
>
|
||||||
<template #right>
|
<template #right>
|
||||||
<van-icon
|
<van-icon
|
||||||
name="question-o"
|
name="setting-o"
|
||||||
size="20"
|
size="20"
|
||||||
style="cursor: pointer; padding-right: 12px;"
|
style="cursor: pointer; padding-right: 12px;"
|
||||||
@click="onClickPrompt"
|
@click="onClickPrompt"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
/>
|
/>
|
||||||
<van-icon
|
<van-icon
|
||||||
v-else
|
v-else
|
||||||
name="info-o"
|
name="setting-o"
|
||||||
size="20"
|
size="20"
|
||||||
@click="savingsConfigRef.open()"
|
@click="savingsConfigRef.open()"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -195,21 +195,21 @@ const viewDetail = async (transaction) => {
|
|||||||
|
|
||||||
// 详情保存后的回调
|
// 详情保存后的回调
|
||||||
const onDetailSave = async (saveData) => {
|
const onDetailSave = async (saveData) => {
|
||||||
// 重新加载当前日期的交易列表
|
var item = dateTransactions.value.find(tx => tx.id === saveData.id);
|
||||||
if (saveData && dateTransactions.value) {
|
if(!item) return
|
||||||
var updatedIndex = dateTransactions.value.findIndex(tx => tx.id === saveData.id);
|
|
||||||
if (updatedIndex !== -1) {
|
// 如果分类发生了变化 移除智能分类的内容,防止被智能分类覆盖
|
||||||
// 更新已有记录
|
if(item.classify !== saveData.classify) {
|
||||||
dateTransactions.value[updatedIndex].amount = saveData.amount;
|
// 通知智能分类按钮组件移除指定项
|
||||||
dateTransactions.value[updatedIndex].balance = saveData.balance;
|
smartClassifyButtonRef.value?.removeClassifiedTransaction(saveData.id)
|
||||||
dateTransactions.value[updatedIndex].type = saveData.type;
|
item.upsetedClassify = ''
|
||||||
dateTransactions.value[updatedIndex].upsetedType = '';
|
|
||||||
dateTransactions.value[updatedIndex].classify = saveData.classify;
|
|
||||||
dateTransactions.value[updatedIndex].upsetedClassify = '';
|
|
||||||
dateTransactions.value[updatedIndex].reason = saveData.reason;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新当前日期交易列表中的数据
|
||||||
|
Object.assign(item, saveData);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 重新加载当前月份的统计数据
|
// 重新加载当前月份的统计数据
|
||||||
const now = selectedDate.value || new Date();
|
const now = selectedDate.value || new Date();
|
||||||
fetchDailyStatistics(now.getFullYear(), now.getMonth() + 1);
|
fetchDailyStatistics(now.getFullYear(), now.getMonth() + 1);
|
||||||
|
|||||||
@@ -53,18 +53,25 @@
|
|||||||
<div v-else class="detail-content">
|
<div v-else class="detail-content">
|
||||||
{{ currentMessage.content }}
|
{{ currentMessage.content }}
|
||||||
</div>
|
</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>
|
</PopupContainer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
import { showToast, showDialog } from 'vant';
|
import { showToast, showDialog } from 'vant';
|
||||||
import { getMessageList, markAsRead, deleteMessage, markAllAsRead } from '@/api/message';
|
import { getMessageList, markAsRead, deleteMessage, markAllAsRead } from '@/api/message';
|
||||||
import { useMessageStore } from '@/stores/message';
|
import { useMessageStore } from '@/stores/message';
|
||||||
import PopupContainer from '@/components/PopupContainer.vue';
|
import PopupContainer from '@/components/PopupContainer.vue';
|
||||||
|
|
||||||
const messageStore = useMessageStore();
|
const messageStore = useMessageStore();
|
||||||
|
const router = useRouter();
|
||||||
const list = ref([]);
|
const list = ref([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const finished = ref(false);
|
const finished = ref(false);
|
||||||
@@ -138,11 +145,7 @@ const viewDetail = async (item) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (item.messageType === 1) {
|
if (item.messageType === 1) {
|
||||||
if (item.content.startsWith('http')) {
|
handleUrlJump(item.url || item.content);
|
||||||
window.open(item.content, '_blank');
|
|
||||||
} else {
|
|
||||||
showToast('无效的URL');
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +153,19 @@ const viewDetail = async (item) => {
|
|||||||
detailVisible.value = true;
|
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) => {
|
const handleDelete = (item) => {
|
||||||
showDialog({
|
showDialog({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
<p>分类</p>
|
<p>分类</p>
|
||||||
</div>
|
</div>
|
||||||
<van-cell-group inset>
|
<van-cell-group inset>
|
||||||
|
<van-cell title="待确认分类" is-link @click="handleUnconfirmedClassification" />
|
||||||
<van-cell title="编辑分类" is-link @click="handleEditClassification" />
|
<van-cell title="编辑分类" is-link @click="handleEditClassification" />
|
||||||
<van-cell title="批量分类" is-link @click="handleBatchClassification" />
|
<van-cell title="批量分类" is-link @click="handleBatchClassification" />
|
||||||
<van-cell title="智能分类" is-link @click="handleSmartClassification" />
|
<van-cell title="智能分类" is-link @click="handleSmartClassification" />
|
||||||
@@ -267,6 +268,10 @@ const handleLogView = () => {
|
|||||||
router.push({ name: 'log' })
|
router.push({ name: 'log' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleUnconfirmedClassification = () => {
|
||||||
|
router.push({ name: 'unconfirmed-classification' })
|
||||||
|
}
|
||||||
|
|
||||||
const handleReloadFromNetwork = async () => {
|
const handleReloadFromNetwork = async () => {
|
||||||
try {
|
try {
|
||||||
await showConfirmDialog({
|
await showConfirmDialog({
|
||||||
|
|||||||
@@ -295,6 +295,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<TransactionList
|
<TransactionList
|
||||||
|
ref="transactionListRef"
|
||||||
:transactions="categoryBills"
|
:transactions="categoryBills"
|
||||||
:loading="billListLoading"
|
:loading="billListLoading"
|
||||||
:finished="billListFinished"
|
:finished="billListFinished"
|
||||||
@@ -683,6 +684,7 @@ const goToTypeOverviewBills = (type) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const smartClassifyButtonRef = ref(null)
|
const smartClassifyButtonRef = ref(null)
|
||||||
|
const transactionListRef = ref(null)
|
||||||
// 加载分类账单数据
|
// 加载分类账单数据
|
||||||
const loadCategoryBills = async (customIndex = null, customSize = null) => {
|
const loadCategoryBills = async (customIndex = null, customSize = null) => {
|
||||||
if (billListLoading.value || billListFinished.value) return
|
if (billListLoading.value || billListFinished.value) return
|
||||||
@@ -756,16 +758,26 @@ const handleCategoryBillsDelete = (deletedId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 账单保存后的回调
|
// 账单保存后的回调
|
||||||
const onBillSave = async () => {
|
const onBillSave = async (updatedTransaction) => {
|
||||||
// 刷新统计数据
|
// 刷新统计数据
|
||||||
await fetchStatistics()
|
await fetchStatistics()
|
||||||
|
|
||||||
// 刷新账单列表
|
// 只刷新列表中指定的账单项
|
||||||
categoryBills.value = []
|
const item = categoryBills.value.find(t => t.id === updatedTransaction.id)
|
||||||
billPageIndex.value = 1
|
if(!item) return
|
||||||
billListFinished.value = false
|
|
||||||
await loadCategoryBills()
|
// 如果分类发生了变化
|
||||||
|
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('保存成功')
|
showToast('保存成功')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -798,12 +810,15 @@ const onSmartClassifySave = async () => {
|
|||||||
const handleNotifiedTransactionId = async (transactionId) => {
|
const handleNotifiedTransactionId = async (transactionId) => {
|
||||||
console.log('收到已处理交易ID通知:', 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) {
|
if (index !== -1) {
|
||||||
// 等待 DOM 更新
|
// 等待 DOM 更新
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
const listElement = document.querySelector('.transaction-list')
|
// 允许一丁点延迟让浏览器响应渲染
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 0))
|
||||||
|
|
||||||
|
const listElement = transactionListRef.value?.$el
|
||||||
if (listElement) {
|
if (listElement) {
|
||||||
const items = listElement.querySelectorAll('.transaction-item')
|
const items = listElement.querySelectorAll('.transaction-item')
|
||||||
const itemElement = items[index]
|
const itemElement = items[index]
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page-container-flex">
|
<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">
|
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||||
<!-- 加载提示 -->
|
<!-- 加载提示 -->
|
||||||
@@ -31,21 +42,6 @@
|
|||||||
:transaction="currentTransaction"
|
:transaction="currentTransaction"
|
||||||
@save="onDetailSave"
|
@save="onDetailSave"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- 底部浮动搜索框 -->
|
|
||||||
<div class="floating-search">
|
|
||||||
<van-search
|
|
||||||
v-model="searchKeyword"
|
|
||||||
placeholder="搜索交易摘要、来源、卡号、分类"
|
|
||||||
shape="round"
|
|
||||||
@update:model-value="onSearchChange"
|
|
||||||
@clear="onSearchClear"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -230,22 +226,16 @@ onBeforeUnmount(() => {
|
|||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.floating-search {
|
.top-search-bar {
|
||||||
position: fixed;
|
background: var(--van-background-2);
|
||||||
bottom: 90px;
|
padding: 4px 12px;
|
||||||
left: 0;
|
z-index: 100;
|
||||||
right: 0;
|
border-bottom: 1px solid var(--van-border-color);
|
||||||
z-index: 999;
|
|
||||||
padding: 8px 16px;
|
|
||||||
background: transparent;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.floating-search :deep(.van-search) {
|
.top-search-bar :deep(.van-search) {
|
||||||
pointer-events: auto;
|
padding: 4px 0;
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
background: transparent;
|
||||||
border-radius: 20px;
|
|
||||||
border: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
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>
|
||||||
@@ -71,6 +71,42 @@ public class TransactionRecordController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取待确认分类的交易记录列表
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<BaseResponse<List<TransactionRecord>>> GetUnconfirmedListAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var list = await transactionRepository.GetUnconfirmedRecordsAsync();
|
||||||
|
return list.Ok();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "获取待确认分类交易列表失败");
|
||||||
|
return $"获取待确认分类交易列表失败: {ex.Message}".Fail<List<TransactionRecord>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 全部确认待确认的交易分类
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<BaseResponse<int>> ConfirmAllUnconfirmedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var count = await transactionRepository.ConfirmAllUnconfirmedAsync();
|
||||||
|
return count.Ok();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "全部确认待确认分类失败");
|
||||||
|
return $"全部确认待确认分类失败: {ex.Message}".Fail<int>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据ID获取交易记录详情
|
/// 根据ID获取交易记录详情
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -176,6 +212,10 @@ public class TransactionRecordController(
|
|||||||
transaction.Balance = dto.Balance;
|
transaction.Balance = dto.Balance;
|
||||||
transaction.Type = dto.Type;
|
transaction.Type = dto.Type;
|
||||||
transaction.Classify = dto.Classify ?? string.Empty;
|
transaction.Classify = dto.Classify ?? string.Empty;
|
||||||
|
|
||||||
|
// 清除待确认状态
|
||||||
|
transaction.UnconfirmedClassify = null;
|
||||||
|
transaction.UnconfirmedType = null;
|
||||||
|
|
||||||
var success = await transactionRepository.UpdateAsync(transaction);
|
var success = await transactionRepository.UpdateAsync(transaction);
|
||||||
if (success)
|
if (success)
|
||||||
|
|||||||
Reference in New Issue
Block a user