实现租客成长等级并优化展示

This commit is contained in:
yml2213
2026-07-26 11:25:25 +08:00
parent 756fa342cb
commit 321f0781cf
27 changed files with 948 additions and 16 deletions
@@ -13,6 +13,9 @@ export interface AdminUserItem {
deposit_free_quota_cent: number
deposit_free_used_cent: number
deposit_free_remaining_cent: number
renter_growth_points: number
renter_growth_level: string
renter_growth_level_name: string
available_balance_cent: number
frozen_balance_cent: number
status: UserStatus
@@ -266,7 +266,17 @@ const ownerLossPriceCent = computed(() => {
const hasOwnerRentBreakdown = computed(() => ownerCoinBasePriceCent.value !== null)
const fundSplitRows = computed(() => {
if (!order.value) return []
const rows: FundSplitRow[] = [{ label: '租客租金', amountCent: order.value.rent_amount_cent }]
const rows: FundSplitRow[] = []
if (Number(order.value.rent_discount_amount_cent || 0) > 0) {
rows.push(
{ label: '原始租金', amountCent: order.value.rent_original_amount_cent },
{
label: `${order.value.renter_growth_level_name || '成长等级'}优惠`,
amountCent: -Number(order.value.rent_discount_amount_cent || 0),
}
)
}
rows.push({ label: '租客租金', amountCent: order.value.rent_amount_cent })
if (hasOwnerRentBreakdown.value) {
rows.push(
{ label: '号主纯币价格', amountCent: ownerCoinBasePriceCent.value },
@@ -843,7 +853,12 @@ function paymentPaidAt(record: AdminPayment) {
<el-button v-if="canResetHandoff" type="success" @click="openAction('reset')">
{{ resetActionLabel }}
</el-button>
<el-button v-if="canPlatformHandoff" type="primary" plain @click="openPlatformHandoff">
<el-button
v-if="canPlatformHandoff"
class="platform-handoff-button"
type="primary"
@click="openPlatformHandoff"
>
{{ platformHandoffAction?.label || '客服代交接' }}
</el-button>
<el-button
@@ -1551,6 +1566,27 @@ function paymentPaidAt(record: AdminPayment) {
word-break: break-word;
}
.platform-handoff-button {
min-width: 96px;
border-color: #2563eb;
background: #2563eb;
color: #ffffff;
font-weight: 700;
box-shadow: 0 6px 14px rgba(37, 99, 235, 0.18);
}
.platform-handoff-button:hover,
.platform-handoff-button:focus {
border-color: #1d4ed8;
background: #1d4ed8;
color: #ffffff;
}
.platform-handoff-button:active {
border-color: #1e40af;
background: #1e40af;
}
.order-detail-grid {
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(360px, 0.9fr);
@@ -312,6 +312,14 @@ function moneyCent(value: number | string | undefined) {
<el-table-column label="实名状态" width="100">
<template #default="{ row }">{{ realnameStatusLabel(row.realname_status) }}</template>
</el-table-column>
<el-table-column label="成长等级" width="100">
<template #default="{ row }">
<el-tag type="success" effect="light">{{ row.renter_growth_level_name || '普通' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="成长积分" width="100" align="right">
<template #default="{ row }">{{ Number(row.renter_growth_points || 0) }}</template>
</el-table-column>
<el-table-column label="可用余额" width="108" align="right">
<template #default="{ row }">
<span class="balance-available">¥{{ moneyCent(row.available_balance_cent) }}</span>
+2
View File
@@ -10,6 +10,8 @@ export interface AuthUser {
realname_status: RealnameStatusValue
risk_status: RiskStatus
credit_score: number
renter_growth_points?: number
renter_growth_level?: string
status: UserStatus
}
@@ -104,11 +104,16 @@ const settingsGroups = [
},
]
onMounted(() => {
onMounted(async () => {
if (!isLoggedIn.value) {
router.replace({ path: '/m/login', query: { redirect: route.fullPath } })
} else {
loadBalance()
try {
await session.loadMe()
} catch {
// 保留本地会话展示,避免个人中心因网络短暂异常整页不可用。
}
void loadBalance()
}
})
@@ -348,6 +353,17 @@ function resolveAvatarURL(url: string | undefined | null) {
</div>
</section>
<section class="growth-card">
<div>
<span>租客成长等级</span>
<strong>{{ session.renterGrowthLevelName }}</strong>
</div>
<div>
<span>当前积分</span>
<strong>{{ session.renterGrowthPoints }}</strong>
</div>
</section>
<!-- 余额卡片 - 银行卡质感暗色卡片 -->
<div class="balance-card">
<div class="balance-row">
@@ -780,6 +796,39 @@ function resolveAvatarURL(url: string | undefined | null) {
font-size: 12px;
}
.growth-card {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin: 12px 16px 0;
}
.growth-card > div {
display: grid;
gap: 4px;
min-width: 0;
padding: 14px;
border: 1px solid rgba(243, 244, 246, 0.9);
border-radius: 16px;
background: #ffffff;
box-shadow:
0 4px 16px rgba(0, 0, 0, 0.02),
0 1px 4px rgba(0, 0, 0, 0.02);
}
.growth-card span {
color: #9ca3af;
font-size: 11px;
font-weight: 700;
}
.growth-card strong {
color: #7c3aed;
font-size: 20px;
line-height: 1.2;
font-weight: 900;
}
/* ========== Balance Card ========== */
.balance-card {
margin: 12px 16px 14px;
@@ -50,8 +50,10 @@ const realnameTone = computed(() => {
})
onMounted(async () => {
if (!session.phone) {
try {
await session.loadMe()
} catch {
// 保留本地会话展示,避免资料页因网络短暂异常整页不可用。
}
resetForm()
})
@@ -258,6 +260,14 @@ async function savePassword() {
<dt>实名状态</dt>
<dd :class="`is-${realnameTone}`">{{ realnameStatusLabel(session.realnameStatus) }}</dd>
</div>
<div>
<dt>租客等级</dt>
<dd class="growth-level">{{ session.renterGrowthLevelName }}</dd>
</div>
<div>
<dt>成长积分</dt>
<dd>{{ session.renterGrowthPoints }}</dd>
</div>
</dl>
<button class="realname-shortcut" type="button" @click="router.push('/realname')">
<span>查看实名认证</span>
@@ -604,6 +614,10 @@ async function savePassword() {
text-align: right;
}
.account-card dd.growth-level {
color: #7c3aed;
}
.realname-shortcut {
display: flex;
align-items: center;
@@ -27,6 +27,13 @@ export interface Order {
deposit_original_amount_cent: number
deposit_waived_amount_cent: number
platform_fee_cent?: number
rent_original_amount_cent?: number
rent_discount_amount_cent?: number
renter_growth_level?: string
renter_growth_level_name?: string
renter_discount_bps?: number
growth_points_awarded?: number
growth_points_awarded_at?: string
account_snapshot?: Record<string, unknown>
listing_snapshot?: string
checkout_info?: string
@@ -188,6 +188,14 @@ async function copyListingCode() {
<div class="meta-item">
<span class="meta-label">{{ orderAmountLabel }}</span>
<strong class="meta-value accent">¥{{ money(orderRentDisplayAmount) }}</strong>
<span
v-if="isRenter && Number(order.rent_discount_amount_cent || 0) > 0"
class="meta-note"
>
{{ order.renter_growth_level_name || '成长等级' }}优惠 ¥{{
money(amountYuan(order.rent_discount_amount_cent))
}}
</span>
</div>
<div v-if="isOwner && ownerIncomeDisplayAmount !== null" class="meta-item">
<span class="meta-label">{{ ownerIncomeLabel }}</span>
@@ -388,6 +388,14 @@ watch([() => route.query.focus, order, loading], () => {
<div class="metric-card highlight">
<span class="metric-label">{{ orderAmountLabel }}</span>
<strong class="metric-value amount">¥{{ money(orderRentDisplayAmount) }}</strong>
<span
v-if="isRenter && Number(order.rent_discount_amount_cent || 0) > 0"
class="metric-note"
>
{{ order.renter_growth_level_name || '成长等级' }}优惠 ¥{{
money(amountYuan(order.rent_discount_amount_cent))
}}
</span>
</div>
<div v-if="isOwner && ownerIncomeDisplayAmount !== null" class="metric-card income">
<span class="metric-label">{{ ownerIncomeLabel }}</span>