优化提号详情

This commit is contained in:
yml2213
2026-07-08 00:16:07 +08:00
parent 2935e16753
commit 8a5c9ab0be
16 changed files with 1083 additions and 119 deletions
@@ -46,6 +46,7 @@ export interface FinanceDailyItem {
export interface FinancePickupSummary {
settled_amount_cent: number
profit_amount_cent: number
completed_count: number
in_progress_count: number
}
@@ -118,6 +119,7 @@ export async function fetchFinanceDashboard(query: FinanceDateQuery = {}) {
daily_items: Array.isArray(data.data?.daily_items) ? data.data.daily_items : [],
pickup_summary: data.data?.pickup_summary ?? {
settled_amount_cent: 0,
profit_amount_cent: 0,
completed_count: 0,
in_progress_count: 0,
},
@@ -14,6 +14,13 @@ export interface AdminPickup {
owner_phone: string
admin_id: number
platform: string
listing_price_cent: number
owner_price_cent: number
website_profit_cent: number
profit_amount_cent: number
seller_ratio: number
buyer_ratio: number
account_snapshot?: Record<string, unknown>
settle_amount_cent: number
status: string
remark: string
@@ -32,16 +39,23 @@ export interface AvailableListing {
login_platform: string
owner_id: number
owner_phone: string
listing_price_cent: number
owner_price_cent: number
website_profit_cent: number
seller_ratio: number
buyer_ratio: number
}
export interface AdminPickupCreateRequest {
listing_id: number
platform?: string
profit_amount_cent?: number
remark?: string
}
export interface AdminPickupCompleteRequest {
settle_amount_cent: number
profit_amount_cent?: number
complete_remark?: string
}
@@ -72,6 +86,11 @@ export async function fetchAdminPickups(query: AdminPickupListQuery = {}) {
}
}
export async function fetchAdminPickup(id: string | number) {
const { data } = await apiClient.get<ApiResponse<AdminPickup>>(`/admin/pickups/${id}`)
return data.data
}
export async function fetchAvailableListings(keyword: string, page = 1, page_size = 20) {
const { data } = await apiClient.get<ApiResponse<PaginatedResult<AvailableListing>>>(
'/admin/pickups/available-listings',
@@ -145,6 +145,11 @@ function rowDiffClass(row: FinanceDailyItem) {
<strong>{{ moneyCent(dashboard.pickup_summary.settled_amount_cent) }}</strong>
<small>{{ dashboard.pickup_summary.completed_count }} 笔已完成</small>
</div>
<div class="metric-card pickup-card">
<span>线下提号利润</span>
<strong>{{ moneyCent(dashboard.pickup_summary.profit_amount_cent) }}</strong>
<small>电商平台手工录入利润</small>
</div>
<div class="metric-card pickup-card">
<span>提号中</span>
<strong>{{ dashboard.pickup_summary.in_progress_count }}</strong>
@@ -0,0 +1,531 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { fetchAdminPickup, type AdminPickup } from '@/features/admin/api/adminPickup'
import { quantity, readNumber, readUnitPrice } from '@/features/orders/composables/useOrderSnapshot'
import { adminPath } from '@/shared/utils/adminPath'
import { formatListingNo } from '@/shared/utils/listingDisplay'
import { formatCentWithSymbol, formatMoneyWithSymbol } from '@/shared/utils/money'
import { pickupStatusLabel } from '@/shared/utils/statusLabels'
import { formatDateTime } from '@/shared/utils/time'
interface SnapshotResource {
key: string
label: string
price: string
mode: string
quantity: number
amount: number
}
const route = useRoute()
const loading = ref(false)
const pickup = ref<AdminPickup | null>(null)
const listingCode = computed(() =>
pickup.value ? formatListingNo(pickup.value.listing_no, pickup.value.listing_id) : '-'
)
const snapshot = computed(() => normalizeRecord(pickup.value?.account_snapshot))
const assetSummary = computed(() => normalizeRecord(snapshot.value?.asset_summary))
const priceBreakdown = computed(() => normalizeRecord(assetSummary.value?.price_breakdown))
const snapshotText = computed(() =>
snapshot.value ? JSON.stringify(snapshot.value, null, 2) : '暂无账号快照'
)
const snapshotHafCoinM = computed(() =>
snapshot.value ? quantity(readNumber(snapshot.value.haf_coin_amount) / 1000000) : '-'
)
const snapshotSeasonTags = computed(() => {
const tags = snapshot.value?.season_tags
return Array.isArray(tags) ? tags.map(item => String(item)).filter(Boolean) : []
})
const snapshotResources = computed(() => readSnapshotResources(assetSummary.value))
const accountSnapshotItems = computed(() => {
const row = snapshot.value
if (!row) return []
return [
{ label: '账号ID', value: displayValue(row.account_id) },
{ label: '账号标题', value: displayValue(row.title) },
{ label: '游戏名称', value: displayValue(row.game_name) },
{ label: '所在区服', value: displayValue(row.server_region) },
{ label: '登录平台', value: displayValue(row.login_platform) },
{ label: '段位等级', value: displayValue(row.rank_level) },
].filter(item => item.value !== '-')
})
const moneySplitRows = computed(() => {
const row = pickup.value
if (!row) return []
const ownerCoinBasePriceCent = readBreakdownCent('seller_coin_base_price')
const ownerLossPriceCent =
readBreakdownCent('consumable_price') ?? fallbackOwnerLossCent(ownerCoinBasePriceCent)
const offlineTotalCent =
row.settle_amount_cent > 0 ? row.settle_amount_cent + row.profit_amount_cent : null
const rows = [
{ label: '网站售价', amountCent: row.listing_price_cent, tone: '' },
{ label: '号主纯币价格', amountCent: ownerCoinBasePriceCent, tone: '' },
{ label: '号主损耗', amountCent: ownerLossPriceCent, tone: '' },
{ label: '号主价合计', amountCent: row.owner_price_cent, tone: 'subtotal' },
{ label: '网站加价', amountCent: row.website_profit_cent, tone: '' },
{ label: '线下利润', amountCent: row.profit_amount_cent, tone: 'profit' },
{ label: '结算给号主', amountCent: row.settle_amount_cent, tone: '' },
]
if (offlineTotalCent !== null) {
rows.push({ label: '线下成交合计', amountCent: offlineTotalCent, tone: 'total' })
}
return rows
})
const breakdownRows = computed(() => {
const row = priceBreakdown.value
if (!row) return []
return [
{ label: '回收比例', value: ratioText(readNumber(row.seller_ratio)) },
{ label: '售卖比例', value: ratioText(readNumber(row.buyer_ratio)) },
{ label: '参考比例', value: ratioText(readNumber(row.seller_reference_ratio)) },
{ label: '加速出号比例', value: ratioText(readNumber(row.accelerated_sale_ratio)) },
{ label: '定价规则', value: displayValue(row.platform_rule_type) },
{ label: '调价原因', value: displayValue(row.admin_adjust_reason) },
{ label: '调价时间', value: displayValue(row.admin_adjusted_at) },
].filter(item => item.value !== '-')
})
onMounted(loadPickup)
async function loadPickup() {
loading.value = true
try {
pickup.value = await fetchAdminPickup(String(route.params.id))
} finally {
loading.value = false
}
}
function statusType(status: string) {
if (status === 'picking_up') return 'warning'
if (status === 'completed') return 'success'
if (status === 'cancelled') return 'info'
return ''
}
function ratioText(value: number) {
const ratio = Number(value || 0)
if (ratio <= 0) return '-'
const rounded = Math.round(ratio * 100) / 100
return `1:${Number.isInteger(rounded) ? rounded : rounded.toFixed(2)}`
}
function displayValue(value: unknown) {
if (value === undefined || value === null || value === '') return '-'
return String(value)
}
function readBreakdownCent(key: string) {
const value = readNumber(priceBreakdown.value?.[key])
return value > 0 ? Math.round(value * 100) : null
}
function fallbackOwnerLossCent(ownerCoinBasePriceCent: number | null) {
const row = pickup.value
if (!row || ownerCoinBasePriceCent === null) return null
return Math.max(row.owner_price_cent - ownerCoinBasePriceCent, 0)
}
function formatNullableCent(value: number | null | undefined) {
if (value === null || value === undefined) return '-'
return formatCentWithSymbol(value)
}
function normalizeRecord(value: unknown): Record<string, unknown> | null {
if (typeof value === 'object' && value !== null) return value as Record<string, unknown>
if (typeof value === 'string' && value.trim()) {
try {
const parsed = JSON.parse(value)
return typeof parsed === 'object' && parsed !== null ? parsed : null
} catch {
return null
}
}
return null
}
function readSnapshotResources(summary: Record<string, unknown> | null): SnapshotResource[] {
const resources = summary?.resources
if (!Array.isArray(resources)) return []
return resources
.filter((item): item is Record<string, unknown> => typeof item === 'object' && item !== null)
.map(item => {
const key = String(item.key || item.label || '')
const label = String(item.label || key || '额外消耗品')
const price = String(item.price || '')
const mode = String(item.mode || '收费')
const count = readNumber(item.quantity)
return {
key,
label,
price,
mode,
quantity: count,
amount: mode === '收费' ? count * readUnitPrice(price) : 0,
}
})
.filter(item => item.key && item.quantity > 0)
}
</script>
<template>
<section class="page" v-loading="loading">
<div v-if="pickup" class="page-header-row">
<div class="page-header">
<p class="eyebrow">{{ pickup.pickup_no }}</p>
<h1>提号详情</h1>
<p>
商品编号 {{ listingCode }} · {{ pickup.account_title }} · {{ pickup.server_region }} /
{{ pickup.login_platform }}
</p>
</div>
<div class="toolbar-actions">
<RouterLink :to="adminPath('pickup')">
<el-button>返回列表</el-button>
</RouterLink>
</div>
</div>
<div v-if="pickup" class="metric-grid pickup-metrics">
<div class="metric-card">
<span>提号状态</span>
<strong>{{ pickupStatusLabel(pickup.status) }}</strong>
<small>创建 {{ formatDateTime(pickup.created_at) }}</small>
</div>
<div class="metric-card">
<span>线下利润</span>
<strong>{{ formatCentWithSymbol(pickup.profit_amount_cent) }}</strong>
<small>财务统计使用该金额</small>
</div>
<div class="metric-card">
<span>结算金额</span>
<strong>{{ formatCentWithSymbol(pickup.settle_amount_cent) }}</strong>
<small>{{ pickup.completed_at ? formatDateTime(pickup.completed_at) : '待结算' }}</small>
</div>
<div class="metric-card">
<span>比例</span>
<strong>{{ ratioText(pickup.seller_ratio) }} / {{ ratioText(pickup.buyer_ratio) }}</strong>
<small>回收 / 售卖</small>
</div>
</div>
<div v-if="pickup" class="pickup-detail-grid">
<section class="dashboard-panel detail-panel">
<div class="panel-heading">
<h2>提号概览</h2>
<el-tag :type="statusType(pickup.status)" effect="light">
{{ pickupStatusLabel(pickup.status) }}
</el-tag>
</div>
<dl class="detail-list">
<div>
<dt>提号编号</dt>
<dd>{{ pickup.pickup_no }}</dd>
</div>
<div>
<dt>上架编号</dt>
<dd>{{ listingCode }}</dd>
</div>
<div>
<dt>账号ID</dt>
<dd>{{ pickup.account_id }}</dd>
</div>
<div>
<dt>号主</dt>
<dd>{{ pickup.owner_phone || pickup.owner_id }}</dd>
</div>
<div>
<dt>交易渠道</dt>
<dd>{{ pickup.platform || '-' }}</dd>
</div>
<div>
<dt>管理员ID</dt>
<dd>{{ pickup.admin_id }}</dd>
</div>
<div>
<dt>创建时间</dt>
<dd>{{ formatDateTime(pickup.created_at) }}</dd>
</div>
<div>
<dt>完成时间</dt>
<dd>{{ formatDateTime(pickup.completed_at, '未完成') }}</dd>
</div>
</dl>
</section>
<section class="dashboard-panel detail-panel">
<div class="panel-heading">
<h2>资金拆分</h2>
<span class="panel-subtitle">提号创建时固化</span>
</div>
<div class="money-list">
<div
v-for="row in moneySplitRows"
:key="row.label"
class="money-row"
:class="`is-${row.tone || 'normal'}`"
>
<span>{{ row.label }}</span>
<strong>{{ formatNullableCent(row.amountCent) }}</strong>
</div>
</div>
</section>
<section class="dashboard-panel detail-panel">
<div class="panel-heading">
<h2>备注</h2>
</div>
<dl class="detail-list">
<div class="wide">
<dt>创建备注</dt>
<dd>{{ pickup.remark || '-' }}</dd>
</div>
<div class="wide">
<dt>完成备注</dt>
<dd>{{ pickup.complete_remark || '-' }}</dd>
</div>
</dl>
</section>
<section v-if="breakdownRows.length" class="dashboard-panel detail-panel">
<div class="panel-heading">
<h2>价格拆分</h2>
<span class="panel-subtitle">来自账号快照</span>
</div>
<dl class="detail-list">
<div v-for="row in breakdownRows" :key="row.label">
<dt>{{ row.label }}</dt>
<dd>{{ row.value }}</dd>
</div>
</dl>
</section>
<section class="dashboard-panel detail-panel pickup-wide-panel">
<div class="panel-heading">
<h2>账号快照</h2>
<span class="panel-subtitle">哈夫币 {{ snapshotHafCoinM }}M</span>
</div>
<dl v-if="accountSnapshotItems.length" class="detail-list snapshot-detail-list">
<div v-for="item in accountSnapshotItems" :key="item.label">
<dt>{{ item.label }}</dt>
<dd>{{ item.value }}</dd>
</div>
</dl>
<div v-if="snapshotSeasonTags.length" class="snapshot-tags">
<el-tag v-for="tag in snapshotSeasonTags" :key="tag" size="small" effect="plain">
{{ tag }}
</el-tag>
</div>
<div v-if="snapshotResources.length" class="snapshot-resources">
<div v-for="resource in snapshotResources" :key="resource.key" class="resource-item">
<strong>{{ resource.label }}</strong>
<span>
{{ quantity(resource.quantity) }} · {{ resource.price || resource.mode }}
<template v-if="resource.amount > 0">
· {{ formatMoneyWithSymbol(resource.amount) }}</template
>
</span>
</div>
</div>
<pre class="snapshot-json">{{ snapshotText }}</pre>
</section>
</div>
</section>
</template>
<style scoped>
.pickup-metrics {
margin-bottom: 20px;
}
.pickup-metrics .metric-card strong {
font-size: 20px;
line-height: 1.25;
overflow-wrap: anywhere;
}
.metric-card small {
display: block;
margin-top: 8px;
color: #8f9bba;
font-size: 12px;
line-height: 1.4;
word-break: break-word;
}
.pickup-detail-grid {
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(360px, 0.9fr);
gap: 20px;
margin-bottom: 28px;
}
.detail-panel {
min-width: 0;
}
.pickup-wide-panel {
grid-column: 1 / -1;
}
.panel-heading {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
}
.panel-heading h2 {
margin: 0;
}
.panel-subtitle {
color: #8f9bba;
font-size: 13px;
line-height: 1.6;
text-align: right;
}
.detail-list {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px 18px;
margin: 0;
}
.detail-list div {
min-width: 0;
border-top: 1px solid #f0f2f5;
padding-top: 10px;
}
.detail-list .wide {
grid-column: 1 / -1;
}
.detail-list dt {
margin-bottom: 5px;
color: #8f9bba;
font-size: 12px;
font-weight: 600;
}
.detail-list dd {
margin: 0;
color: #1b2559;
font-size: 14px;
font-weight: 700;
line-height: 1.5;
overflow-wrap: anywhere;
white-space: pre-wrap;
}
.money-list {
display: grid;
gap: 8px;
}
.money-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
border-top: 1px solid #f0f2f5;
padding-top: 9px;
color: #52616f;
font-size: 13px;
}
.money-row strong {
color: #1b2559;
font-size: 15px;
}
.money-row.is-subtotal {
border-top-color: #d8e2f0;
color: #1b2559;
font-weight: 700;
}
.money-row.is-profit strong {
color: #0f766e;
}
.money-row.is-total {
margin-top: 4px;
border-top-color: #bfdbfe;
color: #1b2559;
font-weight: 800;
}
.money-row.is-total strong {
font-size: 18px;
}
.snapshot-detail-list {
margin-bottom: 14px;
}
.snapshot-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 14px;
}
.snapshot-resources {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 10px;
margin-bottom: 14px;
}
.resource-item {
display: grid;
gap: 4px;
border: 1px solid #eef2f7;
border-radius: 8px;
background: #fbfcff;
padding: 10px 12px;
}
.resource-item strong {
color: #1b2559;
font-size: 13px;
}
.resource-item span {
color: #52616f;
font-size: 12px;
}
.snapshot-json {
overflow-x: auto;
max-height: 360px;
margin: 0;
border-radius: 8px;
background: #0f172a;
color: #dbeafe;
padding: 14px;
font-size: 12px;
line-height: 1.6;
}
@media (max-width: 960px) {
.pickup-detail-grid {
grid-template-columns: 1fr;
}
.pickup-wide-panel {
grid-column: auto;
}
.detail-list {
grid-template-columns: 1fr;
}
}
</style>
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { computed, onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Refresh, Search } from '@element-plus/icons-vue'
import {
@@ -11,9 +11,10 @@ import {
type AdminPickup,
type AvailableListing,
} from '@/features/admin/api/adminPickup'
import { formatCentWithSymbol } from '@/shared/utils/money'
import { centToYuan, formatCentWithSymbol, yuanToCent } from '@/shared/utils/money'
import { formatDateTime } from '@/shared/utils/time'
import { pickupStatusLabel } from '@/shared/utils/statusLabels'
import { adminPath } from '@/shared/utils/adminPath'
const loading = ref(false)
const items = ref<AdminPickup[]>([])
@@ -27,12 +28,16 @@ const activePickupId = ref(0)
const createForm = reactive({
listing_id: null as number | null,
platform: '',
profit_amount: 0,
remark: '',
})
const completeForm = reactive({ settle_amount: 0, complete_remark: '' })
const completeForm = reactive({ settle_amount: 0, profit_amount: 0, complete_remark: '' })
const listingOptions = ref<AvailableListing[]>([])
const listingLoading = ref(false)
const selectedListing = computed(
() => listingOptions.value.find(item => item.id === createForm.listing_id) || null
)
onMounted(loadList)
@@ -64,6 +69,7 @@ function handleSizeChange(s: number) {
function openCreateDialog() {
createForm.listing_id = null
createForm.platform = ''
createForm.profit_amount = 0
createForm.remark = ''
listingOptions.value = []
createDialogVisible.value = true
@@ -88,11 +94,16 @@ async function handleCreate() {
ElMessage.warning('请选择要提号的账号')
return
}
if (createForm.profit_amount < 0) {
ElMessage.warning('线下利润不能为负数')
return
}
loading.value = true
try {
await createAdminPickup({
listing_id: createForm.listing_id,
platform: createForm.platform,
profit_amount_cent: yuanToCent(createForm.profit_amount),
remark: createForm.remark,
})
ElMessage.success('提号订单已创建')
@@ -105,9 +116,10 @@ async function handleCreate() {
}
}
function openCompleteDialog(id: number) {
activePickupId.value = id
function openCompleteDialog(row: AdminPickup) {
activePickupId.value = row.id
completeForm.settle_amount = 0
completeForm.profit_amount = centToYuan(row.profit_amount_cent)
completeForm.complete_remark = ''
completeDialogVisible.value = true
}
@@ -117,10 +129,15 @@ async function handleComplete() {
ElMessage.warning('请输入结算金额')
return
}
if (completeForm.profit_amount < 0) {
ElMessage.warning('线下利润不能为负数')
return
}
loading.value = true
try {
await completeAdminPickup(activePickupId.value, {
settle_amount_cent: Math.round(completeForm.settle_amount * 100),
settle_amount_cent: yuanToCent(completeForm.settle_amount),
profit_amount_cent: yuanToCent(completeForm.profit_amount),
complete_remark: completeForm.complete_remark,
})
ElMessage.success('提号已完成,已给卖家结算')
@@ -169,6 +186,13 @@ function errorMessage(e: unknown) {
}
return ''
}
function ratioText(value: number) {
const ratio = Number(value || 0)
if (ratio <= 0) return '-'
const rounded = Math.round(ratio * 100) / 100
return `1:${Number.isInteger(rounded) ? rounded : rounded.toFixed(2)}`
}
</script>
<template>
@@ -205,7 +229,9 @@ function errorMessage(e: unknown) {
<el-table-column prop="pickup_no" label="提号编号" width="190" />
<el-table-column prop="account_title" label="账号" min-width="180" />
<el-table-column label="区服/平台" width="150">
<template #default="{ row }">{{ row.server_region || '-' }} / {{ row.login_platform || '-' }}</template>
<template #default="{ row }"
>{{ row.server_region || '-' }} / {{ row.login_platform || '-' }}</template
>
</el-table-column>
<el-table-column label="上架编号" width="120">
<template #default="{ row }">{{ row.listing_no }}</template>
@@ -218,7 +244,9 @@ function errorMessage(e: unknown) {
</el-table-column>
<el-table-column label="结算金额" width="130">
<template #default="{ row }">
<span v-if="row.status === 'completed'">{{ formatCentWithSymbol(row.settle_amount_cent) }}</span>
<span v-if="row.status === 'completed'">{{
formatCentWithSymbol(row.settle_amount_cent)
}}</span>
<span v-else class="text-muted">待结算</span>
</template>
</el-table-column>
@@ -232,26 +260,30 @@ function errorMessage(e: unknown) {
<el-table-column label="创建时间" width="170">
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right">
<el-table-column label="操作" width="220" fixed="right">
<template #default="{ row }">
<el-button
v-if="row.status === 'picking_up'"
type="primary"
size="small"
@click="openCompleteDialog(row.id)"
>
完成结算
</el-button>
<el-button
v-if="row.status === 'picking_up'"
type="danger"
size="small"
plain
@click="handleCancel(row.id)"
>
取消
</el-button>
<span v-if="row.status !== 'picking_up'" class="text-muted"></span>
<div class="action-buttons">
<RouterLink :to="adminPath(`pickup/${row.id}`)">
<el-button size="small">详情</el-button>
</RouterLink>
<el-button
v-if="row.status === 'picking_up'"
type="primary"
size="small"
@click="openCompleteDialog(row)"
>
完成结算
</el-button>
<el-button
v-if="row.status === 'picking_up'"
type="danger"
size="small"
plain
@click="handleCancel(row.id)"
>
取消
</el-button>
</div>
</template>
</el-table-column>
</el-table>
@@ -285,15 +317,35 @@ function errorMessage(e: unknown) {
<el-option
v-for="item in listingOptions"
:key="item.id"
:label="`${item.listing_no} · ${item.account_title}`"
:label="`${item.listing_no} · ${item.account_title} · ${formatCentWithSymbol(item.owner_price_cent)} / ${formatCentWithSymbol(item.listing_price_cent)}`"
:value="item.id"
/>
</el-select>
<div class="form-hint">仅显示已发布已审核未在交易中的账号</div>
</el-form-item>
<el-form-item v-if="selectedListing" label="价格快照">
<div class="price-snapshot">
<span>号主价 {{ formatCentWithSymbol(selectedListing.owner_price_cent) }}</span>
<span>网站售价 {{ formatCentWithSymbol(selectedListing.listing_price_cent) }}</span>
<span>网站加价 {{ formatCentWithSymbol(selectedListing.website_profit_cent) }}</span>
<span>回收 {{ ratioText(selectedListing.seller_ratio) }}</span>
<span>售卖 {{ ratioText(selectedListing.buyer_ratio) }}</span>
</div>
</el-form-item>
<el-form-item label="交易渠道">
<el-input v-model="createForm.platform" placeholder="如 微信 / QQ / 闲鱼" />
</el-form-item>
<el-form-item label="线下利润(元)">
<el-input-number
v-model="createForm.profit_amount"
:min="0"
:step="1"
:precision="2"
controls-position="right"
style="width: 100%"
/>
<div class="form-hint">电商平台实际成交利润财务统计按该金额汇总</div>
</el-form-item>
<el-form-item label="备注">
<el-input
v-model="createForm.remark"
@@ -326,6 +378,17 @@ function errorMessage(e: unknown) {
/>
<div class="form-hint">给卖家钱包增加的可用余额</div>
</el-form-item>
<el-form-item label="线下利润(元)">
<el-input-number
v-model="completeForm.profit_amount"
:min="0"
:step="1"
:precision="2"
controls-position="right"
style="width: 100%"
/>
<div class="form-hint">可在结算时修正为最终利润财务统计使用该值</div>
</el-form-item>
<el-form-item label="完成备注">
<el-input
v-model="completeForm.complete_remark"
@@ -335,7 +398,7 @@ function errorMessage(e: unknown) {
/>
</el-form-item>
<el-alert type="warning" :closable="false">
完成后订单置为"已完成"卖家钱包立即入账账号下架不可再上架操作不可撤销
完成后提号记录置为"已完成"卖家钱包立即入账账号下架不可再上架操作不可撤销
</el-alert>
</el-form>
<template #footer>
@@ -355,4 +418,17 @@ function errorMessage(e: unknown) {
.text-muted {
color: #94a3b8;
}
.price-snapshot {
display: flex;
flex-wrap: wrap;
gap: 8px 12px;
color: #475569;
font-size: 13px;
line-height: 1.6;
}
.action-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
</style>
@@ -44,6 +44,13 @@ function statusType(status: string) {
if (status === 'cancelled') return 'info'
return ''
}
function ratioText(value: number) {
const ratio = Number(value || 0)
if (ratio <= 0) return '-'
const rounded = Math.round(ratio * 100) / 100
return `1:${Number.isInteger(rounded) ? rounded : rounded.toFixed(2)}`
}
</script>
<template>
@@ -70,11 +77,19 @@ function statusType(status: string) {
<el-table-column prop="pickup_no" label="提号编号" width="190" />
<el-table-column prop="account_title" label="账号" min-width="180" />
<el-table-column label="区服/平台" width="150">
<template #default="{ row }">{{ row.server_region || '-' }} / {{ row.login_platform || '-' }}</template>
<template #default="{ row }"
>{{ row.server_region || '-' }} / {{ row.login_platform || '-' }}</template
>
</el-table-column>
<el-table-column label="交易渠道" width="110">
<template #default="{ row }">{{ row.platform || '-' }}</template>
</el-table-column>
<el-table-column label="号主价" width="120">
<template #default="{ row }">{{ formatCentWithSymbol(row.owner_price_cent) }}</template>
</el-table-column>
<el-table-column label="回收比例" width="110">
<template #default="{ row }">{{ ratioText(row.seller_ratio) }}</template>
</el-table-column>
<el-table-column label="结算金额" width="140">
<template #default="{ row }">
<span v-if="row.status === 'completed'" class="amount-in">
@@ -100,7 +115,9 @@ function statusType(status: string) {
</el-table-column>
<el-table-column label="备注" min-width="160">
<template #default="{ row }">
<span v-if="row.status === 'completed' && row.complete_remark">{{ row.complete_remark }}</span>
<span v-if="row.status === 'completed' && row.complete_remark">{{
row.complete_remark
}}</span>
<span v-else-if="row.remark">{{ row.remark }}</span>
<span v-else class="text-muted"></span>
</template>
+6
View File
@@ -42,6 +42,12 @@ export const adminRoutes: RouteRecordRaw[] = [
component: () => import('@/features/admin/views/AdminPickupView.vue'),
meta: adminMeta,
},
{
path: adminPath('pickup/:id'),
name: 'admin-pickup-detail',
component: () => import('@/features/admin/views/AdminPickupDetailView.vue'),
meta: adminMeta,
},
{
path: adminPath('orders/:id'),
name: 'admin-order-detail',