From 6450b31129ba048010db34bd38bac23c6f2b93bc Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sun, 19 Jul 2026 16:08:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=92=9E=E8=BD=A6=E8=AE=A2=E5=8D=95=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=8C=E6=8E=A5=E5=BE=85=E4=B8=AD=E3=80=8D=E7=8A=B6?= =?UTF-8?q?=E6=80=81=EF=BC=8C=E6=94=AF=E6=8C=81=E5=90=8E=E5=8F=B0=E6=8E=A5?= =?UTF-8?q?=E5=BE=85=E6=A0=87=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 已支付订单可标记为接待中,避免重复跟进;完成/取消覆盖接待中状态,前后台同步展示与筛选。 --- backend/internal/model/mohong.go | 1 + backend/internal/modules/mohong/dto.go | 4 ++ backend/internal/modules/mohong/errors.go | 1 + backend/internal/modules/mohong/handler.go | 17 +++++++ backend/internal/modules/mohong/order_repo.go | 34 +++++++++++-- backend/internal/modules/mohong/service.go | 7 +++ backend/internal/router/router.go | 1 + .../admin/views/AdminMohongOrdersView.vue | 50 +++++++++++++------ frontend/src/features/mohong/api/mohong.ts | 9 ++++ .../mohong/views/MohongOrdersView.vue | 5 ++ .../orders/views/MobileOrdersView.vue | 5 ++ .../src/features/orders/views/OrdersView.vue | 6 +++ 12 files changed, 123 insertions(+), 17 deletions(-) diff --git a/backend/internal/model/mohong.go b/backend/internal/model/mohong.go index 5d2f6fd..2efd56e 100644 --- a/backend/internal/model/mohong.go +++ b/backend/internal/model/mohong.go @@ -92,6 +92,7 @@ const ( MohongOrderStatusPendingPayment = "pending_payment" MohongOrderStatusPaid = "paid" + MohongOrderStatusReceiving = "receiving" // 接待中:已有人接待买家 MohongOrderStatusCompleted = "completed" MohongOrderStatusCancelled = "cancelled" MohongOrderStatusRefunded = "refunded" diff --git a/backend/internal/modules/mohong/dto.go b/backend/internal/modules/mohong/dto.go index fe3a93e..7f0b617 100644 --- a/backend/internal/modules/mohong/dto.go +++ b/backend/internal/modules/mohong/dto.go @@ -151,6 +151,10 @@ type AdminCompleteOrderRequest struct { Remark string `json:"remark"` } +type AdminReceiveOrderRequest struct { + Remark string `json:"remark"` +} + type AdminCancelOrderRequest struct { Reason string `json:"reason"` } diff --git a/backend/internal/modules/mohong/errors.go b/backend/internal/modules/mohong/errors.go index 1da9bf5..dd2a97d 100644 --- a/backend/internal/modules/mohong/errors.go +++ b/backend/internal/modules/mohong/errors.go @@ -11,6 +11,7 @@ var ( ErrOrderNotFound = errors.New("order not found") ErrOrderCannotPay = errors.New("order cannot pay") ErrOrderCannotCancel = errors.New("order cannot cancel") + ErrOrderCannotReceive = errors.New("order cannot receive") ErrOrderCannotComplete = errors.New("order cannot complete") ErrUnauthorized = errors.New("unauthorized") ) diff --git a/backend/internal/modules/mohong/handler.go b/backend/internal/modules/mohong/handler.go index f38b0bd..9721917 100644 --- a/backend/internal/modules/mohong/handler.go +++ b/backend/internal/modules/mohong/handler.go @@ -292,6 +292,21 @@ func (h *Handler) AdminGetOrder(c *gin.Context) { response.OK(c, item) } +func (h *Handler) AdminReceiveOrder(c *gin.Context) { + id, ok := parseID(c) + if !ok { + return + } + var req AdminReceiveOrderRequest + _ = c.ShouldBindJSON(&req) + item, err := h.service.AdminReceiveOrder(c.Request.Context(), id, req.Remark) + if err != nil { + writeError(c, err) + return + } + response.OK(c, item) +} + func (h *Handler) AdminCompleteOrder(c *gin.Context) { id, ok := parseID(c) if !ok { @@ -357,6 +372,8 @@ func writeError(c *gin.Context, err error) { response.Error(c, 409, "order_cannot_pay", "订单无法支付") case errors.Is(err, ErrOrderCannotCancel): response.Error(c, 409, "order_cannot_cancel", "订单无法取消") + case errors.Is(err, ErrOrderCannotReceive): + response.Error(c, 409, "order_cannot_receive", "订单无法标记为接待中") case errors.Is(err, ErrOrderCannotComplete): response.Error(c, 409, "order_cannot_complete", "订单无法完成") case errors.Is(err, ErrInvalidRequest): diff --git a/backend/internal/modules/mohong/order_repo.go b/backend/internal/modules/mohong/order_repo.go index 8dcc1ce..d286bf9 100644 --- a/backend/internal/modules/mohong/order_repo.go +++ b/backend/internal/modules/mohong/order_repo.go @@ -266,13 +266,15 @@ func (r *Repository) AdminCancelOrder(ctx context.Context, orderID uint64, reaso } return err } - if order.Status != model.MohongOrderStatusPendingPayment && order.Status != model.MohongOrderStatusPaid { + if order.Status != model.MohongOrderStatusPendingPayment && + order.Status != model.MohongOrderStatusPaid && + order.Status != model.MohongOrderStatusReceiving { return ErrOrderCannotCancel } if order.Status == model.MohongOrderStatusPendingPayment { return r.cancelPendingOrderTx(tx, &order, firstNonEmpty(reason, "后台取消")) } - // 已支付:仅标记取消(退款后续可接支付退款)。 + // 已支付/接待中:仅标记取消(退款后续可接支付退款)。 now := time.Now() order.Status = model.MohongOrderStatusCancelled order.CancelledAt = &now @@ -285,6 +287,31 @@ func (r *Repository) AdminCancelOrder(ctx context.Context, orderID uint64, reaso return r.AdminFindOrder(ctx, orderID) } +// AdminReceiveOrder 标记订单为接待中(已支付 → 接待中)。 +func (r *Repository) AdminReceiveOrder(ctx context.Context, orderID uint64, remark string) (*OrderDTO, error) { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var order model.MohongOrder + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return ErrOrderNotFound + } + return err + } + if order.Status != model.MohongOrderStatusPaid { + return ErrOrderCannotReceive + } + order.Status = model.MohongOrderStatusReceiving + if strings.TrimSpace(remark) != "" { + order.AdminRemark = strings.TrimSpace(remark) + } + return tx.Save(&order).Error + }) + if err != nil { + return nil, err + } + return r.AdminFindOrder(ctx, orderID) +} + func (r *Repository) AdminCompleteOrder(ctx context.Context, orderID uint64, remark string) (*OrderDTO, error) { err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var order model.MohongOrder @@ -294,7 +321,7 @@ func (r *Repository) AdminCompleteOrder(ctx context.Context, orderID uint64, rem } return err } - if order.Status != model.MohongOrderStatusPaid { + if order.Status != model.MohongOrderStatusPaid && order.Status != model.MohongOrderStatusReceiving { return ErrOrderCannotComplete } now := time.Now() @@ -319,6 +346,7 @@ func (r *Repository) ConfirmPaidFromChannelTx(tx *gorm.DB, orderID uint64) (uint return 0, err } if order.Status == model.MohongOrderStatusPaid || + order.Status == model.MohongOrderStatusReceiving || order.Status == model.MohongOrderStatusCompleted { return 0, nil } diff --git a/backend/internal/modules/mohong/service.go b/backend/internal/modules/mohong/service.go index 9d33a43..4d47d80 100644 --- a/backend/internal/modules/mohong/service.go +++ b/backend/internal/modules/mohong/service.go @@ -136,6 +136,13 @@ func (s *Service) AdminFindOrder(ctx context.Context, orderID uint64) (*OrderDTO return s.repo.AdminFindOrder(ctx, orderID) } +func (s *Service) AdminReceiveOrder(ctx context.Context, orderID uint64, remark string) (*OrderDTO, error) { + if s.repo == nil { + return nil, ErrDependencyUnavailable + } + return s.repo.AdminReceiveOrder(ctx, orderID, remark) +} + func (s *Service) AdminCompleteOrder(ctx context.Context, orderID uint64, remark string) (*OrderDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go index d25aeb9..92dd97a 100644 --- a/backend/internal/router/router.go +++ b/backend/internal/router/router.go @@ -617,6 +617,7 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine { adminRoutes.DELETE(prefix+"/products/:id", requirePerm("mohong:product_manage"), mohongHandler.AdminDeleteProduct) adminRoutes.GET(prefix+"/orders", requirePerm("mohong:order_view"), mohongHandler.AdminListOrders) adminRoutes.GET(prefix+"/orders/:id", requirePerm("mohong:order_view"), mohongHandler.AdminGetOrder) + adminRoutes.POST(prefix+"/orders/:id/receive", requirePerm("mohong:order_manage"), mohongHandler.AdminReceiveOrder) adminRoutes.POST(prefix+"/orders/:id/complete", requirePerm("mohong:order_manage"), mohongHandler.AdminCompleteOrder) adminRoutes.POST(prefix+"/orders/:id/cancel", requirePerm("mohong:order_manage"), mohongHandler.AdminCancelOrder) adminRoutes.GET(prefix+"/config", requirePerm("mohong:config"), mohongHandler.AdminGetConfig) diff --git a/frontend/src/features/admin/views/AdminMohongOrdersView.vue b/frontend/src/features/admin/views/AdminMohongOrdersView.vue index 80c0371..522b132 100644 --- a/frontend/src/features/admin/views/AdminMohongOrdersView.vue +++ b/frontend/src/features/admin/views/AdminMohongOrdersView.vue @@ -7,6 +7,7 @@ import { completeAdminMohongOrder, fetchAdminMohongOrders, mohongOrderStatusLabel, + receiveAdminMohongOrder, type MohongOrder, } from '@/features/mohong/api/mohong' import { formatCent } from '@/shared/utils/money' @@ -43,6 +44,19 @@ async function load() { } } +async function handleReceive(row: MohongOrder) { + try { + await ElMessageBox.confirm(`确认接待订单 ${row.order_no}?标记后状态变为「接待中」。`, '提示', { + type: 'info', + }) + await receiveAdminMohongOrder(row.id) + ElMessage.success('已标记为接待中') + await load() + } catch (error) { + if (error !== 'cancel') ElMessage.error(readError(error, '操作失败')) + } +} + async function handleComplete(row: MohongOrder) { try { await ElMessageBox.confirm(`确认将订单 ${row.order_no} 标记为已完成?`, '提示', { @@ -71,6 +85,20 @@ async function handleCancel(row: MohongOrder) { } } +function canReceive(row: MohongOrder) { + return row.status === 'paid' +} + +function canComplete(row: MohongOrder) { + return row.status === 'paid' || row.status === 'receiving' +} + +function canCancel(row: MohongOrder) { + return ( + row.status === 'pending_payment' || row.status === 'paid' || row.status === 'receiving' + ) +} + async function copyText(text: string) { try { await navigator.clipboard.writeText(text) @@ -86,7 +114,7 @@ async function copyText(text: string) {

撞车订单

-

查看订单、完成履约、复制用户订单信息

+

查看订单、接待买家、完成履约、复制用户订单信息

刷新
@@ -96,6 +124,7 @@ async function copyText(text: string) { + @@ -121,23 +150,16 @@ async function copyText(text: string) { - + diff --git a/frontend/src/features/mohong/api/mohong.ts b/frontend/src/features/mohong/api/mohong.ts index 8d799fe..30e9452 100644 --- a/frontend/src/features/mohong/api/mohong.ts +++ b/frontend/src/features/mohong/api/mohong.ts @@ -263,6 +263,14 @@ export async function fetchAdminMohongOrder(id: number | string) { return data.data } +export async function receiveAdminMohongOrder(id: number, remark?: string) { + const { data } = await apiClient.post>( + `/admin/crash/orders/${id}/receive`, + { remark: remark || '' } + ) + return data.data +} + export async function completeAdminMohongOrder(id: number, remark?: string) { const { data } = await apiClient.post>( `/admin/crash/orders/${id}/complete`, @@ -293,6 +301,7 @@ export function mohongOrderStatusLabel(status: string) { const map: Record = { pending_payment: '待支付', paid: '已支付', + receiving: '接待中', completed: '已完成', cancelled: '已取消', refunded: '已退款', diff --git a/frontend/src/features/mohong/views/MohongOrdersView.vue b/frontend/src/features/mohong/views/MohongOrdersView.vue index 2cf24f4..def0e7c 100644 --- a/frontend/src/features/mohong/views/MohongOrdersView.vue +++ b/frontend/src/features/mohong/views/MohongOrdersView.vue @@ -157,6 +157,11 @@ async function loadOrders() { color: #059669; } +.status-tag.receiving { + background: #fff7ed; + color: #d97706; +} + .status-tag.completed { background: #eff6ff; color: #2563eb; diff --git a/frontend/src/features/orders/views/MobileOrdersView.vue b/frontend/src/features/orders/views/MobileOrdersView.vue index 3fb29c4..927e16f 100644 --- a/frontend/src/features/orders/views/MobileOrdersView.vue +++ b/frontend/src/features/orders/views/MobileOrdersView.vue @@ -67,6 +67,7 @@ const statusTabs = [ { key: 'pending_handoff', label: '待交接' }, { key: 'renting', label: '使用中' }, { key: 'paid', label: '已支付' }, + { key: 'receiving', label: '接待中' }, { key: 'pending_checkout_confirm', label: '待结账' }, { key: 'completed', label: '已完成' }, { key: 'cancelled', label: '已取消' }, @@ -638,6 +639,10 @@ async function copyListingCode(order: Order) { color: #1477ff; background: rgba(20, 119, 255, 0.08); } +.badge-receiving { + color: #d97706; + background: rgba(217, 119, 6, 0.1); +} .badge-completed { color: #10b981; background: rgba(16, 185, 129, 0.08); diff --git a/frontend/src/features/orders/views/OrdersView.vue b/frontend/src/features/orders/views/OrdersView.vue index 57ee174..8f60691 100644 --- a/frontend/src/features/orders/views/OrdersView.vue +++ b/frontend/src/features/orders/views/OrdersView.vue @@ -51,6 +51,7 @@ const statusTabs = [ { key: 'pending_handoff', label: '待交接' }, { key: 'renting', label: '使用中' }, { key: 'paid', label: '已支付' }, + { key: 'receiving', label: '接待中' }, { key: 'pending_checkout_confirm', label: '待结账' }, { key: 'completed', label: '已完成' }, { key: 'cancelled', label: '已取消' }, @@ -844,6 +845,11 @@ function bizTypeLabel(bizType: BizType) { color: #059669; } +.crash-status.receiving { + background: #fff7ed; + color: #d97706; +} + .crash-status.completed { background: #eff6ff; color: #2563eb;