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:
yml2213
2026-10-09 12:46:15 +08:00
parent 6b74e07e70
commit 9d9adfbd5e
16 changed files with 1571 additions and 75 deletions
@@ -19,12 +19,16 @@ export interface AdminPickup {
owner_id: number
owner_phone: string
admin_id: number
operator: string
platform: string
third_party_order_no: string
shop_name: string
account_source: PickupAccountSource
source_channel: string
settlement_mode: PickupSettlementMode
listing_price_cent: number
order_amount_cent: number
deposit_amount_cent: number
owner_price_cent: number
website_profit_cent: number
profit_amount_cent: number
@@ -108,8 +112,12 @@ export interface AvailableListing {
export interface AdminPickupCreateRequest {
listing_id: number
operator?: string
platform?: string
third_party_order_no?: string
shop_name?: string
order_amount_cent?: number
deposit_amount_cent?: number
profit_amount_cent?: number
remark?: string
}
@@ -147,6 +155,43 @@ export interface AdminPickupListQuery {
shop_name?: string
account_source?: PickupAccountSource | ''
offline_settlement_status?: PickupOfflineSettlementStatus | ''
operator?: string
third_party_order_no?: string
start_time?: string
end_time?: string
}
export interface PickupShop {
id: number
name: string
platform: string
status: 'active' | 'disabled'
sort_order: number
remark: string
created_at: string
updated_at: string
}
export interface PickupShopCreateRequest {
name: string
platform?: string
status?: 'active' | 'disabled'
sort_order?: number
remark?: string
}
export interface PickupShopUpdateRequest {
name: string
platform?: string
status: 'active' | 'disabled'
sort_order?: number
remark?: string
}
export interface PickupShopQuery {
keyword?: string
platform?: string
status?: string
}
function cleanParams(query: object) {
@@ -270,3 +315,27 @@ export async function cancelAdminPickup(id: number, reason: string) {
)
return data.data
}
export async function fetchPickupShops(query: PickupShopQuery = {}) {
const { data } = await apiClient.get<ApiResponse<PickupShop[]>>('/admin/pickups/shops', {
params: cleanParams(query),
})
return Array.isArray(data.data) ? data.data : []
}
export async function createPickupShop(req: PickupShopCreateRequest) {
const { data } = await apiClient.post<ApiResponse<PickupShop>>('/admin/pickups/shops', req)
return data.data
}
export async function updatePickupShop(id: number, req: PickupShopUpdateRequest) {
const { data } = await apiClient.put<ApiResponse<PickupShop>>(`/admin/pickups/shops/${id}`, req)
return data.data
}
export async function deletePickupShop(id: number) {
const { data } = await apiClient.delete<ApiResponse<{ deleted: boolean }>>(
`/admin/pickups/shops/${id}`
)
return data.data
}
@@ -0,0 +1,401 @@
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Delete, Edit, Plus, Refresh, Search } from '@element-plus/icons-vue'
import {
createPickupShop,
deletePickupShop,
fetchPickupShops,
updatePickupShop,
type PickupShop,
type PickupShopCreateRequest,
type PickupShopUpdateRequest,
} from '@/features/admin/api/adminPickup'
import { formatDateTime } from '@/shared/utils/time'
const props = defineProps<{
modelValue: boolean
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'updated'): void
}>()
const loading = ref(false)
const shopList = ref<PickupShop[]>([])
const filters = reactive({
keyword: '',
platform: '',
status: '',
})
// 新建/编辑店铺弹窗状态
const formDialogVisible = ref(false)
const formSaving = ref(false)
const editingId = ref<number | null>(null)
const shopForm = reactive({
name: '',
platform: '',
sort_order: 0,
status: 'active' as 'active' | 'disabled',
remark: '',
})
const platformOptions = ['淘宝', '拼多多', '闲鱼', '微信', 'QQ', '抖音', '快手']
function handleClose() {
emit('update:modelValue', false)
}
async function loadShops() {
loading.value = true
try {
shopList.value = await fetchPickupShops({
keyword: filters.keyword.trim(),
platform: filters.platform,
status: filters.status,
})
} catch (err: unknown) {
ElMessage.error(getErrorMessage(err) || '加载店铺列表失败')
} finally {
loading.value = false
}
}
function handleSearch() {
loadShops()
}
function resetFilters() {
filters.keyword = ''
filters.platform = ''
filters.status = ''
loadShops()
}
function openCreateForm() {
editingId.value = null
shopForm.name = ''
shopForm.platform = ''
shopForm.sort_order = 0
shopForm.status = 'active'
shopForm.remark = ''
formDialogVisible.value = true
}
function openEditForm(row: PickupShop) {
editingId.value = row.id
shopForm.name = row.name
shopForm.platform = row.platform
shopForm.sort_order = row.sort_order
shopForm.status = row.status
shopForm.remark = row.remark
formDialogVisible.value = true
}
async function handleSaveShop() {
if (!shopForm.name.trim()) {
ElMessage.warning('请输入店铺名称')
return
}
formSaving.value = true
try {
if (editingId.value) {
const payload: PickupShopUpdateRequest = {
name: shopForm.name.trim(),
platform: shopForm.platform.trim(),
sort_order: Number(shopForm.sort_order) || 0,
status: shopForm.status,
remark: shopForm.remark.trim(),
}
await updatePickupShop(editingId.value, payload)
ElMessage.success('店铺更新成功')
} else {
const payload: PickupShopCreateRequest = {
name: shopForm.name.trim(),
platform: shopForm.platform.trim(),
sort_order: Number(shopForm.sort_order) || 0,
status: shopForm.status,
remark: shopForm.remark.trim(),
}
await createPickupShop(payload)
ElMessage.success('店铺创建成功')
}
formDialogVisible.value = false
await loadShops()
emit('updated')
} catch (err: unknown) {
ElMessage.error(getErrorMessage(err) || '保存店铺失败')
} finally {
formSaving.value = false
}
}
async function handleToggleStatus(row: PickupShop) {
const nextStatus = row.status === 'active' ? 'disabled' : 'active'
const actionText = nextStatus === 'active' ? '启用' : '禁用'
try {
await updatePickupShop(row.id, {
name: row.name,
platform: row.platform,
sort_order: row.sort_order,
status: nextStatus,
remark: row.remark,
})
ElMessage.success(`已${actionText}店铺「${row.name}」`)
row.status = nextStatus
emit('updated')
} catch (err: unknown) {
ElMessage.error(getErrorMessage(err) || `${actionText}失败`)
}
}
async function handleDeleteShop(row: PickupShop) {
try {
await ElMessageBox.confirm(
`确定要删除店铺「${row.name}」吗?历史提号中的记录不会被删除。`,
'确认删除店铺',
{
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning',
}
)
loading.value = true
try {
await deletePickupShop(row.id)
ElMessage.success('店铺已删除')
await loadShops()
emit('updated')
} catch (err: unknown) {
ElMessage.error(getErrorMessage(err) || '删除店铺失败')
} finally {
loading.value = false
}
} catch {
/* 用户放弃 */
}
}
function getErrorMessage(e: unknown): string {
if (e && typeof e === 'object') {
const response = (e as { response?: { data?: { message?: string } } }).response
if (response?.data?.message) return response.data.message
const message = (e as { message?: string }).message
if (message) return message
}
return ''
}
onMounted(() => {
loadShops()
})
</script>
<template>
<el-dialog
:model-value="props.modelValue"
title="店铺管理"
width="min(920px, calc(100vw - 32px))"
:before-close="handleClose"
append-to-body
>
<div class="shop-mgr-toolbar">
<el-form inline @submit.prevent>
<el-form-item label="店铺名称">
<el-input
v-model="filters.keyword"
placeholder="搜索名称或备注"
clearable
style="width: 170px"
@keyup.enter="handleSearch"
@clear="handleSearch"
/>
</el-form-item>
<el-form-item label="平台">
<el-select
v-model="filters.platform"
placeholder="全部平台"
clearable
style="width: 120px"
@change="handleSearch"
>
<el-option v-for="item in platformOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select
v-model="filters.status"
placeholder="全部状态"
clearable
style="width: 110px"
@change="handleSearch"
>
<el-option label="启用" value="active" />
<el-option label="禁用" value="disabled" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
<el-button @click="resetFilters">重置</el-button>
<el-button :icon="Refresh" @click="loadShops">刷新</el-button>
</el-form-item>
</el-form>
<div class="toolbar-right">
<el-button type="success" :icon="Plus" @click="openCreateForm">新增店铺</el-button>
</div>
</div>
<el-table v-loading="loading" :data="shopList" border stripe max-height="460">
<el-table-column prop="name" label="店铺名称" min-width="150">
<template #default="{ row }">
<strong style="color: var(--el-text-color-primary)">{{ row.name }}</strong>
</template>
</el-table-column>
<el-table-column prop="platform" label="平台/渠道" width="120">
<template #default="{ row }">
<el-tag v-if="row.platform" size="small" effect="plain">{{ row.platform }}</el-tag>
<span v-else class="text-muted">-</span>
</template>
</el-table-column>
<el-table-column prop="sort_order" label="排序权重" width="100" align="center" />
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.status === 'active' ? 'success' : 'info'" size="small">
{{ row.status === 'active' ? '启用中' : '已禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip>
<template #default="{ row }">{{ row.remark || '-' }}</template>
</el-table-column>
<el-table-column prop="updated_at" label="更新时间" width="160">
<template #default="{ row }">{{ formatDateTime(row.updated_at) }}</template>
</el-table-column>
<el-table-column label="操作" width="190" fixed="right" align="center">
<template #default="{ row }">
<div class="shop-actions">
<el-button size="small" :icon="Edit" @click="openEditForm(row)">编辑</el-button>
<el-button
size="small"
:type="row.status === 'active' ? 'warning' : 'success'"
plain
@click="handleToggleStatus(row)"
>
{{ row.status === 'active' ? '禁用' : '启用' }}
</el-button>
<el-button
size="small"
type="danger"
plain
:icon="Delete"
@click="handleDeleteShop(row)"
>
删除
</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 新建/编辑店铺弹窗 -->
<el-dialog
v-model="formDialogVisible"
:title="editingId ? '编辑店铺' : '新增店铺'"
width="460px"
append-to-body
>
<el-form label-width="90px">
<el-form-item label="店铺名称" required>
<el-input
v-model="shopForm.name"
placeholder="例如:糖浆、淘宝王大锤专营店"
maxlength="100"
show-word-limit
/>
</el-form-item>
<el-form-item label="所属平台">
<el-select
v-model="shopForm.platform"
filterable
allow-create
default-first-option
clearable
placeholder="选择或输入平台(如 淘宝/拼多多)"
style="width: 100%"
>
<el-option v-for="item in platformOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="排序权重">
<el-input-number
v-model="shopForm.sort_order"
:min="0"
:max="9999"
controls-position="right"
style="width: 100%"
/>
<div class="form-hint">权重越大的店铺在下拉选择时排在越前面</div>
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="shopForm.status">
<el-radio value="active">启用</el-radio>
<el-radio value="disabled">禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注说明">
<el-input
v-model="shopForm.remark"
type="textarea"
:rows="2"
placeholder="填写店铺说明或客服负责人(可选)"
maxlength="255"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="formDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="formSaving" @click="handleSaveShop">保存</el-button>
</template>
</el-dialog>
<template #footer>
<el-button @click="handleClose">关闭</el-button>
</template>
</el-dialog>
</template>
<style scoped>
.shop-mgr-toolbar {
display: flex;
justify-content: space-between;
align-items: flex-start;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 12px;
}
.toolbar-right {
display: flex;
align-items: center;
}
.shop-actions {
display: flex;
gap: 6px;
justify-content: center;
}
.text-muted {
color: var(--el-text-color-secondary);
}
.form-hint {
font-size: 12px;
color: var(--el-text-color-secondary);
line-height: 1.4;
margin-top: 4px;
}
</style>
@@ -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">