admin web 修改订单状态

This commit is contained in:
2026-07-01 22:00:10 +08:00
parent 139e37651b
commit 8a67d72de4
5 changed files with 70 additions and 20 deletions
+32 -17
View File
@@ -10,21 +10,11 @@ import {
Table, Table,
Tag, Tag,
Typography, Typography,
message,
} from 'antd'; } from 'antd';
import type { ColumnsType } from 'antd/es/table'; import type { ColumnsType } from 'antd/es/table';
import { request, type AdminOrderRow, type Paginated } from '../lib/api'; import { request, type AdminOrderRow, type Paginated } from '../lib/api';
import { ORDER_STATUS_LABELS } from '../lib/constants';
const STATUS_LABELS: Record<string, string> = {
PENDING_PAY: '待付款',
PENDING_SHIP: '待发货',
OUT_WAREHOUSE: '已出库',
SHIPPING: '配送中',
PENDING_RECEIVE: '待收货',
COMPLETED: '已完成',
CANCELLED: '已取消',
REFUNDING: '退款中',
REFUNDED: '已退款',
};
type OrderDetail = AdminOrderRow & { type OrderDetail = AdminOrderRow & {
receiverAddress?: string; receiverAddress?: string;
@@ -93,7 +83,7 @@ export default function OrdersPage() {
title: '状态', title: '状态',
dataIndex: 'status', dataIndex: 'status',
width: 100, width: 100,
render: (s) => <Tag>{STATUS_LABELS[s] || s}</Tag>, render: (s) => <Tag>{ORDER_STATUS_LABELS[s] || s}</Tag>,
}, },
{ {
title: '配送', title: '配送',
@@ -140,7 +130,7 @@ export default function OrdersPage() {
<Input placeholder="DK..." allowClear /> <Input placeholder="DK..." allowClear />
</Form.Item> </Form.Item>
<Form.Item name="status" label="状态"> <Form.Item name="status" label="状态">
<Select allowClear style={{ width: 120 }} options={Object.entries(STATUS_LABELS).map(([value, label]) => ({ value, label }))} /> <Select allowClear style={{ width: 120 }} options={Object.entries(ORDER_STATUS_LABELS).map(([value, label]) => ({ value, label }))} />
</Form.Item> </Form.Item>
<Form.Item name="receiverPhone" label="收货手机"> <Form.Item name="receiverPhone" label="收货手机">
<Input allowClear /> <Input allowClear />
@@ -171,12 +161,37 @@ export default function OrdersPage() {
}} }}
/> />
<Drawer title="订单详情" width={640} open={drawerOpen} onClose={() => setDrawerOpen(false)}> <Drawer
title="订单详情"
width={640}
open={drawerOpen}
onClose={() => setDrawerOpen(false)}
extra={detail && (
<Space>
<Typography.Text type="secondary"></Typography.Text>
<Select
value={detail.status}
style={{ width: 120 }}
options={Object.entries(ORDER_STATUS_LABELS).map(([value, label]) => ({ value, label }))}
onChange={async (status) => {
await request(`/admin/orders/${detail.id}/status`, {
method: 'PUT',
body: JSON.stringify({ status }),
});
message.success('状态已更新(调试)');
const res = await request<OrderDetail>(`/admin/orders/${detail.id}`);
setDetail(res);
void load();
}}
/>
</Space>
)}
>
{detail && ( {detail && (
<> <>
<Descriptions column={1} bordered size="small" title="基本信息"> <Descriptions column={1} bordered size="small" title="基本信息">
<Descriptions.Item label="订单号">{detail.orderNo}</Descriptions.Item> <Descriptions.Item label="订单号">{detail.orderNo}</Descriptions.Item>
<Descriptions.Item label="状态">{STATUS_LABELS[detail.status] || detail.status}</Descriptions.Item> <Descriptions.Item label="状态">{ORDER_STATUS_LABELS[detail.status] || detail.status}</Descriptions.Item>
<Descriptions.Item label="用户">{detail.user?.userNo} / {detail.user?.nickname}</Descriptions.Item> <Descriptions.Item label="用户">{detail.user?.userNo} / {detail.user?.nickname}</Descriptions.Item>
<Descriptions.Item label="商品额">¥{detail.productAmount}</Descriptions.Item> <Descriptions.Item label="商品额">¥{detail.productAmount}</Descriptions.Item>
<Descriptions.Item label="实付">¥{detail.payAmount}</Descriptions.Item> <Descriptions.Item label="实付">¥{detail.payAmount}</Descriptions.Item>
@@ -215,7 +230,7 @@ export default function OrdersPage() {
dataSource={detail.statusLogs} dataSource={detail.statusLogs}
columns={[ columns={[
{ title: '从', dataIndex: 'fromStatus', render: (v) => v || '—' }, { title: '从', dataIndex: 'fromStatus', render: (v) => v || '—' },
{ title: '到', dataIndex: 'toStatus', render: (v) => STATUS_LABELS[v] || v }, { title: '到', dataIndex: 'toStatus', render: (v) => ORDER_STATUS_LABELS[v] || v },
{ title: '时间', dataIndex: 'createdAt', render: (v) => new Date(v).toLocaleString('zh-CN') }, { title: '时间', dataIndex: 'createdAt', render: (v) => new Date(v).toLocaleString('zh-CN') },
]} ]}
/> />
@@ -1,6 +1,7 @@
import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common'; import { Body, Controller, Get, Param, Put, Query, UseGuards } from '@nestjs/common';
import { HqAuthGuard } from '../../common/guards/hq-auth.guard'; import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
import { AdminOrdersService } from './admin-orders.service'; import { AdminOrdersService } from './admin-orders.service';
import { UpdateOrderStatusDto } from './dto/admin-mutate.dto';
import { AdminOrdersQueryDto } from './dto/admin-query.dto'; import { AdminOrdersQueryDto } from './dto/admin-query.dto';
@Controller('admin/orders') @Controller('admin/orders')
@@ -17,4 +18,10 @@ export class AdminOrdersController {
detail(@Param('id') id: string) { detail(@Param('id') id: string) {
return this.ordersService.detail(BigInt(id)); return this.ordersService.detail(BigInt(id));
} }
/** preV1 调试:直接改订单状态,不走业务校验 */
@Put(':id/status')
updateStatus(@Param('id') id: string, @Body() dto: UpdateOrderStatusDto) {
return this.ordersService.updateStatusDebug(BigInt(id), dto.status);
}
} }
@@ -4,11 +4,15 @@ import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator'; import { serializeBigInt } from '../../common/decorators/current-user.decorator';
import { orderStatusLogWhere } from '../../common/event/event.helpers'; import { orderStatusLogWhere } from '../../common/event/event.helpers';
import { mapOrderCompat, mapStatusLogCompat } from '../../common/compat/v31-compat'; import { mapOrderCompat, mapStatusLogCompat } from '../../common/compat/v31-compat';
import { TradeService } from '../trade/trade.service';
import type { AdminOrdersQueryDto } from './dto/admin-query.dto'; import type { AdminOrdersQueryDto } from './dto/admin-query.dto';
@Injectable() @Injectable()
export class AdminOrdersService { export class AdminOrdersService {
constructor(private readonly prisma: PrismaService) {} constructor(
private readonly prisma: PrismaService,
private readonly tradeService: TradeService,
) {}
async list(query: AdminOrdersQueryDto) { async list(query: AdminOrdersQueryDto) {
const page = query.page ?? 1; const page = query.page ?? 1;
@@ -76,4 +80,11 @@ export class AdminOrdersService {
benefitCoupons: order.benefitCoupon ? [order.benefitCoupon] : [], benefitCoupons: order.benefitCoupon ? [order.benefitCoupon] : [],
})); }));
} }
async updateStatusDebug(id: bigint, status: string) {
const order = await this.prisma.order.findUnique({ where: { id } });
if (!order) throw new NotFoundException('订单不存在');
await this.tradeService.applyStatusTransition(id, order.status, status, 'HQ_DEBUG');
return this.detail(id);
}
} }
@@ -270,6 +270,22 @@ export class UpdateStoreMediaDto {
sortOrder?: number; sortOrder?: number;
} }
export class UpdateOrderStatusDto {
@IsString()
@IsIn([
'PENDING_PAY',
'PENDING_SHIP',
'OUT_WAREHOUSE',
'SHIPPING',
'PENDING_RECEIVE',
'COMPLETED',
'CANCELLED',
'REFUNDING',
'REFUNDED',
])
status: string;
}
export class UpdateDeliveryDto { export class UpdateDeliveryDto {
@IsOptional() @IsOptional()
@IsString() @IsString()
@@ -1,5 +1,6 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { IamModule } from '../iam/iam.module'; import { IamModule } from '../iam/iam.module';
import { TradeModule } from '../trade/trade.module';
import { AdminDashboardController } from './admin-dashboard.controller'; import { AdminDashboardController } from './admin-dashboard.controller';
import { AdminDashboardService } from './admin-dashboard.service'; import { AdminDashboardService } from './admin-dashboard.service';
import { AdminUsersController } from './admin-users.controller'; import { AdminUsersController } from './admin-users.controller';
@@ -23,7 +24,7 @@ import { AdminProductsService } from './admin-products.service';
import { SuperAdminGuard } from '../../common/guards/super-admin.guard'; import { SuperAdminGuard } from '../../common/guards/super-admin.guard';
@Module({ @Module({
imports: [IamModule], imports: [IamModule, TradeModule],
controllers: [ controllers: [
AdminDashboardController, AdminDashboardController,
AdminUsersController, AdminUsersController,