Files
cma-management/frontend/src/views/Receivables.vue
T

192 lines
7.5 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="receivables-page">
<div class="page-header">
<h2>💰 应收款催收</h2>
<div class="header-right">
<el-select v-model="owner" size="small" clearable placeholder="按负责人筛选" style="width:140px" @change="loadData">
<el-option v-for="o in owners" :key="o" :value="o" :label="o" />
</el-select>
<el-select v-model="status" size="small" clearable placeholder="按状态筛选" style="width:130px" @change="loadData">
<el-option value="pending" label="待收款" />
<el-option value="overdue" label="已逾期" />
<el-option value="completed" label="已回款" />
</el-select>
<el-button type="primary" size="small" @click="loadData">刷新</el-button>
</div>
</div>
<!-- 汇总卡片 -->
<el-row :gutter="16">
<el-col :span="6"><el-card shadow="never" class="stat-card red"><div class="stat-val">¥{{ (summary.total_balance || 0).toFixed(2) }}</div><div class="stat-label">应收总额</div></el-card></el-col>
<el-col :span="6"><el-card shadow="never" class="stat-card orange"><div class="stat-val">¥{{ (summary.overdue_amount || 0).toFixed(2) }}</div><div class="stat-label">逾期金额 ({{ summary.overdue_count || 0 }})</div></el-card></el-col>
<el-col :span="6"><el-card shadow="never" class="stat-card green"><div class="stat-val">¥{{ (summary.total_paid || 0).toFixed(2) }}</div><div class="stat-label">已回款</div></el-card></el-col>
<el-col :span="6"><el-card shadow="never" class="stat-card blue"><div class="stat-val">{{ summary.completed_count || 0 }}</div><div class="stat-label">已完成笔数</div></el-card></el-col>
</el-row>
<!-- 账龄分布 -->
<el-row style="margin-top:16px">
<el-col :span="24">
<el-card shadow="never">
<template #header>账龄分布万元</template>
<div ref="agingRef" style="height:220px"></div>
</el-card>
</el-col>
</el-row>
<!-- 应收明细 -->
<el-row style="margin-top:16px">
<el-col :span="24">
<el-card shadow="never">
<template #header>应收明细</template>
<el-table :data="items" size="small" v-loading="loading">
<el-table-column prop="name" label="客户/单位" min-width="140" />
<el-table-column prop="owner" label="负责人" width="90" />
<el-table-column prop="amount" label="金额(万)" width="90">
<template #default="{ row }"><span style="font-weight:600">¥{{ row.amount }}</span></template>
</el-table-column>
<el-table-column prop="paid_amount" label="已回款(万)" width="100" />
<el-table-column prop="due_date" label="到期日" width="100" />
<el-table-column prop="aging_label" label="账龄" width="100">
<template #default="{ row }">
<el-tag :type="row.overdue ? 'danger' : (row.aging_label === '未到期' ? 'success' : 'warning')" size="small">
{{ row.aging_label }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="80">
<template #default="{ row }">
<el-tag :type="row.status === 'completed' ? 'success' : (row.overdue ? 'danger' : 'info')" size="small">
{{ row.status === 'completed' ? '已回款' : row.overdue ? '已逾期' : '待收款' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template #default="{ row }">
<el-button v-if="row.status !== 'completed'" type="primary" size="small" @click="openPayment(row)">登记回款</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
<!-- 回款登记弹窗 -->
<el-dialog v-model="paymentVisible" title="登记回款" width="420px" append-to-body>
<el-form label-width="90px">
<el-form-item label="客户">{{ currentRow?.name }}</el-form-item>
<el-form-item label="应收金额">¥{{ currentRow?.amount }}</el-form-item>
<el-form-item label="回款金额">
<el-input-number v-model="paymentAmount" :min="0" :max="currentRow?.amount || 0" :precision="2" style="width:180px" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="paymentNote" type="textarea" :rows="2" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="paymentVisible = false">取消</el-button>
<el-button type="primary" :loading="paying" @click="submitPayment">确认回款</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, nextTick } from 'vue'
import * as echarts from 'echarts'
import api from '@/api'
import { ElMessage } from 'element-plus'
const items = ref<any[]>([])
const owners = ref<string[]>([])
const summary = ref<any>({})
const owner = ref('')
const status = ref('')
const loading = ref(false)
const paymentVisible = ref(false)
const currentRow = ref<any>(null)
const paymentAmount = ref(0)
const paymentNote = ref('')
const paying = ref(false)
const agingRef = ref<HTMLElement>()
async function loadData() {
loading.value = true
try {
const params: any = {}
if (owner.value) params.owner = owner.value
if (status.value) params.status = status.value
const r: any = await api.get('/cash/receivables', { params })
const d = r.data || r || {}
summary.value = d.summary || {}
owners.value = d.owners || []
const rawItems = d.data || d.items || d.receivables || []
items.value = rawItems
await nextTick()
renderAging()
} catch (e) {
ElMessage.error('加载应收数据失败')
} finally {
loading.value = false
}
}
function renderAging() {
if (!agingRef.value) return
const chart = echarts.init(agingRef.value)
const aging = summary.value.aging || {}
chart.setOption({
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: Object.keys(aging) },
yAxis: { type: 'value', name: '万元' },
series: [{
type: 'bar',
data: Object.values(aging),
itemStyle: { color: '#409EFF', borderRadius: [4, 4, 0, 0] },
label: { show: true, position: 'top' },
}],
})
}
function openPayment(row: any) {
currentRow.value = row
paymentAmount.value = row.amount || 0
paymentNote.value = ''
paymentVisible.value = true
}
async function submitPayment() {
if (!currentRow.value || paymentAmount.value <= 0) return
paying.value = true
try {
await api.post(`/cash/receivables/${currentRow.value.id}/payment`, {
amount: paymentAmount.value,
note: paymentNote.value,
})
ElMessage.success('回款登记成功')
paymentVisible.value = false
loadData()
} catch (e) {
ElMessage.error('回款登记失败')
} finally {
paying.value = false
}
}
onMounted(loadData)
</script>
<style scoped>
.receivables-page { max-width: 1400px; margin: 0 auto; padding: 16px; }
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.header-right { display: flex; gap: 8px; }
.stat-card { text-align: center; }
.stat-val { font-size: 24px; font-weight: 700; }
.stat-label { font-size: 12px; color: #888; margin-top: 4px; }
.red .stat-val { color: #f56c6c; }
.orange .stat-val { color: #e6a23c; }
.green .stat-val { color: #67c23a; }
.blue .stat-val { color: #409EFF; }
</style>