Files
EmailBill/Web/src/views/ClassificationNLP.vue
孙诚 319f8f7d7b
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Failing after 1m10s
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
大量的代码格式化
2026-01-16 11:15:44 +08:00

315 lines
8.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="page-container-flex classification-nlp">
<van-nav-bar title="自然语言分类" left-text="返回" left-arrow @click-left="onClickLeft" />
<div class="scroll-content">
<!-- 输入区域 -->
<div class="input-section">
<van-cell-group inset>
<van-field
v-model="userInput"
rows="3"
autosize
type="textarea"
maxlength="200"
placeholder="用自然语言描述您的需求AI将帮您找到相关交易并自动设置分类。例如我想要将苏州城慧的支出都改为地铁通勤消费"
show-word-limit
/>
</van-cell-group>
<div class="action-buttons">
<van-button type="primary" block round :loading="analyzing" @click="handleAnalyze">
分析查询
</van-button>
</div>
</div>
<!-- 分析结果展示 -->
<div v-if="analysisResult" class="result-section">
<van-cell-group inset>
<van-cell title="查询关键词" :value="analysisResult.searchKeyword" />
<van-cell title="AI建议类型" :value="getTypeName(analysisResult.targetType)" />
<van-cell title="AI建议分类" :value="analysisResult.targetClassify" />
<van-cell
title="找到记录"
:value="`${analysisResult.records.length} 条`"
is-link
@click="showRecordsList = true"
/>
</van-cell-group>
</div>
</div>
<!-- 交易详情弹窗 -->
<TransactionDetail
v-model:show="showDetail"
:transaction="currentTransaction"
@save="handleDetailSave"
/>
<!-- 记录列表弹窗 -->
<PopupContainer v-model="showRecordsList" title="交易记录列表" height="75%">
<div style="background: var(--van-background)">
<!-- 批量操作按钮 -->
<div class="batch-actions">
<van-button plain type="primary" size="small" @click="selectAll"> 全选 </van-button>
<van-button plain type="default" size="small" @click="selectNone"> 全不选 </van-button>
<van-button
type="success"
size="small"
:loading="submitting"
:disabled="selectedIds.size === 0"
@click="handleSubmit"
>
提交分类 ({{ selectedIds.size }})
</van-button>
</div>
<!-- 交易记录列表 -->
<div class="records-list">
<TransactionList
:transactions="displayRecords"
:loading="false"
:finished="true"
:show-checkbox="true"
:selected-ids="selectedIds"
:show-delete="false"
@update:selected-ids="updateSelectedIds"
@click="handleRecordClick"
/>
</div>
</div>
</PopupContainer>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { showToast, showConfirmDialog } from 'vant'
import { nlpAnalysis, batchUpdateClassify } from '@/api/transactionRecord'
import TransactionList from '@/components/TransactionList.vue'
import TransactionDetail from '@/components/TransactionDetail.vue'
import PopupContainer from '@/components/PopupContainer.vue'
const router = useRouter()
const userInput = ref('')
const analyzing = ref(false)
const submitting = ref(false)
const analysisResult = ref(null)
const selectedIds = ref(new Set())
const showDetail = ref(false)
const currentTransaction = ref(null)
const showRecordsList = ref(false) // 控制记录列表弹窗
// 返回按钮
const onClickLeft = () => {
router.back()
}
// 将带目标分类的记录转换为普通交易记录格式供列表显示
const displayRecords = computed(() => {
if (!analysisResult.value) {
return []
}
return analysisResult.value.records.map((r) => ({
id: r.id,
reason: r.reason,
amount: r.amount,
balance: r.balance,
card: r.card,
occurredAt: r.occurredAt,
createTime: r.createTime,
importFrom: r.importFrom,
refundAmount: r.refundAmount,
// 显示目标类型和分类
type: r.targetType,
classify: r.targetClassify,
upsetedClassify: r.upsetedClassify,
upsetedType: r.upsetedType
}))
})
// 获取交易类型名称
const getTypeName = (type) => {
const typeMap = {
0: '支出',
1: '收入',
2: '不计入收支'
}
return typeMap[type] || '未知'
}
// 分析用户输入
const handleAnalyze = async () => {
if (!userInput.value.trim()) {
showToast('请输入查询条件')
return
}
try {
analyzing.value = true
const response = await nlpAnalysis(userInput.value)
if (response.success) {
analysisResult.value = response.data
// 默认全选
const allIds = new Set(response.data.records.map((r) => r.id))
selectedIds.value = allIds
showToast(`找到 ${response.data.records.length} 条记录`)
} else {
showToast(response.message || '分析失败')
}
} catch (error) {
console.error('分析失败:', error)
showToast('分析失败,请重试')
} finally {
analyzing.value = false
}
}
// 全选
const selectAll = () => {
if (!analysisResult.value) {
return
}
const allIds = new Set(analysisResult.value.records.map((r) => r.id))
selectedIds.value = allIds
}
// 全不选
const selectNone = () => {
selectedIds.value = new Set()
}
// 更新选中状态
const updateSelectedIds = (newSelectedIds) => {
selectedIds.value = newSelectedIds
}
// 点击记录查看详情
const handleRecordClick = (transaction) => {
// 从原始记录中获取完整信息
const record = analysisResult.value?.records.find((r) => r.id === transaction.id)
if (record) {
currentTransaction.value = {
id: record.id,
reason: record.reason,
amount: record.amount,
balance: record.balance,
card: record.card,
occurredAt: record.occurredAt,
createTime: record.createTime,
importFrom: record.importFrom,
refundAmount: record.refundAmount,
// 用户可以在详情中修改类型和分类
type: record.targetType,
classify: record.targetClassify
}
showDetail.value = true
}
}
// 详情保存后
const handleDetailSave = () => {
// 详情中的修改已经保存到服务器
// 这里可以选择重新分析或者只更新本地显示
showToast('修改已保存')
}
// 提交分类
const handleSubmit = async () => {
if (selectedIds.value.size === 0) {
showToast('请至少选择一条记录')
return
}
try {
await showConfirmDialog({
title: '确认提交',
message: `确定要为选中的 ${selectedIds.value.size} 条记录设置分类吗?`
})
} catch {
return
}
try {
submitting.value = true
// 构建批量更新数据使用AI修改后的结果
const items = analysisResult.value.records
.filter((r) => selectedIds.value.has(r.id))
.map((r) => ({
id: r.id,
classify: r.upsetedClassify,
type: r.upsetedType
}))
const response = await batchUpdateClassify(items)
if (response.success) {
showToast('分类设置成功')
// 清空结果,让用户进行新的查询
analysisResult.value = null
selectedIds.value = new Set()
userInput.value = ''
} else {
showToast(response.message || '设置失败')
}
} catch (error) {
console.error('提交失败:', error)
showToast('提交失败,请重试')
} finally {
submitting.value = false
}
}
</script>
<style scoped>
.scroll-content {
flex: 1;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.input-section {
padding: 12px 0;
}
.action-buttons {
padding: 12px 16px;
}
.result-section {
padding-top: 8px;
}
.van-notice-bar {
margin: 12px 16px;
}
.batch-actions {
display: flex;
gap: 8px;
padding: 12px 16px;
background-color: var(--van-background-2);
margin-bottom: 8px;
}
.batch-actions > button:last-child {
margin-left: auto;
}
.records-list {
padding-bottom: 20px;
}
/* 设置页面容器背景色 */
:deep(.van-nav-bar) {
background: transparent !important;
}
</style>