问题:TenantRoutingProperties 定义了配置但未被使用 解决方案: 1. TenantAwareLoadBalancer 注入 TenantRoutingProperties - 使用配置的 tenantHeader 名称 - 使用配置的 buildTenantGroup 方法 - 使用配置的 isSharedService 判断 - 使用配置的 isFallbackToShared 策略 2. 新增功能 - 支持 enabled=false 禁用租户路由 - 共享服务跳过租户过滤 - 可配置是否回退到共享实例 3. 更新测试适配新构造函数
277 lines
6.2 KiB
Vue
277 lines
6.2 KiB
Vue
<template>
|
|
<div class="page home">
|
|
<!-- 顶部标题 -->
|
|
<div class="header">
|
|
<div class="header-title">资金服务平台</div>
|
|
<div class="header-subtitle">Financial Platform</div>
|
|
</div>
|
|
|
|
<!-- 数据概览卡片 -->
|
|
<div class="summary-card mac-card fade-in-up">
|
|
<div class="card-header">
|
|
<span class="card-title">今日概览</span>
|
|
<span class="card-date">{{ currentDate }}</span>
|
|
</div>
|
|
<div class="summary-grid">
|
|
<div class="summary-item">
|
|
<div class="summary-icon income">
|
|
<van-icon name="arrow-down" />
|
|
</div>
|
|
<div class="summary-content">
|
|
<div class="summary-value">{{ formatMoney(summary.todayIncome) }}</div>
|
|
<div class="summary-label">今日收入</div>
|
|
</div>
|
|
</div>
|
|
<div class="summary-item">
|
|
<div class="summary-icon expense">
|
|
<van-icon name="arrow-up" />
|
|
</div>
|
|
<div class="summary-content">
|
|
<div class="summary-value">{{ formatMoney(summary.todayExpense) }}</div>
|
|
<div class="summary-label">今日支出</div>
|
|
</div>
|
|
</div>
|
|
<div class="summary-item">
|
|
<div class="summary-icon pending">
|
|
<van-icon name="clock-o" />
|
|
</div>
|
|
<div class="summary-content">
|
|
<div class="summary-value">{{ formatMoney(summary.pendingReceivable) }}</div>
|
|
<div class="summary-label">待收款</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 快捷入口卡片 -->
|
|
<div class="quick-card mac-card fade-in-up delay-1">
|
|
<div class="card-header">
|
|
<span class="card-title">快捷操作</span>
|
|
</div>
|
|
<div class="quick-grid">
|
|
<div class="quick-item" @click="$router.push('/expense/add')">
|
|
<div class="quick-icon">
|
|
<van-icon name="plus" />
|
|
</div>
|
|
<div class="quick-text">新增支出</div>
|
|
</div>
|
|
<div class="quick-item" @click="$router.push('/receivable')">
|
|
<div class="quick-icon">
|
|
<van-icon name="balance-list-o" />
|
|
</div>
|
|
<div class="quick-text">应收款</div>
|
|
</div>
|
|
<div class="quick-item" @click="$router.push('/my')">
|
|
<div class="quick-icon">
|
|
<van-icon name="user-o" />
|
|
</div>
|
|
<div class="quick-text">我的</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import request from '@/api/request'
|
|
|
|
const summary = ref({
|
|
todayIncome: 0,
|
|
todayExpense: 0,
|
|
pendingReceivable: 0
|
|
})
|
|
|
|
const currentDate = computed(() => {
|
|
const now = new Date()
|
|
const month = now.getMonth() + 1
|
|
const day = now.getDate()
|
|
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
|
return `${month}月${day}日 ${weekDays[now.getDay()]}`
|
|
})
|
|
|
|
const formatMoney = (value: number) => {
|
|
if (!value) return '¥0'
|
|
if (value >= 10000) {
|
|
return '¥' + (value / 10000).toFixed(1) + 'w'
|
|
}
|
|
return '¥' + value.toLocaleString()
|
|
}
|
|
|
|
// 加载统计数据
|
|
const loadSummary = async () => {
|
|
try {
|
|
const [incomeRes, expenseRes, unpaidRes] = await Promise.all([
|
|
request.get('/receipt/api/v1/receipt/receivable/stats/today-income'),
|
|
request.get('/exp/api/v1/exp/expense/stats/today-expense'),
|
|
request.get('/receipt/api/v1/receipt/receivable/stats/unpaid-amount')
|
|
])
|
|
|
|
summary.value.todayIncome = (incomeRes as any).data || 0
|
|
summary.value.todayExpense = (expenseRes as any).data || 0
|
|
summary.value.pendingReceivable = (unpaidRes as any).data || 0
|
|
} catch (e) {
|
|
console.error('加载统计数据失败', e)
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadSummary()
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.home {
|
|
padding: 0 16px;
|
|
}
|
|
|
|
.header {
|
|
padding: 60px 0 24px;
|
|
text-align: center;
|
|
}
|
|
|
|
.header-title {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
color: var(--mac-text);
|
|
letter-spacing: 1px;
|
|
}
|
|
|
|
.header-subtitle {
|
|
font-size: 13px;
|
|
color: var(--mac-text-secondary);
|
|
margin-top: 6px;
|
|
letter-spacing: 2px;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.summary-card {
|
|
margin-bottom: 16px;
|
|
padding: 20px;
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.card-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--mac-text);
|
|
}
|
|
|
|
.card-date {
|
|
font-size: 12px;
|
|
color: var(--mac-text-secondary);
|
|
}
|
|
|
|
.summary-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 12px;
|
|
}
|
|
|
|
.summary-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 12px 8px;
|
|
background: rgba(0, 0, 0, 0.02);
|
|
border-radius: 12px;
|
|
transition: transform 0.2s ease;
|
|
}
|
|
|
|
.summary-item:active {
|
|
transform: scale(0.97);
|
|
}
|
|
|
|
.summary-icon {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 18px;
|
|
color: #fff;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.summary-icon.income {
|
|
background: linear-gradient(135deg, #34C759, #30D158);
|
|
}
|
|
|
|
.summary-icon.expense {
|
|
background: linear-gradient(135deg, #FF3B30, #FF453A);
|
|
}
|
|
|
|
.summary-icon.pending {
|
|
background: linear-gradient(135deg, #FF9500, #FF9F0A);
|
|
}
|
|
|
|
.summary-content {
|
|
text-align: center;
|
|
}
|
|
|
|
.summary-value {
|
|
font-size: 16px;
|
|
font-weight: 700;
|
|
color: var(--mac-text);
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.summary-label {
|
|
font-size: 11px;
|
|
color: var(--mac-text-secondary);
|
|
}
|
|
|
|
.quick-card {
|
|
padding: 20px;
|
|
}
|
|
|
|
.quick-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 16px;
|
|
}
|
|
|
|
.quick-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 16px 8px;
|
|
background: rgba(0, 0, 0, 0.02);
|
|
border-radius: 16px;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.quick-item:active {
|
|
transform: scale(0.95);
|
|
background: rgba(0, 122, 255, 0.08);
|
|
}
|
|
|
|
.quick-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 14px;
|
|
background: linear-gradient(135deg, var(--mac-primary), #5AC8FA);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 22px;
|
|
color: #fff;
|
|
margin-bottom: 10px;
|
|
box-shadow: 0 4px 12px rgba(0, 122, 255, 0.25);
|
|
}
|
|
|
|
.quick-text {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--mac-text);
|
|
}
|
|
</style>
|