Files
hfb_sys/frontend/src/features/orders/components/CheckoutTrialPreview.vue
T
yml2213 dff0577385 feat(checkout): 修复后置损耗结算全额归号主、支持角色隔离实时试算与全流程UI重构
- fix(pricing): 后置消耗/损耗金额 100% 划归号主收益,超出预收部分平台不抽成,避免被平台费吸收
- feat(checkout): 前端新增结账实时试算预览组件 (CheckoutTrialPreview + useCheckoutSimulator),角色隔离展示(仅见自身收入/退款,屏蔽平台服务费)
- refactor(admin): 重构交易全流程时间线 (ProcessTimeline.vue),将平铺挤压指标重构成4大分类仪表盘微型卡片矩阵,升级可视化业务状态流转徽章与阶段色彩主题
- style(admin): 优化支付与交接流水元信息网格布局,防止超宽屏下长单行拉伸
2026-09-13 10:19:33 +08:00

266 lines
6.8 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
<script setup lang="ts">
import type { CheckoutSimulation } from '../composables/useCheckoutSimulator'
import { money } from '../composables/useOrderSnapshot'
const props = withDefaults(
defineProps<{
simulation: CheckoutSimulation
variant?: 'desktop' | 'mobile'
}>(),
{ variant: 'desktop' }
)
</script>
<template>
<div v-if="props.simulation" :class="['trial-preview-container', props.variant]">
<!-- ───────────── 号主视角 ───────────── -->
<template v-if="props.simulation.role === 'owner'">
<div class="trial-header">
<span class="trial-title"> <i class="trial-icon">💰</i> 结账到手试算(仅您可见) </span>
<span v-if="props.simulation.isDepositSufficient" class="badge success">押金充足</span>
<span v-else class="badge danger">押金不足</span>
</div>
<div class="trial-body">
<div class="main-metric">
<span class="label">预计最终到手总额</span>
<strong class="value income">¥{{ money(props.simulation.totalIncomeYuan) }}</strong>
</div>
<div class="metric-breakdown">
<div class="breakdown-item">
<span>租金收益</span>
<span>¥{{ money(props.simulation.rentIncomeYuan) }}</span>
</div>
<div v-if="props.simulation.damageCompensationYuan > 0" class="breakdown-item highlight">
<span>损耗/损坏赔偿 (全额到手)</span>
<span class="text-green">+¥{{ money(props.simulation.damageCompensationYuan) }}</span>
</div>
</div>
<div v-if="props.simulation.isDepositSufficient" class="trial-note info">
<span>💡 租客押金充足,方案确认后款项将全额转入您的可用余额,平台 0 抽成。</span>
</div>
<div v-else class="trial-note warning">
<span
>⚠️ 租客押金不足差额 ¥{{
money(props.simulation.shortfallYuan)
}},无法自动完结,需协商调低金额或发起争议。</span
>
</div>
</div>
</template>
<!-- ───────────── 租客视角 ───────────── -->
<template v-else-if="props.simulation.role === 'renter'">
<div class="trial-header">
<span class="trial-title"> <i class="trial-icon">🧾</i> 费用与退款试算(仅您可见) </span>
<span v-if="props.simulation.isDepositSufficient" class="badge info">正常结算</span>
<span v-else class="badge danger">需补差额</span>
</div>
<div class="trial-body">
<div class="metrics-grid">
<div class="main-metric">
<span class="label">实际租用消费</span>
<strong class="value">¥{{ money(props.simulation.actualCostYuan) }}</strong>
</div>
<div class="main-metric">
<span class="label">预计原路退还</span>
<strong class="value refund">¥{{ money(props.simulation.totalRefundYuan) }}</strong>
</div>
</div>
<div class="metric-breakdown">
<div class="breakdown-item">
<span>实际使用租金</span>
<span>¥{{ money(props.simulation.buyerRentYuan) }}</span>
</div>
<div v-if="props.simulation.damageDeductYuan > 0" class="breakdown-item">
<span>损耗赔偿扣除 (从押金)</span>
<span class="text-danger">-¥{{ money(props.simulation.damageDeductYuan) }}</span>
</div>
<div v-if="props.simulation.rentRefundYuan > 0" class="breakdown-item">
<span>未用租金退还</span>
<span class="text-green">+¥{{ money(props.simulation.rentRefundYuan) }}</span>
</div>
<div class="breakdown-item">
<span>剩余押金退还</span>
<span class="text-green">+¥{{ money(props.simulation.depositRefundYuan) }}</span>
</div>
</div>
<div v-if="props.simulation.overshootYuan > 0" class="trial-note warning">
<span
>ℹ️ 哈夫币使用已打超预收,超出金额 ¥{{
money(props.simulation.overshootYuan)
}}
已自动从押金抵扣。</span
>
</div>
<div v-if="!props.simulation.isDepositSufficient" class="trial-note danger">
<span
>⚠️ 押金不足以抵扣打超与赔偿,差额 ¥{{
money(props.simulation.shortfallYuan)
}},无法自动同意。</span
>
</div>
</div>
</template>
</div>
</template>
<style scoped>
.trial-preview-container {
margin-top: 12px;
padding: 12px 14px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
font-size: 13px;
color: #334155;
transition: all 0.2s ease;
}
.trial-preview-container.mobile {
margin: 10px 12px 16px;
padding: 10px 12px;
font-size: 12px;
}
.trial-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
padding-bottom: 6px;
border-bottom: 1px solid #e2e8f0;
}
.trial-title {
display: flex;
align-items: center;
gap: 6px;
font-weight: 600;
color: #1e293b;
}
.trial-icon {
font-style: normal;
}
.badge {
font-size: 11px;
padding: 2px 8px;
border-radius: 10px;
font-weight: 500;
}
.badge.success {
background: #dcfce7;
color: #15803d;
}
.badge.info {
background: #e0f2fe;
color: #0369a1;
}
.badge.danger {
background: #fee2e2;
color: #b91c1c;
}
.metrics-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-bottom: 8px;
}
.main-metric {
display: flex;
align-items: baseline;
justify-content: space-between;
padding: 6px 10px;
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 6px;
margin-bottom: 6px;
}
.main-metric .label {
font-size: 12px;
color: #64748b;
}
.main-metric .value {
font-size: 16px;
font-weight: 700;
color: #0f172a;
}
.main-metric .value.income {
color: #16a34a;
}
.main-metric .value.refund {
color: #0284c7;
}
.metric-breakdown {
display: flex;
flex-direction: column;
gap: 4px;
padding: 4px 6px;
}
.breakdown-item {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 12px;
color: #475569;
}
.breakdown-item.highlight {
font-weight: 600;
}
.text-green {
color: #16a34a;
font-weight: 600;
}
.text-danger {
color: #dc2626;
font-weight: 600;
}
.trial-note {
margin-top: 8px;
padding: 6px 10px;
border-radius: 6px;
font-size: 11px;
line-height: 1.4;
}
.trial-note.info {
background: #f0fdf4;
color: #166534;
border: 1px solid #bbf7d0;
}
.trial-note.warning {
background: #fffbeb;
color: #92400e;
border: 1px solid #fde68a;
}
.trial-note.danger {
background: #fef2f2;
color: #991b1b;
border: 1px solid #fecaca;
}
</style>