feat(pickup): 线下提号支持扩展字段店铺管理CRUD及时间筛选
- 线下提号创建表单扩展:增加「操作人」、「第三方单号」、「下单金额」、「押金金额」字段,重构弹窗布局为紧凑双列/三列金额卡片排布,消除过度垂直滚动并默认预填操作人 - 店铺管理 CRUD:新增 pickup_shops 配置表与配套 API,支持在提号页面直接管理店铺(增删改查、启用/禁用、筛选),下拉选项自动优先加载活跃配置店铺并兼顾历史店铺 - 列表时间范围筛选:增加精确到秒的「日期+时间」时间范围选择器,支持快捷时段与后端时间范围过滤 - 生产环境数据核查:只读提取线上全部 61 项历史提号店铺名称与单量分布,完整整理沉淀至实施文档供后续映射规则参考 - 验证方式:执行并通过全套端到端代码门禁(scripts/check.sh:SQL约束、go vet、go build、MySQL真实测试库单元测试及覆盖率、vue大小检查、prettier、eslint、vue-tsc及vite构建)
This commit is contained in:
@@ -97,6 +97,12 @@ const moneySplitRows = computed(() => {
|
||||
{ label: '线下利润', amountCent: effectiveProfitCent(row), tone: 'profit' },
|
||||
{ label: '结算给号主', amountCent: effectiveSettleCent(row), tone: '' },
|
||||
]
|
||||
if (row.order_amount_cent > 0) {
|
||||
rows.push({ label: '下单金额', amountCent: row.order_amount_cent, tone: '' })
|
||||
}
|
||||
if (row.deposit_amount_cent > 0) {
|
||||
rows.push({ label: '押金金额', amountCent: row.deposit_amount_cent, tone: '' })
|
||||
}
|
||||
if (offlineTotalCent !== null) {
|
||||
rows.push({ label: '线下成交合计', amountCent: offlineTotalCent, tone: 'total' })
|
||||
}
|
||||
@@ -454,10 +460,36 @@ function readSnapshotResources(summary: Record<string, unknown> | null): Snapsho
|
||||
<dt>交易渠道</dt>
|
||||
<dd>{{ pickup.platform || '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>第三方单号</dt>
|
||||
<dd>{{ pickup.third_party_order_no || '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>店铺名称</dt>
|
||||
<dd>{{ pickup.shop_name || '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>操作人</dt>
|
||||
<dd>{{ pickup.operator || '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>下单金额</dt>
|
||||
<dd>
|
||||
{{
|
||||
pickup.order_amount_cent > 0 ? formatCentWithSymbol(pickup.order_amount_cent) : '-'
|
||||
}}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>押金金额</dt>
|
||||
<dd>
|
||||
{{
|
||||
pickup.deposit_amount_cent > 0
|
||||
? formatCentWithSymbol(pickup.deposit_amount_cent)
|
||||
: '-'
|
||||
}}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>管理员ID</dt>
|
||||
<dd>{{ pickup.admin_id }}</dd>
|
||||
|
||||
@@ -118,3 +118,27 @@
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.amount-group-card {
|
||||
background: var(--el-fill-color-light, #f8fafc);
|
||||
border: 1px solid var(--el-border-color-lighter, #e2e8f0);
|
||||
border-radius: 8px;
|
||||
padding: 12px 14px 6px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.amount-group-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #334155;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.amount-item :deep(.el-form-item__label) {
|
||||
font-size: 12px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.create-pickup-form :deep(.el-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { EditPen, Plus, Refresh, Search } from '@element-plus/icons-vue'
|
||||
import { EditPen, Plus, Refresh, Search, Setting, Shop } from '@element-plus/icons-vue'
|
||||
import {
|
||||
cancelAdminPickup,
|
||||
confirmAdminPickupOfflineSettlement,
|
||||
@@ -18,14 +18,20 @@ import {
|
||||
type PickupAccountSource,
|
||||
type PickupOfflineSettlementStatus,
|
||||
} from '@/features/admin/api/adminPickup'
|
||||
import PickupShopDialog from '@/features/admin/components/PickupShopDialog.vue'
|
||||
import { useAdminSessionStore } from '@/stores/adminSession'
|
||||
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 adminSession = useAdminSessionStore()
|
||||
const currentAdminName = computed(() => adminSession.nickname || adminSession.username || '')
|
||||
|
||||
const loading = ref(false)
|
||||
const items = ref<AdminPickup[]>([])
|
||||
const total = ref(0)
|
||||
const filterTimeRange = ref<[string, string] | null>(null)
|
||||
const filters = reactive({
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
@@ -34,12 +40,15 @@ const filters = reactive({
|
||||
shop_name: '',
|
||||
account_source: '' as PickupAccountSource | '',
|
||||
offline_settlement_status: '' as PickupOfflineSettlementStatus | '',
|
||||
start_time: '',
|
||||
end_time: '',
|
||||
})
|
||||
|
||||
const createDialogVisible = ref(false)
|
||||
const completeDialogVisible = ref(false)
|
||||
const profitDialogVisible = ref(false)
|
||||
const financialAdjustmentDialogVisible = ref(false)
|
||||
const shopMgrVisible = ref(false)
|
||||
const activePickupId = ref(0)
|
||||
const activePickup = ref<AdminPickup | null>(null)
|
||||
const profitSaving = ref(false)
|
||||
@@ -47,8 +56,12 @@ const financialAdjustmentSaving = ref(false)
|
||||
|
||||
const createForm = reactive({
|
||||
listing_id: null as number | null,
|
||||
operator: '',
|
||||
platform: '',
|
||||
third_party_order_no: '',
|
||||
shop_name: '',
|
||||
order_amount: 0,
|
||||
deposit_amount: 0,
|
||||
profit_amount: 0,
|
||||
remark: '',
|
||||
})
|
||||
@@ -103,12 +116,26 @@ function handleSearch() {
|
||||
loadList()
|
||||
}
|
||||
|
||||
function handleTimeRangeChange(val: [string, string] | null) {
|
||||
if (val && val.length === 2) {
|
||||
filters.start_time = val[0]
|
||||
filters.end_time = val[1]
|
||||
} else {
|
||||
filters.start_time = ''
|
||||
filters.end_time = ''
|
||||
}
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
filters.keyword = ''
|
||||
filters.status = ''
|
||||
filters.shop_name = ''
|
||||
filters.account_source = ''
|
||||
filters.offline_settlement_status = ''
|
||||
filters.start_time = ''
|
||||
filters.end_time = ''
|
||||
filterTimeRange.value = null
|
||||
filters.page = 1
|
||||
loadList()
|
||||
}
|
||||
@@ -124,8 +151,12 @@ function handleSizeChange(s: number) {
|
||||
|
||||
function openCreateDialog() {
|
||||
createForm.listing_id = null
|
||||
createForm.operator = currentAdminName.value || ''
|
||||
createForm.platform = ''
|
||||
createForm.third_party_order_no = ''
|
||||
createForm.shop_name = ''
|
||||
createForm.order_amount = 0
|
||||
createForm.deposit_amount = 0
|
||||
createForm.profit_amount = 0
|
||||
createForm.remark = ''
|
||||
listingOptions.value = []
|
||||
@@ -176,6 +207,14 @@ async function handleCreate() {
|
||||
ElMessage.warning('请选择要提号的账号')
|
||||
return
|
||||
}
|
||||
if (createForm.order_amount < 0) {
|
||||
ElMessage.warning('下单金额不能为负数')
|
||||
return
|
||||
}
|
||||
if (createForm.deposit_amount < 0) {
|
||||
ElMessage.warning('押金金额不能为负数')
|
||||
return
|
||||
}
|
||||
if (createForm.profit_amount < 0) {
|
||||
ElMessage.warning('线下利润不能为负数')
|
||||
return
|
||||
@@ -184,10 +223,14 @@ async function handleCreate() {
|
||||
try {
|
||||
await createAdminPickup({
|
||||
listing_id: createForm.listing_id,
|
||||
platform: createForm.platform,
|
||||
shop_name: createForm.shop_name,
|
||||
operator: createForm.operator.trim(),
|
||||
platform: createForm.platform.trim(),
|
||||
third_party_order_no: createForm.third_party_order_no.trim(),
|
||||
shop_name: createForm.shop_name.trim(),
|
||||
order_amount_cent: yuanToCent(createForm.order_amount),
|
||||
deposit_amount_cent: yuanToCent(createForm.deposit_amount),
|
||||
profit_amount_cent: yuanToCent(createForm.profit_amount),
|
||||
remark: createForm.remark,
|
||||
remark: createForm.remark.trim(),
|
||||
})
|
||||
ElMessage.success('提号订单已创建')
|
||||
createDialogVisible.value = false
|
||||
@@ -449,6 +492,7 @@ function listingOptionLabel(item: AvailableListing) {
|
||||
<p>管理员线下提号,站内上传结算到钱包,平台代管通过线下结算确认打款。</p>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-button :icon="Shop" @click="shopMgrVisible = true">店铺管理</el-button>
|
||||
<el-button type="primary" :icon="Plus" @click="openCreateDialog">创建提号</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -458,7 +502,7 @@ function listingOptionLabel(item: AvailableListing) {
|
||||
<el-form-item label="关键词" class="pickup-keyword-filter">
|
||||
<el-input
|
||||
v-model="filters.keyword"
|
||||
placeholder="提号编号 / 上架编号 / 账号标题 / 手机号"
|
||||
placeholder="提号编号 / 上架编号 / 账号标题 / 手机号 / 第三方单号 / 操作人"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
@clear="handleSearch"
|
||||
@@ -504,6 +548,19 @@ function listingOptionLabel(item: AvailableListing) {
|
||||
<el-option label="已确认" value="settled" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间">
|
||||
<el-date-picker
|
||||
v-model="filterTimeRange"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
|
||||
style="width: 350px"
|
||||
@change="handleTimeRangeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="pickup-filter-actions">
|
||||
<el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="resetFilters">重置</el-button>
|
||||
@@ -526,6 +583,9 @@ function listingOptionLabel(item: AvailableListing) {
|
||||
<el-table-column label="卖家" width="140">
|
||||
<template #default="{ row }">{{ row.owner_phone }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operator" label="操作人" width="105">
|
||||
<template #default="{ row }">{{ row.operator || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="账号来源" width="180">
|
||||
<template #default="{ row }">
|
||||
<div class="source-cell">
|
||||
@@ -539,9 +599,34 @@ function listingOptionLabel(item: AvailableListing) {
|
||||
<el-table-column label="交易渠道" width="110">
|
||||
<template #default="{ row }">{{ row.platform || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="第三方单号" min-width="150" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.third_party_order_no">{{ row.third_party_order_no }}</span>
|
||||
<span v-else class="text-muted">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="店铺名称" min-width="130">
|
||||
<template #default="{ row }">{{ row.shop_name || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="下单金额" width="115">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.order_amount_cent > 0">{{
|
||||
formatCentWithSymbol(row.order_amount_cent)
|
||||
}}</span>
|
||||
<span v-else class="text-muted">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="押金金额" width="115">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.deposit_amount_cent > 0">{{
|
||||
formatCentWithSymbol(row.deposit_amount_cent)
|
||||
}}</span>
|
||||
<span v-else class="text-muted">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="线下利润" width="120">
|
||||
<template #default="{ row }">{{ formatCentWithSymbol(effectiveProfitCent(row)) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结算金额" width="130">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.status === 'completed'">{{
|
||||
@@ -550,9 +635,6 @@ function listingOptionLabel(item: AvailableListing) {
|
||||
<span v-else class="text-muted">待结算</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="线下利润" width="120">
|
||||
<template #default="{ row }">{{ formatCentWithSymbol(effectiveProfitCent(row)) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusType(row.status)" effect="light">
|
||||
@@ -642,20 +724,32 @@ function listingOptionLabel(item: AvailableListing) {
|
||||
<el-dialog
|
||||
v-model="createDialogVisible"
|
||||
title="创建线下提号"
|
||||
width="min(640px, calc(100vw - 32px))"
|
||||
width="min(720px, calc(100vw - 32px))"
|
||||
>
|
||||
<el-form label-width="110px">
|
||||
<el-form-item label="账号来源">
|
||||
<el-radio-group
|
||||
v-model="listingSourceType"
|
||||
size="small"
|
||||
@change="handleListingSourceChange"
|
||||
>
|
||||
<el-radio-button value="">全部</el-radio-button>
|
||||
<el-radio-button value="external">外部上传 · 平台代管</el-radio-button>
|
||||
<el-radio-button value="internal">站内上传</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form label-width="95px" class="create-pickup-form">
|
||||
<!-- 第 1 行:账号来源 + 操作人(双列网格) -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :sm="14">
|
||||
<el-form-item label="账号来源">
|
||||
<el-radio-group
|
||||
v-model="listingSourceType"
|
||||
size="small"
|
||||
@change="handleListingSourceChange"
|
||||
>
|
||||
<el-radio-button value="">全部</el-radio-button>
|
||||
<el-radio-button value="external">外部上传 · 平台代管</el-radio-button>
|
||||
<el-radio-button value="internal">站内上传</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="10">
|
||||
<el-form-item label="操作人">
|
||||
<el-input v-model="createForm.operator" placeholder="经办人/客服姓名" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第 2 行:选择账号(全宽) -->
|
||||
<el-form-item label="选择账号" required>
|
||||
<el-select
|
||||
v-model="createForm.listing_id"
|
||||
@@ -721,44 +815,105 @@ function listingOptionLabel(item: AvailableListing) {
|
||||
<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>
|
||||
|
||||
<!-- 第 3 行:第三方订单号 + 交易渠道(双列网格) -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :sm="12">
|
||||
<el-form-item label="第三方单号">
|
||||
<el-input
|
||||
v-model="createForm.third_party_order_no"
|
||||
placeholder="淘宝/拼多多/外部订单号"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12">
|
||||
<el-form-item label="交易渠道">
|
||||
<el-input
|
||||
v-model="createForm.platform"
|
||||
placeholder="如 淘宝 / 微信 / QQ / 闲鱼"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第 4 行:店铺名称(全宽,带快捷管理) -->
|
||||
<el-form-item label="店铺名称">
|
||||
<el-select
|
||||
v-model="createForm.shop_name"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
clearable
|
||||
:loading="shopOptionsLoading"
|
||||
placeholder="选择历史店铺或输入新店铺"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option v-for="name in shopOptions" :key="name" :label="name" :value="name" />
|
||||
</el-select>
|
||||
<div class="form-hint">首次使用的店铺可直接输入名称创建</div>
|
||||
<div style="display: flex; gap: 8px; width: 100%">
|
||||
<el-select
|
||||
v-model="createForm.shop_name"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
clearable
|
||||
:loading="shopOptionsLoading"
|
||||
placeholder="选择配置店铺或输入新店铺"
|
||||
style="flex: 1"
|
||||
>
|
||||
<el-option v-for="name in shopOptions" :key="name" :label="name" :value="name" />
|
||||
</el-select>
|
||||
<el-button :icon="Setting" plain @click="shopMgrVisible = true">店铺管理</el-button>
|
||||
</div>
|
||||
<div class="form-hint">首次使用的店铺可直接输入名称创建,也可通过「店铺管理」预设</div>
|
||||
</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="备注">
|
||||
|
||||
<!-- 第 5 行:金额信息(三列并排卡片) -->
|
||||
<div class="amount-group-card">
|
||||
<div class="amount-group-title">金额核算</div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :sm="8">
|
||||
<el-form-item label="下单金额(元)" class="amount-item">
|
||||
<el-input-number
|
||||
v-model="createForm.order_amount"
|
||||
:min="0"
|
||||
:step="1"
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8">
|
||||
<el-form-item label="押金金额(元)" class="amount-item">
|
||||
<el-input-number
|
||||
v-model="createForm.deposit_amount"
|
||||
:min="0"
|
||||
:step="1"
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8">
|
||||
<el-form-item label="线下利润(元)" class="amount-item">
|
||||
<el-input-number
|
||||
v-model="createForm.profit_amount"
|
||||
:min="0"
|
||||
:step="1"
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="form-hint" style="margin-top: -6px; margin-bottom: 4px">
|
||||
电商平台实际成交利润,财务统计按该金额汇总
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第 6 行:备注说明(全宽) -->
|
||||
<el-form-item label="备注" style="margin-top: 14px">
|
||||
<el-input
|
||||
v-model="createForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
:rows="2"
|
||||
placeholder="线下交易说明(可选)"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-alert type="info" :closable="false">
|
||||
<el-alert type="info" :closable="false" show-icon>
|
||||
创建后账号将被锁定为"提号中",卖家收到通知;不会立即结算,需手动完成结算。
|
||||
</el-alert>
|
||||
</el-form>
|
||||
@@ -768,6 +923,9 @@ function listingOptionLabel(item: AvailableListing) {
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 店铺管理弹窗 -->
|
||||
<PickupShopDialog v-model="shopMgrVisible" @updated="loadShopOptions" />
|
||||
|
||||
<!-- 完成提号 -->
|
||||
<el-dialog v-model="completeDialogVisible" title="完成提号" width="480px">
|
||||
<el-form label-width="110px">
|
||||
|
||||
Reference in New Issue
Block a user