feat: manage group buy order readiness

This commit is contained in:
王鹏
2026-07-07 17:45:31 +08:00
parent 09217c4821
commit 4658b35aaf
5 changed files with 161 additions and 4 deletions

View File

@@ -1,5 +1,9 @@
import { describe, expect, it } from 'vitest'
import { filterPendingDeliveryGroupBuyOrders, groupBuyOrderDispatchLabel } from './groupBuyApi'
import {
buildGroupBuyOrderActionPath,
filterPendingDeliveryGroupBuyOrders,
groupBuyOrderDispatchLabel
} from './groupBuyApi'
import type { GroupBuyOrder } from '../types'
describe('group buy dispatch helpers', () => {
@@ -21,6 +25,11 @@ describe('group buy dispatch helpers', () => {
amountCent: 5980
}))).toBe('GB00000001 Today eggs x2 ¥59.80')
})
it('builds admin action paths for group buy orders', () => {
expect(buildGroupBuyOrderActionPath(1, 'confirm')).toBe('/api/admin/group-buy-orders/1/confirm')
expect(buildGroupBuyOrderActionPath(1, 'ready')).toBe('/api/admin/group-buy-orders/1/ready')
})
})
function groupBuyOrder(overrides: Partial<GroupBuyOrder>): GroupBuyOrder {

View File

@@ -17,6 +17,18 @@ export function groupBuyOrderDispatchLabel(order: GroupBuyOrder) {
return `${order.orderNo} ${order.groupBuyTitle} x${order.quantity} ¥${(order.amountCent / 100).toFixed(2)}`
}
export function buildGroupBuyOrderActionPath(id: number, action: 'confirm' | 'ready') {
return `/api/admin/group-buy-orders/${id}/${action}`
}
export function confirmGroupBuyOrder(id: number) {
return apiPut<GroupBuyOrder>(buildGroupBuyOrderActionPath(id, 'confirm'))
}
export function markGroupBuyOrderReady(id: number) {
return apiPut<GroupBuyOrder>(buildGroupBuyOrderActionPath(id, 'ready'))
}
export function createGroupBuy(data: Partial<GroupBuy>) {
return apiPost<GroupBuy>('/api/admin/group-buys', data)
}

View File

@@ -1,20 +1,27 @@
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { Check, Close, Plus, Refresh, VideoPlay } from '@element-plus/icons-vue'
import { Check, Close, Plus, Refresh, Tickets, VideoPlay } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import {
closeGroupBuy,
completeGroupBuy,
confirmGroupBuyOrder,
createGroupBuy,
listGroupBuyOrders,
listGroupBuys,
markGroupBuyOrderReady,
startGroupBuy
} from '../api/groupBuyApi'
import type { GroupBuy } from '../types'
import type { GroupBuy, GroupBuyOrder } from '../types'
import { statusText, yuan } from '../utils/format'
const loading = ref(false)
const orderLoading = ref(false)
const rows = ref<GroupBuy[]>([])
const orders = ref<GroupBuyOrder[]>([])
const activeGroupBuy = ref<GroupBuy | null>(null)
const dialog = ref(false)
const orderDialog = ref(false)
const form = reactive({
title: '',
coverUrl: '',
@@ -37,6 +44,24 @@ async function load() {
}
}
async function openOrders(row: GroupBuy) {
activeGroupBuy.value = row
orderDialog.value = true
await loadOrders(row.id)
}
async function loadOrders(groupBuyId = activeGroupBuy.value?.id) {
if (!groupBuyId) return
orderLoading.value = true
try {
orders.value = await listGroupBuyOrders(groupBuyId)
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '加载失败')
} finally {
orderLoading.value = false
}
}
async function save() {
try {
await createGroupBuy(form)
@@ -58,6 +83,17 @@ async function run(action: () => Promise<GroupBuy>) {
}
}
async function runOrder(action: () => Promise<GroupBuyOrder>) {
try {
await action()
ElMessage.success('操作成功')
await loadOrders()
await load()
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '操作失败')
}
}
onMounted(load)
</script>
@@ -79,18 +115,46 @@ onMounted(load)
<template #default="{ row }">{{ statusText(row.status) }}</template>
</el-table-column>
<el-table-column prop="endTime" label="截单时间" min-width="170" />
<el-table-column label="操作" width="260" fixed="right">
<el-table-column label="操作" width="330" fixed="right">
<template #default="{ row }">
<div class="table-action">
<el-button size="small" :icon="VideoPlay" @click="run(() => startGroupBuy(row.id))">开始</el-button>
<el-button size="small" :icon="Close" @click="run(() => closeGroupBuy(row.id))">截单</el-button>
<el-button size="small" :icon="Check" @click="run(() => completeGroupBuy(row.id))">完成</el-button>
<el-button size="small" :icon="Tickets" @click="openOrders(row)">订单</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog v-model="orderDialog" :title="activeGroupBuy ? `${activeGroupBuy.title} 订单` : '团购订单'" width="760px">
<div class="toolbar">
<el-button :icon="Refresh" :loading="orderLoading" @click="loadOrders()">刷新</el-button>
</div>
<el-table :data="orders" border v-loading="orderLoading">
<el-table-column prop="orderNo" label="订单号" min-width="140" />
<el-table-column prop="quantity" label="数量" width="80" />
<el-table-column label="金额" width="110">
<template #default="{ row }">{{ yuan(row.amountCent) }}</template>
</el-table-column>
<el-table-column label="配送" width="110">
<template #default="{ row }">{{ row.deliveryMethod === 'SELF_PICKUP' ? '自提' : '配送' }}</template>
</el-table-column>
<el-table-column label="状态" width="130">
<template #default="{ row }">{{ statusText(row.status) }}</template>
</el-table-column>
<el-table-column label="操作" width="190" fixed="right">
<template #default="{ row }">
<div class="table-action">
<el-button v-if="row.status === 'PENDING_CONFIRM'" size="small" :icon="Check" @click="runOrder(() => confirmGroupBuyOrder(row.id))">确认</el-button>
<el-button v-if="row.status === 'CONFIRMED'" size="small" type="primary" :icon="Check" @click="runOrder(() => markGroupBuyOrderReady(row.id))">备货完成</el-button>
</div>
</template>
</el-table-column>
</el-table>
</el-dialog>
<el-dialog v-model="dialog" title="新建团购" width="560px">
<el-form label-width="90px">
<el-form-item label="标题"><el-input v-model="form.title" /></el-form-item>