zhangjf 8e4afcd1a5 feat: TenantAwareLoadBalancer 整合 TenantRoutingProperties 配置
问题:TenantRoutingProperties 定义了配置但未被使用

解决方案:
1. TenantAwareLoadBalancer 注入 TenantRoutingProperties
   - 使用配置的 tenantHeader 名称
   - 使用配置的 buildTenantGroup 方法
   - 使用配置的 isSharedService 判断
   - 使用配置的 isFallbackToShared 策略

2. 新增功能
   - 支持 enabled=false 禁用租户路由
   - 共享服务跳过租户过滤
   - 可配置是否回退到共享实例

3. 更新测试适配新构造函数
2026-02-19 21:02:25 +08:00

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>