feat: 反馈加反馈类型/运营回复 + 提现加提现类型筛选 (#34)

用户反馈后台 + 提现审核后台的运营字段与筛选。

- 反馈页:新增「反馈类型」列(比价反馈/普通反馈 + 场景)、顶部「反馈类型」筛选;审核抽屉采纳/拒绝
  都能填「给用户的回复」(用户端可见),并回显反馈类型/场景/运营回复。
- 提现页:新增「提现类型」列(福利页提现/邀请提现)、顶部「提现类型」筛选,详情抽屉展示提现类型。
- types.ts:Feedback 加 source/scene/admin_reply,WithdrawOrder 加 source。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: zzhyyyyy <2685922758@qq.com>
Reviewed-on: #34
Co-authored-by: zhuzihao <zhuzihao@wonderable.ai>
Co-committed-by: zhuzihao <zhuzihao@wonderable.ai>
This commit was merged in pull request #34.
This commit is contained in:
2026-07-02 19:50:09 +08:00
committed by marco
parent 2727eaa929
commit 281cfdb0e4
4 changed files with 164 additions and 10 deletions
@@ -49,6 +49,17 @@ const statusTag = (s: string) => {
return <Tag color={m.color}>{m.label}</Tag>;
};
// 反馈类型:comparison=比价反馈 / profile(及旧数据)=普通反馈
const SOURCE_META: Record<string, { label: string; color: string }> = {
comparison: { label: '比价反馈', color: 'geekblue' },
profile: { label: '普通反馈', color: 'default' },
};
const sourceTag = (s: string) => {
const m = SOURCE_META[s] ?? { label: '普通反馈', color: 'default' };
return <Tag color={m.color}>{m.label}</Tag>;
};
function FeedbackImages({ images }: { images: string[] | null }) {
if (!images || !images.length) return <Text type="secondary"></Text>;
return (
@@ -113,12 +124,14 @@ export default function FeedbackHandleDrawer({ feedback, open, onClose, onDone }
await api.post(`/admin/api/feedbacks/${feedback.id}/approve`, {
reward_coins: v.reward_coins,
note: v.note?.trim() || null,
reply: v.reply?.trim() || null,
});
message.success(`已采纳,发放 ${v.reward_coins} 金币`);
} else {
await api.post(`/admin/api/feedbacks/${feedback.id}/reject`, {
reason: v.reason.trim(),
note: v.note?.trim() || null,
reply: v.reply?.trim() || null,
});
message.success('已拒绝');
}
@@ -155,6 +168,14 @@ export default function FeedbackHandleDrawer({ feedback, open, onClose, onDone }
<Text type="secondary">
{feedback.user_id} · {formatWallTime(feedback.created_at)}
</Text>
<div style={{ marginTop: 6 }}>
{sourceTag(feedback.source)}
{feedback.scene ? (
<Text type="secondary" style={{ marginLeft: 6 }}>
:{feedback.scene}
</Text>
) : null}
</div>
<Paragraph style={{ whiteSpace: 'pre-wrap', marginTop: 8, marginBottom: 8 }}>
{feedback.content || '-'}
</Paragraph>
@@ -200,7 +221,11 @@ export default function FeedbackHandleDrawer({ feedback, open, onClose, onDone }
</div>
)}
<div style={{ marginTop: 6 }}>
:{feedback.review_note || <Text type="secondary"></Text>}
():{feedback.review_note || <Text type="secondary"></Text>}
</div>
<div style={{ marginTop: 6 }}>
():
{feedback.admin_reply || <Text type="secondary"></Text>}
</div>
{isPending(feedback.status) && (
<div style={{ marginTop: 6 }}>
@@ -243,11 +268,23 @@ export default function FeedbackHandleDrawer({ feedback, open, onClose, onDone }
</Form.Item>
<Form.Item
name="note"
label="采纳要点 / 审核备注(选填)"
label="采纳要点 / 审核备注(选填,内部)"
rules={[{ max: 256, message: '最多 256 字' }]}
>
<Input.TextArea rows={3} maxLength={256} showCount placeholder="如:建议已采纳,将在下个版本上线" />
</Form.Item>
<Form.Item
name="reply"
label="给用户的回复(选填,用户端可见)"
rules={[{ max: 256, message: '最多 256 字' }]}
>
<Input.TextArea
rows={2}
maxLength={256}
showCount
placeholder="将展示在用户的「我的反馈」里,如:感谢反馈,已收到~"
/>
</Form.Item>
</>
) : (
<>
@@ -268,6 +305,18 @@ export default function FeedbackHandleDrawer({ feedback, open, onClose, onDone }
>
<Input.TextArea rows={2} maxLength={256} showCount placeholder="运营内部备注" />
</Form.Item>
<Form.Item
name="reply"
label="给用户的回复(选填,用户端可见)"
rules={[{ max: 256, message: '最多 256 字' }]}
>
<Input.TextArea
rows={2}
maxLength={256}
showCount
placeholder="除未采纳原因外,想额外对用户说的话(选填)"
/>
</Form.Item>
</>
)}
</Form>
@@ -294,7 +343,10 @@ export default function FeedbackHandleDrawer({ feedback, open, onClose, onDone }
<Text type="secondary">
#{h.id} · {formatWallTime(h.created_at)}
</Text>
{statusTag(h.status)}
<Space size={4}>
{sourceTag(h.source)}
{statusTag(h.status)}
</Space>
</div>
<Paragraph
style={{ whiteSpace: 'pre-wrap', margin: '6px 0 0' }}
@@ -313,6 +365,9 @@ export default function FeedbackHandleDrawer({ feedback, open, onClose, onDone }
:{h.reject_reason || '-'}
</Text>
)}
{h.admin_reply ? (
<div style={{ fontSize: 12, marginTop: 2 }}>:{h.admin_reply}</div>
) : null}
</div>
))}
</Space>