feat: 银行卡数量统计,编码。

This commit is contained in:
tianyongbao
2026-02-02 19:25:03 +08:00
parent 658691878a
commit e008b0c0e3
3 changed files with 438 additions and 2 deletions

View File

@@ -113,3 +113,11 @@ export function getOpenCardList(query) {
params: query
})
}
// 按银行统计银行卡数量
export function getBankCardStatistics() {
return request({
url: '/invest/analysis/bankCardStatistics',
method: 'get'
})
}

View File

@@ -0,0 +1,428 @@
<template>
<div class="app-container">
<div class="header-title">
<el-icon class="title-icon"><CreditCard /></el-icon>
银行卡统计分析
</div>
<div class="summary-cards">
<div class="summary-card gradient-blue">
<div class="icon-wrapper">
<el-icon class="card-icon"><Wallet /></el-icon>
</div>
<div class="card-info">
<div class="card-title">银行总数</div>
<div class="card-num">{{ bankCount }}<span></span></div>
</div>
</div>
<div class="summary-card gradient-purple">
<div class="icon-wrapper">
<el-icon class="card-icon"><Document /></el-icon>
</div>
<div class="card-info">
<div class="card-title">卡片总数</div>
<div class="card-num">{{ totalCards }}<span></span></div>
</div>
</div>
<div class="summary-card gradient-green">
<div class="icon-wrapper">
<el-icon class="card-icon"><CreditCard /></el-icon>
</div>
<div class="card-info">
<div class="card-title">信用卡总数</div>
<div class="card-num">{{ creditCardBanksCount }}<span class="unit-text"></span>{{ totalCreditCard }}<span class="unit-text"></span></div>
</div>
</div>
<div class="summary-card gradient-orange">
<div class="icon-wrapper">
<el-icon class="card-icon"><Coin /></el-icon>
</div>
<div class="card-info">
<div class="card-title">储蓄卡总数</div>
<div class="card-num">{{ debitCardBanksCount }}<span class="unit-text"></span>{{ totalDebitCard }}<span class="unit-text"></span></div>
</div>
</div>
<div class="summary-card gradient-cyan">
<div class="icon-wrapper">
<el-icon class="card-icon"><Postcard /></el-icon>
</div>
<div class="card-info">
<div class="card-title">I类储蓄卡总数</div>
<div class="card-num">{{ totalDebitTypeOne }}<span></span></div>
</div>
</div>
<div class="summary-card gradient-lime">
<div class="icon-wrapper">
<el-icon class="card-icon"><Files /></el-icon>
</div>
<div class="card-info">
<div class="card-title">II类储蓄卡总数</div>
<div class="card-num">{{ totalDebitTypeTwo }}<span></span></div>
</div>
</div>
</div>
<div class="table-container">
<div class="table-header">
<span>银行卡统计明细</span>
<el-button type="primary" :icon="Refresh" @click="handleRefresh">刷新</el-button>
</div>
<div class="table-wrapper">
<el-table
v-loading="loading"
:data="bankCardList"
style="width: 100%"
:header-cell-style="{ background: '#f5f7fa', color: '#303133', fontWeight: '600' }"
:cell-style="{ padding: '16px 0' }"
height="600"
>
<el-table-column prop="bankName" label="银行名称" min-width="200">
<template #default="{ row }">
<div class="bank-name">
<el-icon class="bank-icon"><Bank /></el-icon>
<span>{{ row.bankName }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="总卡数量" align="center">
<template #default="{ row }">
<el-tag type="primary" size="large" effect="dark">
{{ row.creditCardCount + row.debitCardCount }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="信用卡数量" align="center">
<template #default="{ row }">
<el-tag :type="row.creditCardCount > 0 ? 'success' : 'info'" size="large"> {{ row.creditCardCount }} </el-tag>
</template>
</el-table-column>
<el-table-column label="储蓄卡数量" align="center">
<template #default="{ row }">
<el-tag :type="row.debitCardCount > 0 ? 'warning' : 'info'" size="large"> {{ row.debitCardCount }} </el-tag>
</template>
</el-table-column>
<el-table-column label="I类储蓄卡" align="center">
<template #default="{ row }">
<span class="number-badge type-one">{{ row.debitTypeOneCount }}</span>
</template>
</el-table-column>
<el-table-column label="II类储蓄卡" align="center">
<template #default="{ row }">
<span class="number-badge type-two">{{ row.debitTypeTwoCount }}</span>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<script setup name="BankCardStatistics">
import { getBankCardStatistics } from '@/api/invest/statisticAnalysis'
import { Refresh } from '@element-plus/icons-vue'
const loading = ref(false)
const bankCardList = ref([])
const bankCount = ref(0)
const totalCreditCard = ref(0)
const totalDebitCard = ref(0)
const totalDebitTypeOne = ref(0)
const totalDebitTypeTwo = ref(0)
const creditCardBanksCount = ref(0)
const debitCardBanksCount = ref(0)
const creditCardColor = ref('#67c23a')
const debitCardColor = ref('#e6a23c')
const totalCards = computed(() => totalCreditCard.value + totalDebitCard.value)
const getList = () => {
loading.value = true
getBankCardStatistics()
.then((res) => {
bankCardList.value = res.data || []
calculateTotals()
})
.finally(() => {
loading.value = false
})
}
const calculateTotals = () => {
bankCount.value = bankCardList.value.length
totalCreditCard.value = bankCardList.value.reduce((sum, item) => sum + (item.creditCardCount || 0), 0)
totalDebitCard.value = bankCardList.value.reduce((sum, item) => sum + (item.debitCardCount || 0), 0)
totalDebitTypeOne.value = bankCardList.value.reduce((sum, item) => sum + (item.debitTypeOneCount || 0), 0)
totalDebitTypeTwo.value = bankCardList.value.reduce((sum, item) => sum + (item.debitTypeTwoCount || 0), 0)
// 计算有信用卡的银行数量
creditCardBanksCount.value = bankCardList.value.filter((item) => item.creditCardCount > 0).length
// 计算有储蓄卡的银行数量
debitCardBanksCount.value = bankCardList.value.filter((item) => item.debitCardCount > 0).length
}
const handleRefresh = () => {
getList()
}
onMounted(() => {
getList()
})
</script>
<style lang="scss" scoped>
.app-container {
background: #f5f7fa;
padding: 20px;
min-height: calc(100vh - 84px);
}
.header-title {
font-size: 20px;
font-weight: 600;
color: #303133;
margin-bottom: 24px;
padding-left: 12px;
border-left: 4px solid #667eea;
display: flex;
align-items: center;
background: linear-gradient(135deg, #ffffff 0%, #f8fbff 100%);
padding: 16px 20px;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(102, 126, 234, 0.08);
.title-icon {
margin-right: 12px;
font-size: 22px;
color: #667eea;
}
}
.summary-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 20px;
margin-bottom: 24px;
.summary-card {
background: linear-gradient(135deg, #ffffff 0%, #f8fbff 100%);
border-radius: 16px;
display: flex;
align-items: center;
padding: 24px;
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.08);
transition: all 0.3s ease;
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.15);
}
.icon-wrapper {
width: 70px;
height: 70px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20px;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.08) 0%, rgba(118, 75, 162, 0.05) 100%);
border: 1px solid rgba(102, 126, 234, 0.1);
.card-icon {
font-size: 36px;
}
}
.card-info {
flex: 1;
.card-title {
color: #909399;
font-size: 14px;
margin-bottom: 8px;
font-weight: 500;
}
.card-num {
font-size: 28px;
font-family: DIN, Arial, sans-serif;
font-weight: 700;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
display: flex;
align-items: baseline;
gap: 2px;
.unit-text {
font-size: 16px;
color: #909399;
font-weight: 500;
background: none;
-webkit-text-fill-color: #909399;
margin-left: 2px;
}
span {
font-size: 14px;
color: #909399;
font-weight: 500;
background: none;
-webkit-text-fill-color: #909399;
margin-left: 4px;
}
}
}
&.gradient-blue .card-icon {
color: #409eff;
}
&.gradient-green .card-icon {
color: #67c23a;
}
&.gradient-orange .card-icon {
color: #e6a23c;
}
&.gradient-purple .card-icon {
color: #9c27b0;
}
&.gradient-cyan .card-icon {
color: #00bcd4;
}
&.gradient-lime .card-icon {
color: #aeea00;
}
}
}
.table-container {
background: #ffffff;
border-radius: 16px;
padding: 24px;
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.08);
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
span {
font-size: 16px;
font-weight: 600;
color: #303133;
}
}
.table-wrapper {
max-height: 600px;
overflow-y: auto;
overflow-x: hidden;
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
&::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: rgba(102, 126, 234, 0.3);
border-radius: 4px;
transition: background 0.3s;
&:hover {
background: rgba(102, 126, 234, 0.5);
}
}
}
.bank-name {
display: flex;
align-items: center;
font-weight: 600;
color: #303133;
.bank-icon {
margin-right: 8px;
color: #667eea;
font-size: 18px;
}
}
.number-badge {
display: inline-block;
padding: 6px 16px;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
background: #f5f7fa;
color: #606266;
&.type-one {
background: linear-gradient(135deg, rgba(64, 158, 255, 0.1) 0%, rgba(64, 158, 255, 0.05) 100%);
color: #409eff;
}
&.type-two {
background: linear-gradient(135deg, rgba(103, 194, 58, 0.1) 0%, rgba(103, 194, 58, 0.05) 100%);
color: #67c23a;
}
}
.card-distribution {
.dist-item {
display: flex;
align-items: center;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
.dist-label {
width: 60px;
font-size: 13px;
color: #606266;
margin-right: 12px;
}
:deep(.el-progress) {
flex: 1;
}
}
}
}
// 响应式设计
@media (max-width: 1200px) {
.summary-cards {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.app-container {
padding: 16px;
}
.summary-cards {
grid-template-columns: 1fr;
gap: 12px;
}
.table-container {
padding: 16px;
}
}
</style>

View File

@@ -42,8 +42,8 @@ export default defineConfig(({ mode, command }) => {
proxy: {
// https://cn.vitejs.dev/config/#server-proxy
'/dev-api': {
target: 'https://www.qdintc.com/prod-api/',
// target: 'http://127.0.0.1:8080',
// target: 'https://www.qdintc.com/prod-api/',
target: 'http://127.0.0.1:8080',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
},