From 55a0a8c6bacabc11d44c393e351589f4b2f54d5e Mon Sep 17 00:00:00 2001 From: yyg-max <175597134+yyg-max@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:15:08 +0800 Subject: [PATCH 1/2] feat(admin): add order management and refund support --- docs/docs.go | 148 ++++ docs/swagger.json | 148 ++++ docs/swagger.yaml | 103 +++ frontend/app/(main)/admin/orders/page.tsx | 7 + frontend/components/common/admin/orders.tsx | 776 ++++++++++++++++++ .../common/general/dispute-dialog.tsx | 210 +++-- frontend/components/layout/sidebar.tsx | 2 + frontend/lib/services/admin/admin.service.ts | 27 + frontend/lib/services/admin/index.ts | 8 +- frontend/lib/services/admin/types.ts | 88 ++ frontend/lib/services/index.ts | 7 + frontend/pnpm-workspace.yaml | 4 + internal/apps/admin/order/constants.go | 24 + internal/apps/admin/order/errs.go | 22 + internal/apps/admin/order/logics.go | 171 ++++ internal/apps/admin/order/routers.go | 138 ++++ internal/apps/admin/order/utils.go | 61 ++ internal/apps/dispute/routers.go | 31 +- internal/apps/dispute/tasks.go | 44 +- internal/apps/payment/routers.go | 32 +- internal/model/orders.go | 4 +- internal/router/router.go | 5 + internal/service/payment.go | 67 ++ 23 files changed, 1947 insertions(+), 180 deletions(-) create mode 100644 frontend/app/(main)/admin/orders/page.tsx create mode 100644 frontend/components/common/admin/orders.tsx create mode 100644 frontend/pnpm-workspace.yaml create mode 100644 internal/apps/admin/order/constants.go create mode 100644 internal/apps/admin/order/errs.go create mode 100644 internal/apps/admin/order/logics.go create mode 100644 internal/apps/admin/order/routers.go create mode 100644 internal/apps/admin/order/utils.go diff --git a/docs/docs.go b/docs/docs.go index 46e2b3ec..2d80e61c 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -93,6 +93,76 @@ const docTemplate = `{ } } }, + "/api/v1/admin/orders": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "parameters": [ + { + "description": "request body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/order.listOrdersRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseAny" + } + } + } + } + }, + "/api/v1/admin/orders/{id}/refund": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "parameters": [ + { + "type": "integer", + "description": "订单ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "request body", + "name": "request", + "in": "body", + "schema": { + "$ref": "#/definitions/order.refundOrderRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseAny" + } + } + } + } + }, "/api/v1/admin/system-configs": { "get": { "produces": [ @@ -2143,6 +2213,84 @@ const docTemplate = `{ } } }, + "order.listOrdersRequest": { + "type": "object", + "properties": { + "client_id": { + "description": "ClientID 商户应用 Client ID。", + "type": "string", + "maxLength": 64 + }, + "end_time": { + "description": "EndTime 创建时间终点。", + "type": "string" + }, + "id": { + "description": "ID 订单 ID。", + "type": "string", + "example": "0" + }, + "merchant_order_no": { + "description": "MerchantOrderNo 商户订单号。", + "type": "string", + "maxLength": 64 + }, + "order_name": { + "description": "OrderName 订单名称前缀。", + "type": "string", + "maxLength": 64 + }, + "page": { + "description": "Page 页码,从 1 开始。", + "type": "integer", + "minimum": 1 + }, + "page_size": { + "description": "PageSize 每页数量。", + "type": "integer", + "maximum": 100, + "minimum": 1 + }, + "payee_username": { + "description": "PayeeUsername 服务方用户名前缀。", + "type": "string", + "maxLength": 255 + }, + "payer_username": { + "description": "PayerUsername 消费方用户名前缀。", + "type": "string", + "maxLength": 255 + }, + "start_time": { + "description": "StartTime 创建时间起点。", + "type": "string" + }, + "statuses": { + "description": "Statuses 订单状态筛选。", + "type": "array", + "items": { + "type": "string" + } + }, + "types": { + "description": "Types 订单类型筛选。", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "order.refundOrderRequest": { + "type": "object", + "properties": { + "remark": { + "description": "Remark 管理员备注,可选;有争议时追加到争议原因,无争议时追加到订单备注。", + "type": "string", + "maxLength": 100 + } + } + }, "payment.CreateOrderRequest": { "type": "object", "required": [ diff --git a/docs/swagger.json b/docs/swagger.json index 905a7701..a962a214 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -84,6 +84,76 @@ } } }, + "/api/v1/admin/orders": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "parameters": [ + { + "description": "request body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/order.listOrdersRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseAny" + } + } + } + } + }, + "/api/v1/admin/orders/{id}/refund": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "parameters": [ + { + "type": "integer", + "description": "订单ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "request body", + "name": "request", + "in": "body", + "schema": { + "$ref": "#/definitions/order.refundOrderRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseAny" + } + } + } + } + }, "/api/v1/admin/system-configs": { "get": { "produces": [ @@ -2134,6 +2204,84 @@ } } }, + "order.listOrdersRequest": { + "type": "object", + "properties": { + "client_id": { + "description": "ClientID 商户应用 Client ID。", + "type": "string", + "maxLength": 64 + }, + "end_time": { + "description": "EndTime 创建时间终点。", + "type": "string" + }, + "id": { + "description": "ID 订单 ID。", + "type": "string", + "example": "0" + }, + "merchant_order_no": { + "description": "MerchantOrderNo 商户订单号。", + "type": "string", + "maxLength": 64 + }, + "order_name": { + "description": "OrderName 订单名称前缀。", + "type": "string", + "maxLength": 64 + }, + "page": { + "description": "Page 页码,从 1 开始。", + "type": "integer", + "minimum": 1 + }, + "page_size": { + "description": "PageSize 每页数量。", + "type": "integer", + "maximum": 100, + "minimum": 1 + }, + "payee_username": { + "description": "PayeeUsername 服务方用户名前缀。", + "type": "string", + "maxLength": 255 + }, + "payer_username": { + "description": "PayerUsername 消费方用户名前缀。", + "type": "string", + "maxLength": 255 + }, + "start_time": { + "description": "StartTime 创建时间起点。", + "type": "string" + }, + "statuses": { + "description": "Statuses 订单状态筛选。", + "type": "array", + "items": { + "type": "string" + } + }, + "types": { + "description": "Types 订单类型筛选。", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "order.refundOrderRequest": { + "type": "object", + "properties": { + "remark": { + "description": "Remark 管理员备注,可选;有争议时追加到争议原因,无争议时追加到订单备注。", + "type": "string", + "maxLength": 100 + } + } + }, "payment.CreateOrderRequest": { "type": "object", "required": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index f42068c2..58c404e2 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -231,6 +231,65 @@ definitions: type: string type: array type: object + order.listOrdersRequest: + properties: + client_id: + description: ClientID 商户应用 Client ID。 + maxLength: 64 + type: string + end_time: + description: EndTime 创建时间终点。 + type: string + id: + description: ID 订单 ID。 + example: "0" + type: string + merchant_order_no: + description: MerchantOrderNo 商户订单号。 + maxLength: 64 + type: string + order_name: + description: OrderName 订单名称前缀。 + maxLength: 64 + type: string + page: + description: Page 页码,从 1 开始。 + minimum: 1 + type: integer + page_size: + description: PageSize 每页数量。 + maximum: 100 + minimum: 1 + type: integer + payee_username: + description: PayeeUsername 服务方用户名前缀。 + maxLength: 255 + type: string + payer_username: + description: PayerUsername 消费方用户名前缀。 + maxLength: 255 + type: string + start_time: + description: StartTime 创建时间起点。 + type: string + statuses: + description: Statuses 订单状态筛选。 + items: + type: string + type: array + types: + description: Types 订单类型筛选。 + items: + type: string + type: array + type: object + order.refundOrderRequest: + properties: + remark: + description: Remark 管理员备注,可选;有争议时追加到争议原因,无争议时追加到订单备注。 + maxLength: 100 + type: string + type: object payment.CreateOrderRequest: properties: amount: @@ -583,6 +642,50 @@ paths: $ref: '#/definitions/payment.RefundMerchantOrderResponse' tags: - payment + /api/v1/admin/orders: + post: + consumes: + - application/json + parameters: + - description: request body + in: body + name: request + required: true + schema: + $ref: '#/definitions/order.listOrdersRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseAny' + tags: + - admin + /api/v1/admin/orders/{id}/refund: + post: + consumes: + - application/json + parameters: + - description: 订单ID + in: path + name: id + required: true + type: integer + - description: request body + in: body + name: request + schema: + $ref: '#/definitions/order.refundOrderRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseAny' + tags: + - admin /api/v1/admin/system-configs: get: produces: diff --git a/frontend/app/(main)/admin/orders/page.tsx b/frontend/app/(main)/admin/orders/page.tsx new file mode 100644 index 00000000..c5ef16a4 --- /dev/null +++ b/frontend/app/(main)/admin/orders/page.tsx @@ -0,0 +1,7 @@ +"use client" + +import { OrdersManager } from "@/components/common/admin/orders" + +export default function OrdersPage() { + return +} diff --git a/frontend/components/common/admin/orders.tsx b/frontend/components/common/admin/orders.tsx new file mode 100644 index 00000000..d1b39e5f --- /dev/null +++ b/frontend/components/common/admin/orders.tsx @@ -0,0 +1,776 @@ +"use client" + +import * as React from "react" +import Link from "next/link" +import { toast } from "sonner" +import { Eye, Layers, Loader2, ReceiptText, RotateCw, Search, Undo2, X } from "lucide-react" + +import { AdminService, type AdminOrder, type AdminOrderStatus, type AdminOrderType, type ListAdminOrdersRequest } from "@/lib/services" +import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Separator } from "@/components/ui/separator" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { Textarea } from "@/components/ui/textarea" +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet" +import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" +import { ErrorInline } from "@/components/layout/error" +import { EmptyStateWithBorder } from "@/components/layout/empty" +import { LoadingStateWithBorder } from "@/components/layout/loading" +import { FilterSelect, TablePagination, statusConfig } from "@/components/common/general/table-filter" +import { DisputeHistoryTimeline } from "@/components/common/general/dispute-dialog" +import { cn, formatDateTime } from "@/lib/utils" + +const ADMIN_TYPE_CONFIG: Record = { + payment: { label: "积分消耗", color: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300" }, + transfer: { label: "积分转账", color: "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300" }, + community: { label: "社区划转", color: "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300" }, + online: { label: "在线活动", color: "bg-teal-100 text-teal-800 dark:bg-teal-900 dark:text-teal-300" }, + test: { label: "应用测试", color: "bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-300 font-bold" }, + distribute: { label: "商户分发", color: "bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-300" }, + red_envelope_send: { label: "红包支出", color: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300" }, + red_envelope_receive: { label: "红包收入", color: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300" }, + red_envelope_refund: { label: "红包退款", color: "bg-muted/50 text-gray-800 dark:bg-gray-900 dark:text-gray-300" }, +} + +const ADMIN_STATUS_CONFIG = statusConfig as Record + +const TRANSFER_STATUS_LABEL: Record = { + pending: "结算中", + completed: "已结算", +} + +const DISPUTE_STATUS_LABEL: Record = { + disputing: "处理中", + refund: "已退款", + closed: "已关闭", +} + +interface SearchValues { + id: string + order_name: string + client_id: string + merchant_order_no: string + payer_username: string + payee_username: string +} + +const EMPTY_SEARCH: SearchValues = { + id: "", + order_name: "", + client_id: "", + merchant_order_no: "", + payer_username: "", + payee_username: "", +} + +function toISODateTime(value: string, endOfDay = false) { + if (!value) return undefined + const time = endOfDay ? "23:59:59" : "00:00:00" + return new Date(`${ value }T${ time }`).toISOString() +} + +function isRefundable(order: AdminOrder) { + return (order.type === "payment" || order.type === "online") + && (order.status === "success" || order.status === "disputing" || order.status === "refused") +} + +function displayAmount(amount: string) { + return Number.parseFloat(amount).toFixed(2) +} + +function userInitial(username?: string) { + return username ? username.substring(0, 1).toUpperCase() : "-" +} + +export function OrdersManager() { + const [orders, setOrders] = React.useState([]) + const [total, setTotal] = React.useState(0) + const [page, setPage] = React.useState(1) + const [pageSize, setPageSize] = React.useState(20) + const [selectedTypes, setSelectedTypes] = React.useState([]) + const [selectedStatuses, setSelectedStatuses] = React.useState([]) + const [searchValues, setSearchValues] = React.useState(EMPTY_SEARCH) + const [draftSearchValues, setDraftSearchValues] = React.useState(EMPTY_SEARCH) + const [startDate, setStartDate] = React.useState("") + const [endDate, setEndDate] = React.useState("") + const [loading, setLoading] = React.useState(false) + const [error, setError] = React.useState(null) + const [selectedOrder, setSelectedOrder] = React.useState(null) + const [disputeOrder, setDisputeOrder] = React.useState(null) + const [refundOrder, setRefundOrder] = React.useState(null) + const [refundRemark, setRefundRemark] = React.useState("") + const [refunding, setRefunding] = React.useState(false) + + const totalPages = Math.max(1, Math.ceil(total / pageSize)) + const hasSearchValues = Object.values(searchValues).some(Boolean) + const hasDateFilter = Boolean(startDate || endDate) + + const buildRequest = React.useCallback((targetPage: number): ListAdminOrdersRequest => ({ + page: targetPage, + page_size: pageSize, + types: selectedTypes.length ? selectedTypes : undefined, + statuses: selectedStatuses.length ? selectedStatuses : undefined, + start_time: toISODateTime(startDate), + end_time: toISODateTime(endDate, true), + id: searchValues.id || undefined, + order_name: searchValues.order_name || undefined, + client_id: searchValues.client_id || undefined, + merchant_order_no: searchValues.merchant_order_no || undefined, + payer_username: searchValues.payer_username || undefined, + payee_username: searchValues.payee_username || undefined, + }), [endDate, pageSize, searchValues, selectedStatuses, selectedTypes, startDate]) + + const fetchOrders = React.useCallback(async (targetPage: number) => { + try { + setLoading(true) + setError(null) + const response = await AdminService.listOrders(buildRequest(targetPage)) + setOrders(response.orders) + setTotal(response.total) + setPage(response.page) + setPageSize(response.page_size) + } catch (err) { + setError(err instanceof Error ? err : new Error("加载订单列表失败")) + } finally { + setLoading(false) + } + }, [buildRequest]) + + React.useEffect(() => { + fetchOrders(1) + }, [fetchOrders]) + + const handleApplySearch = () => { + setSearchValues({ ...draftSearchValues }) + setPage(1) + } + + const handleClearFilters = () => { + setSelectedTypes([]) + setSelectedStatuses([]) + setSearchValues(EMPTY_SEARCH) + setDraftSearchValues(EMPTY_SEARCH) + setStartDate("") + setEndDate("") + setPage(1) + } + + const handleRefund = async () => { + if (!refundOrder) return + + try { + setRefunding(true) + await AdminService.refundOrder(refundOrder.id, { + remark: refundRemark.trim() || undefined, + }) + toast.success("退款成功", { + description: `订单 ${ refundOrder.order_no } 已退款`, + }) + setRefundOrder(null) + setRefundRemark("") + await fetchOrders(page) + } catch (err) { + toast.error("退款失败", { + description: err instanceof Error ? err.message : "未知错误", + }) + } finally { + setRefunding(false) + } + } + + const activeFilter = selectedTypes.length > 0 || selectedStatuses.length > 0 || hasSearchValues || hasDateFilter + + return ( +
+
+
订单管理
+
+ +
+
+ { + setDraftSearchValues(EMPTY_SEARCH) + setSearchValues(EMPTY_SEARCH) + setPage(1) + }} + /> + + label="类型" + selectedValues={selectedTypes} + options={ADMIN_TYPE_CONFIG} + onToggleValue={(type) => { + setPage(1) + setSelectedTypes(prev => prev.includes(type) ? prev.filter(item => item !== type) : [...prev, type]) + }} + /> + + label="状态" + selectedValues={selectedStatuses} + options={ADMIN_STATUS_CONFIG} + onToggleValue={(status) => { + setPage(1) + setSelectedStatuses(prev => prev.includes(status) ? prev.filter(item => item !== status) : [...prev, status]) + }} + /> + { + setStartDate(value) + setPage(1) + }} + onEndChange={(value) => { + setEndDate(value) + setPage(1) + }} + /> + {activeFilter && ( + <> + + + + )} +
+ + + + { + setPage(targetPage) + fetchOrders(targetPage) + }} + onPageSizeChange={(size) => { + setPageSize(size) + setPage(1) + }} + onRefresh={() => fetchOrders(page)} + loading={loading} + /> +
+ + {error ? ( +
+ fetchOrders(page)} className="justify-center" /> +
+ ) : loading && orders.length === 0 ? ( + + ) : orders.length === 0 ? ( + + ) : ( + { + setRefundOrder(order) + setRefundRemark("") + }} + /> + )} + + !open && setSelectedOrder(null)} /> + !open && setDisputeOrder(null)} /> + { + if (!open && !refunding) { + setRefundOrder(null) + setRefundRemark("") + } + }} + onConfirm={handleRefund} + /> +
+ ) +} + +function OrderSearchFilter({ + values, + applied, + onChange, + onApply, + onClear, +}: { + values: SearchValues + applied: boolean + onChange: (values: SearchValues) => void + onApply: () => void + onClear: () => void +}) { + return ( + + + + + +
+ onChange({ ...values, id: value })} /> + onChange({ ...values, order_name: value })} /> + onChange({ ...values, client_id: value })} /> + onChange({ ...values, merchant_order_no: value })} /> + onChange({ ...values, payer_username: value })} /> + onChange({ ...values, payee_username: value })} /> +
+
+ + +
+
+
+ ) +} + +function SearchInput({ label, value, onChange }: { label: string; value: string; onChange: (value: string) => void }) { + return ( +
+ + onChange(event.target.value)} className="h-8 text-xs" /> +
+ ) +} + +function DateFilter({ + startDate, + endDate, + onStartChange, + onEndChange, +}: { + startDate: string + endDate: string + onStartChange: (value: string) => void + onEndChange: (value: string) => void +}) { + const active = Boolean(startDate || endDate) + + return ( + + + + + +
+
+ + onStartChange(event.target.value)} className="h-8 text-xs" /> +
+
+ + onEndChange(event.target.value)} className="h-8 text-xs" /> +
+
+
+
+ ) +} + +function OrdersTable({ + orders, + loading, + onShowDetail, + onShowDispute, + onRefund, +}: { + orders: AdminOrder[] + loading: boolean + onShowDetail: (order: AdminOrder) => void + onShowDispute: (order: AdminOrder) => void + onRefund: (order: AdminOrder) => void +}) { + return ( +
+
+ + + + 名称 + 积分 + 类型 + 状态 + 积分动向 + 应用名 + 编号 + 业务单号 + 结算 + 创建时间 + 操作 + + + + {orders.map(order => { + const typeMeta = ADMIN_TYPE_CONFIG[order.type] + const statusMeta = ADMIN_STATUS_CONFIG[order.status] + const isDisputing = order.status === "disputing" + return ( + + {order.order_name} + {displayAmount(order.amount)} + + {typeMeta.label} + + + {statusMeta.label} + + + + + + {order.app_name ? ( + order.app_homepage_url ? ( + + {order.app_name} + + ) : ( + order.app_name + ) + ) : ( + "-" + )} + + {order.order_no} + {order.merchant_order_no || "-"} + {TRANSFER_STATUS_LABEL[order.payee_transfer_status] || "-"} + {formatDateTime(order.created_at)} + + + {order.dispute_id && ( + + )} + {isRefundable(order) && ( + + )} + + + ) + })} + +
+
+
+ ) +} + +function OrderFlow({ order }: { order: AdminOrder }) { + if (order.status === "pending" || order.status === "expired" || order.type === "community" || order.type === "red_envelope_send" || order.type === "red_envelope_refund") { + return
-
+ } + + return ( + + + +
+ + + + {userInitial(order.payer_username)} + + +
+ + + + {userInitial(order.payee_username)} + + +
+
+ +
+
+

消费方

+

ID: {order.payer_user_id}

+

账户: {order.payer_username || "-"}

+
+
+

服务方

+

ID: {order.payee_user_id}

+

账户: {order.payee_username || "-"}

+
+
+
+
+
+ ) +} + +function OrderDetailSheet({ order, onOpenChange }: { order: AdminOrder | null; onOpenChange: (open: boolean) => void }) { + return ( + + + + 订单详情 + {order?.order_no} + + {order && ( +
+ + + + +
+ )} +
+
+ ) +} + +function DetailGroup({ rows }: { rows: Array<[string, string]> }) { + return ( +
+ {rows.map(([label, value]) => ( +
+ {label} + {value} +
+ ))} +
+ ) +} + +function AdminDisputeDialog({ order, onOpenChange }: { order: AdminOrder | null; onOpenChange: (open: boolean) => void }) { + const disputeHistory = order?.dispute_id && order.dispute_reason && order.dispute_created_at && order.dispute_updated_at + ? { + reason: order.dispute_reason, + created_at: order.dispute_created_at, + updated_at: order.dispute_updated_at, + } + : null + const timelineStatus = order?.status === "refused" ? order.status : (order?.dispute_status || order?.status) + + return ( + + + + 争议详情 + {order ? `订单 ${ order.order_no }` : "查看争议处理记录"} + + {order && ( +
+
+
+ 争议 ID + {order.dispute_id || "-"} +
+
+ 争议状态 + {order.dispute_status ? DISPUTE_STATUS_LABEL[order.dispute_status] : "-"} +
+
+ 消费方 + {order.payer_username || "-"} +
+
+ 服务方 + {order.payee_username || "-"} +
+
+
+ + {disputeHistory && timelineStatus ? ( + + ) : ( +
+ 暂无争议内容 +
+ )} +
+
+ )} + + + +
+
+ ) +} + +function RefundDialog({ + order, + remark, + refunding, + onRemarkChange, + onOpenChange, + onConfirm, +}: { + order: AdminOrder | null + remark: string + refunding: boolean + onRemarkChange: (value: string) => void + onOpenChange: (open: boolean) => void + onConfirm: () => void +}) { + const disputeHistory = order?.dispute_id && order.dispute_reason && order.dispute_created_at && order.dispute_updated_at + ? { + reason: order.dispute_reason, + created_at: order.dispute_created_at, + updated_at: order.dispute_updated_at, + } + : null + + return ( + + + + 确认退款 + + 退款会退还消费方订单全额,商家手续费不退。该操作提交后不能二次退款。 + + + {order && ( +
+
+
+ 编号 + {order.order_no} +
+
+ 订单积分 + {displayAmount(order.amount)} +
+
+ 服务方 + {order.payee_username || "-"} +
+
+
+ + {disputeHistory ? ( + + ) : ( +
+ 该订单没有争议记录 +
+ )} +
+
+ +