Files
EmailBill/Web/src/components/Budget/BudgetCard.vue

220 lines
4.1 KiB
Vue
Raw Normal View History

<template>
<div class="common-card budget-card" @click="$emit('click')">
<div class="card-header">
<div class="budget-info">
<h3 class="card-title">{{ budget.name }}</h3>
<slot name="tag">
<van-tag v-if="budget.isStopped" type="danger" size="small" plain>已停止</van-tag>
<van-tag v-else :type="statusTagType" size="small" plain>{{ statusTagText }}</van-tag>
</slot>
</div>
<div class="header-actions">
<slot name="actions">
<van-button
:icon="budget.isStopped ? 'play' : 'pause'"
size="mini"
plain
round
@click.stop="$emit('toggle-stop', budget)"
/>
</slot>
</div>
</div>
<div class="budget-body">
<div class="amount-info">
<slot name="amount-info"></slot>
</div>
<div class="progress-section">
<div class="progress-info">
<slot name="progress-info">
<span class="period-type">{{ periodLabel }}进度</span>
<span class="percent" :class="percentClass">
{{ percentage }}%
</span>
</slot>
</div>
<van-progress
:percentage="Math.min(percentage, 100)"
stroke-width="8"
:color="progressColor"
:show-pivot="false"
/>
</div>
</div>
<div class="card-footer">
<div class="period-navigation" @click.stop>
<van-button
icon="arrow-left"
class="nav-icon"
plain
size="small"
style="width: 50px;"
@click="$emit('switch-period', -1)"
/>
<span class="period-text">{{ budget.period }}</span>
<van-button
icon="arrow"
class="nav-icon"
plain
size="small"
style="width: 50px;"
@click="$emit('switch-period', 1)"
/>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
budget: {
type: Object,
required: true
},
statusTagType: {
type: String,
default: 'success'
},
statusTagText: {
type: String,
default: '进行中'
},
progressColor: {
type: String,
default: '#1989fa'
},
percentClass: {
type: [String, Object],
default: ''
},
periodLabel: {
type: String,
default: ''
}
})
defineEmits(['toggle-stop', 'switch-period', 'click'])
const percentage = computed(() => {
if (!props.budget.limit) return 0
return Math.round((props.budget.current / props.budget.limit) * 100)
})
</script>
<style scoped>
.budget-card {
margin-left: 0;
margin-right: 0;
margin-bottom: 0;
padding-bottom: 8px;
}
.budget-info {
display: flex;
align-items: center;
gap: 8px;
}
.card-title {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.header-actions {
display: flex;
gap: 8px;
}
.amount-info {
display: flex;
justify-content: space-between;
margin: 16px 0;
text-align: center;
}
:deep(.info-item) .label {
font-size: 12px;
color: #969799;
margin-bottom: 4px;
}
:deep(.info-item) .value {
font-size: 15px;
font-weight: 600;
}
:deep(.value.expense) {
color: #ee0a24;
}
:deep(.value.income) {
color: #07c160;
}
.progress-section {
margin-bottom: 16px;
}
.progress-info {
display: flex;
justify-content: space-between;
font-size: 13px;
margin-bottom: 6px;
color: #646566;
}
.percent.warning {
color: #ff976a;
font-weight: bold;
}
.percent.income {
color: #07c160;
font-weight: bold;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
color: #969799;
padding: 12px 12px 0;
padding-top: 8px;
border-top: 1px solid #ebedf0;
}
.period-navigation {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.period-text {
font-size: 14px;
font-weight: 500;
color: #323233;
}
.nav-icon {
padding: 4px;
font-size: 12px;
color: #1989fa;
}
@media (prefers-color-scheme: dark) {
.card-footer {
border-top-color: #2c2c2c;
}
.period-text {
color: #f5f5f5;
}
}
</style>