diff --git a/frontend/src/features/admin/views/AdminListingDetailView.vue b/frontend/src/features/admin/views/AdminListingDetailView.vue index 236e0ad..f5fc860 100644 --- a/frontend/src/features/admin/views/AdminListingDetailView.vue +++ b/frontend/src/features/admin/views/AdminListingDetailView.vue @@ -9,7 +9,11 @@ import { fetchAdminUsers, type AdminUserItem } from '@/features/admin/api/adminU import { fetchAdminFileBlob } from '@/shared/api/files' import AuthImage from '@/shared/components/business/AuthImage.vue' import { adminPath } from '@/shared/utils/adminPath' -import { formatCentWithSymbol } from '@/shared/utils/money' +import { + formatCentWithSymbol, + formatMoneyWithSymbol, + roundMoney, +} from '@/shared/utils/money' import { adminMarkListingAbnormal, adminOfflineListing, @@ -31,7 +35,9 @@ import { getCoinWan, getDailyLoss, getListingConsumablePrice, + getListingDisplayPrice, getListingResources, + getListingSellerPrice, getOnlineTimeText, getResourceQuantity, getSkinNames, @@ -88,21 +94,28 @@ const assetRows = computed(() => { }) const ownerRows = computed(() => { if (!listing.value) return [] + const row = listing.value + const renterPrice = buyerTotalPrice(row) + const ownerCoinBase = sellerCoinBasePrice(row) + const ownerLoss = getListingConsumablePrice(row) + const ownerTotal = sellerTotalPrice(row) + const platformFee = platformMarkupAmount(row) return [ { label: '号主', - value: listing.value.owner_phone || listing.value.owner_nickname || listing.value.owner_id, - }, - { label: '号主 ID', value: listing.value.owner_id }, - { label: '价格', value: listingPrice(listing.value) }, - { label: '押金', value: moneyCent(listing.value.deposit_amount_cent) }, - { label: '哈夫币', value: formatHafCoinM(getCoinWan(listing.value)) }, - { label: '每日损耗', value: getDailyLoss(listing.value) || '-' }, - { label: '预计可租', value: formatEstimatedRentalDuration(listing.value) }, - { - label: '额外消耗', - value: moneyCent(Math.round(getListingConsumablePrice(listing.value) * 100)), + value: row.owner_phone || row.owner_nickname || row.owner_id, }, + { label: '号主 ID', value: row.owner_id }, + // 价格字段与订单详情「资金拆分」对齐,便于客服核对 + { label: '租客租金', value: moneyYuan(renterPrice) }, + { label: '号主纯币价格', value: moneyYuan(ownerCoinBase) }, + { label: '号主损耗', value: moneyYuan(ownerLoss) }, + { label: '号主租金合计', value: moneyYuan(ownerTotal) }, + { label: '平台费用', value: moneyYuan(platformFee) }, + { label: '押金', value: moneyCent(row.deposit_amount_cent) }, + { label: '哈夫币', value: formatHafCoinM(getCoinWan(row)) }, + { label: '每日损耗', value: getDailyLoss(row) || '-' }, + { label: '预计可租', value: formatEstimatedRentalDuration(row) }, ] }) const resourceCards = computed(() => { @@ -240,8 +253,56 @@ function moneyCent(value: number) { return formatCentWithSymbol(value) } +function moneyYuan(value: number) { + return formatMoneyWithSymbol(value) +} + function listingPrice(row: Listing) { - return moneyCent(row.price_cent) + return moneyYuan(buyerTotalPrice(row)) +} + +function priceBreakdown(row: Listing) { + const breakdown = row.asset_summary?.price_breakdown + return typeof breakdown === 'object' && breakdown !== null + ? (breakdown as Record) + : {} +} + +function breakdownNumber(row: Listing, key: string) { + const value = priceBreakdown(row)[key] + if (typeof value === 'number') return value + if (typeof value === 'string' && value.trim()) { + const parsed = Number(value) + return Number.isFinite(parsed) ? parsed : 0 + } + return 0 +} + +/** 租客侧展示总价(含平台加价与额外消耗) */ +function buyerTotalPrice(row: Listing) { + const value = breakdownNumber(row, 'buyer_total_price') + return value > 0 ? roundMoney(value) : roundMoney(getListingDisplayPrice(row)) +} + +/** 号主纯币基础价 */ +function sellerCoinBasePrice(row: Listing) { + const value = breakdownNumber(row, 'seller_coin_base_price') + if (value > 0) return roundMoney(value) + return Math.max(0, roundMoney(sellerTotalPrice(row) - getListingConsumablePrice(row))) +} + +/** 号主租金合计 = 纯币基础价 + 额外消耗 */ +function sellerTotalPrice(row: Listing) { + const value = breakdownNumber(row, 'seller_total_price') + if (value > 0) return roundMoney(value) + return roundMoney(getListingSellerPrice(row)) +} + +/** 平台费用 = 租客价 - 号主价 */ +function platformMarkupAmount(row: Listing) { + const value = breakdownNumber(row, 'platform_markup_amount') + if (value > 0) return roundMoney(value) + return Math.max(0, roundMoney(buyerTotalPrice(row) - sellerTotalPrice(row))) } function assetText(row: Listing, key: string) { diff --git a/frontend/src/features/listings/views/MobileHomeFilterSheet.vue b/frontend/src/features/listings/views/MobileHomeFilterSheet.vue index aacb4c0..b79bafd 100644 --- a/frontend/src/features/listings/views/MobileHomeFilterSheet.vue +++ b/frontend/src/features/listings/views/MobileHomeFilterSheet.vue @@ -464,7 +464,8 @@ function sectionTopInBody(section: HTMLElement, body: HTMLElement) { border-radius: 8px; background: #fff; color: #333; - font-size: 13px; + /* iOS Safari:font-size < 16px 时聚焦会自动放大页面,需手动缩小才能恢复 */ + font-size: 16px; text-align: center; } diff --git a/frontend/src/features/listings/views/MobileHomeView.css b/frontend/src/features/listings/views/MobileHomeView.css index e96878f..8f9d8d7 100644 --- a/frontend/src/features/listings/views/MobileHomeView.css +++ b/frontend/src/features/listings/views/MobileHomeView.css @@ -81,7 +81,8 @@ } .home-search :deep(.van-field__control) { - font-size: 14px; + /* iOS Safari:font-size < 16px 时聚焦会自动放大页面 */ + font-size: 16px; } .mobile-service { diff --git a/frontend/src/features/orders/components/OrderResourceUsageEditor.vue b/frontend/src/features/orders/components/OrderResourceUsageEditor.vue index 607f6dd..20edf46 100644 --- a/frontend/src/features/orders/components/OrderResourceUsageEditor.vue +++ b/frontend/src/features/orders/components/OrderResourceUsageEditor.vue @@ -634,7 +634,8 @@ function updateNumberField(field: 'coin_consumed_m' | 'otherAmountYuan', event: border: none; outline: none; background: transparent; - font-size: 14px; + /* iOS Safari:font-size < 16px 时聚焦会自动放大页面 */ + font-size: 16px; color: #182232; } diff --git a/frontend/src/features/orders/views/MobileOrderDetailView.vue b/frontend/src/features/orders/views/MobileOrderDetailView.vue index 7514f16..9a8cfc7 100644 --- a/frontend/src/features/orders/views/MobileOrderDetailView.vue +++ b/frontend/src/features/orders/views/MobileOrderDetailView.vue @@ -821,7 +821,8 @@ async function copyListingCode() { border: none; background: transparent; outline: none; - font-size: 14px; + /* iOS Safari:font-size < 16px 时聚焦会自动放大页面 */ + font-size: 16px; flex: 1; } diff --git a/frontend/src/features/seller/views/MobileSellerListingCreateView.css b/frontend/src/features/seller/views/MobileSellerListingCreateView.css index 9a9457c..75a5b9d 100644 --- a/frontend/src/features/seller/views/MobileSellerListingCreateView.css +++ b/frontend/src/features/seller/views/MobileSellerListingCreateView.css @@ -84,7 +84,8 @@ .publish-field :deep(.van-field__control) { color: #20242a; - font-size: 14px; + /* iOS Safari:font-size < 16px 时聚焦会自动放大页面 */ + font-size: 16px; } .hint-label, diff --git a/frontend/src/styles/base.css b/frontend/src/styles/base.css index 1bbe58e..44ac404 100644 --- a/frontend/src/styles/base.css +++ b/frontend/src/styles/base.css @@ -37,6 +37,21 @@ a { text-decoration: none; } +/* + * iOS Safari 在 input/textarea/select 的 font-size < 16px 时, + * 聚焦会自动放大页面,导致 UI 被轻微放大且需手动缩小才能恢复。 + * 在触控设备上统一保证可编辑控件至少 16px。 + * 组件内若需更小视觉字号,请用 transform: scale() 等方式,不要直接用 <16px 的 font-size。 + */ +@media (hover: none) and (pointer: coarse) { + input:not([type='checkbox']):not([type='radio']):not([type='range']):not([type='file']):not([type='button']):not([type='submit']):not([type='reset']):not([type='image']), + textarea, + select, + .van-field__control { + font-size: 16px !important; + } +} + /* ── Vant Toast 保护规则 ────────────────────────────────── .van-popup 声明 background: var(--van-popup-background) = #fff (白色) .van-toast 声明 background: var(--van-toast-background) = rgba(0,0,0,.7) (黑色)