Files
order_site/apps/frontend/src/pages/admin/panels/FeedbacksPanel.tsx
T

176 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ReloadOutlined } from '@ant-design/icons'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { App, Button, Card, Form, Input, Modal, Space, Table, Tag, Typography } from 'antd'
import type { TableColumnsType } from 'antd'
import { useEffect, useState } from 'react'
import { useSearchParams } from 'react-router'
import { fetchAdminWorkerOrderFeedbacks, resolveAdminWorkerOrderFeedback } from '@/services/admin'
import type { WorkerOrderFeedback } from '@/types/worker-platform'
import { formatAdminDateTime } from '@/utils/admin-time'
type FeedbackReplyFormValues = {
reply?: string
}
export default function FeedbacksPanel() {
const { message } = App.useApp()
const queryClient = useQueryClient()
const [searchParams, setSearchParams] = useSearchParams()
const [target, setTarget] = useState<WorkerOrderFeedback | null>(null)
const [form] = Form.useForm<FeedbackReplyFormValues>()
const feedbacksQuery = useQuery({
queryKey: ['admin-worker-order-feedbacks', 'pending'],
queryFn: () => fetchAdminWorkerOrderFeedbacks('pending'),
})
useEffect(() => {
const feedbackId = Number(searchParams.get('feedbackId') || 0)
if (!Number.isInteger(feedbackId) || feedbackId <= 0) return
const feedback = (feedbacksQuery.data?.data.items || []).find(
(item) => item.feedbackId === feedbackId,
)
if (!feedback) return
setTarget(feedback)
form.resetFields()
setSearchParams(
(current) => {
current.delete('feedbackId')
return current
},
{ replace: true },
)
}, [feedbacksQuery.data, form, searchParams, setSearchParams])
async function submitReply(values: FeedbackReplyFormValues) {
if (!target) return
try {
await resolveAdminWorkerOrderFeedback(target.feedbackId, {
reply: String(values.reply || '').trim(),
})
message.success('问题反馈已处理')
setTarget(null)
form.resetFields()
await Promise.all([
queryClient.invalidateQueries({ queryKey: ['admin-worker-order-feedbacks'] }),
queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-orders'] }),
])
} catch (error) {
message.error(error instanceof Error ? error.message : '处理问题反馈失败')
}
}
const columns: TableColumnsType<WorkerOrderFeedback> = [
{
title: '订单',
width: 260,
render: (_, feedback) => (
<div className="cell-stack">
<Typography.Text strong>{feedback.productName || '-'}</Typography.Text>
<Typography.Text type="secondary">{feedback.workOrderNo || '-'}</Typography.Text>
<Typography.Text type="secondary">{feedback.shopName || '-'}</Typography.Text>
</div>
),
},
{
title: '打手',
width: 180,
render: (_, feedback) => (
<Space wrap>
<Typography.Text>{feedback.workerName || `打手 #${feedback.workerId}`}</Typography.Text>
{feedback.shareQuantity ? (
<Tag color="purple">拼单 {feedback.shareQuantity} 份</Tag>
) : null}
</Space>
),
},
{
title: '反馈内容',
render: (_, feedback) => (
<Typography.Text ellipsis={{ tooltip: feedback.content }}>
{feedback.content}
</Typography.Text>
),
},
{
title: '反馈时间',
width: 180,
render: (_, feedback) => formatAdminDateTime(feedback.createdAt),
},
{
title: '操作',
width: 120,
render: (_, feedback) => (
<Button type="primary" size="small" onClick={() => setTarget(feedback)}>
处理
</Button>
),
},
]
return (
<>
<Card
title={`待处理问题反馈(${feedbacksQuery.data?.data.items.length || 0})`}
extra={
<Button
icon={<ReloadOutlined />}
loading={feedbacksQuery.isFetching}
onClick={() => feedbacksQuery.refetch()}
>
刷新
</Button>
}
bordered={false}
>
<Table<WorkerOrderFeedback>
rowKey="feedbackId"
size="small"
loading={feedbacksQuery.isLoading}
dataSource={feedbacksQuery.data?.data.items || []}
columns={columns}
pagination={false}
locale={{ emptyText: '暂无待处理问题反馈' }}
scroll={{ x: 900 }}
/>
</Card>
<Modal
title="处理问题反馈"
open={Boolean(target)}
onCancel={() => {
setTarget(null)
form.resetFields()
}}
onOk={() => form.submit()}
okText="完成处理"
destroyOnHidden
>
{target ? (
<Space direction="vertical" size={12} style={{ width: '100%' }}>
<Typography.Text>
{target.workerName || `打手 #${target.workerId}`}:{target.content}
</Typography.Text>
<Typography.Text type="secondary">
完成处理不会更改订单状态、验收或资金,仅向打手记录处理回复。
</Typography.Text>
<Form form={form} layout="vertical" onFinish={submitReply}>
<Form.Item
label="处理回复"
name="reply"
rules={[{ required: true, whitespace: true, message: '请填写处理回复' }]}
>
<Input.TextArea
rows={4}
maxLength={500}
showCount
placeholder="请说明已如何处理,或请打手后续操作"
/>
</Form.Item>
</Form>
</Space>
) : null}
</Modal>
</>
)
}