From c6a8cb2cf0da8429aec7b1c8e0b6f86edb29dcfc Mon Sep 17 00:00:00 2001 From: zzhyyyyy <2685922758@qq.com> Date: Thu, 2 Jul 2026 16:43:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8F=8D=E9=A6=88=E5=8A=A0=E5=8F=8D?= =?UTF-8?q?=E9=A6=88=E7=B1=BB=E5=9E=8B/=E8=BF=90=E8=90=A5=E5=9B=9E?= =?UTF-8?q?=E5=A4=8D=20+=20=E6=8F=90=E7=8E=B0=E5=8A=A0=E6=8F=90=E7=8E=B0?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E7=AD=9B=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户反馈后台 + 提现审核后台的运营字段与筛选。 - 反馈页:新增「反馈类型」列(比价反馈/普通反馈 + 场景)、顶部「反馈类型」筛选;审核抽屉采纳/拒绝 都能填「给用户的回复」(用户端可见),并回显反馈类型/场景/运营回复。 - 提现页:新增「提现类型」列(福利页提现/邀请提现)、顶部「提现类型」筛选,详情抽屉展示提现类型。 - types.ts:Feedback 加 source/scene/admin_reply,WithdrawOrder 加 source。 Co-Authored-By: Claude Opus 4.8 --- .../(main)/feedbacks/FeedbackHandleDrawer.tsx | 61 ++++++++++++++++- src/app/(main)/feedbacks/page.tsx | 67 +++++++++++++++++-- src/app/(main)/withdraws/page.tsx | 37 +++++++++- src/lib/types.ts | 9 ++- 4 files changed, 164 insertions(+), 10 deletions(-) diff --git a/src/app/(main)/feedbacks/FeedbackHandleDrawer.tsx b/src/app/(main)/feedbacks/FeedbackHandleDrawer.tsx index 3621b7b..e25dd3a 100644 --- a/src/app/(main)/feedbacks/FeedbackHandleDrawer.tsx +++ b/src/app/(main)/feedbacks/FeedbackHandleDrawer.tsx @@ -49,6 +49,17 @@ const statusTag = (s: string) => { return {m.label}; }; +// 反馈类型:comparison=比价反馈 / profile(及旧数据)=普通反馈 +const SOURCE_META: Record = { + comparison: { label: '比价反馈', color: 'geekblue' }, + profile: { label: '普通反馈', color: 'default' }, +}; + +const sourceTag = (s: string) => { + const m = SOURCE_META[s] ?? { label: '普通反馈', color: 'default' }; + return {m.label}; +}; + function FeedbackImages({ images }: { images: string[] | null }) { if (!images || !images.length) return 无截图; 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 } 用户 {feedback.user_id} · {formatWallTime(feedback.created_at)} +
+ {sourceTag(feedback.source)} + {feedback.scene ? ( + + 场景:{feedback.scene} + + ) : null} +
{feedback.content || '-'} @@ -200,7 +221,11 @@ export default function FeedbackHandleDrawer({ feedback, open, onClose, onDone } )}
- 审核备注:{feedback.review_note || (无)} + 审核备注(内部):{feedback.review_note || (无)} +
+
+ 给用户的回复(用户可见): + {feedback.admin_reply || (无)}
{isPending(feedback.status) && (
@@ -243,11 +268,23 @@ export default function FeedbackHandleDrawer({ feedback, open, onClose, onDone } + + + ) : ( <> @@ -268,6 +305,18 @@ export default function FeedbackHandleDrawer({ feedback, open, onClose, onDone } > + + + )} @@ -294,7 +343,10 @@ export default function FeedbackHandleDrawer({ feedback, open, onClose, onDone } #{h.id} · {formatWallTime(h.created_at)} - {statusTag(h.status)} + + {sourceTag(h.source)} + {statusTag(h.status)} +
)} + {h.admin_reply ? ( +
回复:{h.admin_reply}
+ ) : null} ))} diff --git a/src/app/(main)/feedbacks/page.tsx b/src/app/(main)/feedbacks/page.tsx index 9447e92..f773e96 100644 --- a/src/app/(main)/feedbacks/page.tsx +++ b/src/app/(main)/feedbacks/page.tsx @@ -49,7 +49,42 @@ const statusTag = (s: string) => { return {m.label}; }; -// 审核结果展示(采纳=金币+批注 / 未采纳=原因 / 待审核=-),主表与「该用户全部反馈」抽屉共用 +// 反馈类型:comparison=比价反馈(比价结果页入口) / profile=普通反馈(「我的」页入口)。 +// 旧数据与未识别来源按普通反馈展示。 +const SOURCE_META: Record = { + comparison: { label: '比价反馈', color: 'geekblue' }, + profile: { label: '普通反馈', color: 'default' }, +}; + +const SOURCE_OPTIONS = [ + { value: 'comparison', label: '比价反馈' }, + { value: 'profile', label: '普通反馈' }, +]; + +// 反馈类型标签 +(比价反馈时)问题场景副标题 +const renderSource = (f: Feedback) => { + const m = SOURCE_META[f.source] ?? { label: '普通反馈', color: 'default' }; + return ( + + {m.label} + {f.scene ? ( + + {f.scene} + + ) : null} + + ); +}; + +// 用户可见的运营回复留言(采纳/未采纳都可能有) +const replyLine = (f: Feedback) => + f.admin_reply ? ( + + 回复:{f.admin_reply} + + ) : null; + +// 审核结果展示(采纳=金币+批注 / 未采纳=原因 / 回复留言 / 待审核=-),主表与「该用户全部反馈」抽屉共用 const renderReview = (f: Feedback) => { if (f.status === 'adopted') { return ( @@ -60,14 +95,18 @@ const renderReview = (f: Feedback) => { {f.review_note} ) : null} + {replyLine(f)} ); } if (f.status === 'rejected') { return ( - - 未采纳:{f.reject_reason || '-'} - + + + 未采纳:{f.reject_reason || '-'} + + {replyLine(f)} + ); } return -; @@ -95,6 +134,7 @@ const renderDeviceOs = (f: Feedback) => { const RECORD_COLUMNS: ColumnsType = [ { title: '提交时间', dataIndex: 'created_at', width: 150, render: (v: string) => formatWallTime(v) }, { title: '状态', dataIndex: 'status', width: 80, render: statusTag }, + { title: '反馈类型', key: 'source', width: 100, render: (_: unknown, f: Feedback) => renderSource(f) }, { title: '内容', dataIndex: 'content', @@ -116,6 +156,7 @@ const RECORD_COLUMNS: ColumnsType = [ export default function FeedbacksPage() { // 筛选草稿:点「查询」才应用(避免输入即刷新) const [status, setStatus] = useState(); + const [source, setSource] = useState(); const [userId, setUserId] = useState(''); const [content, setContent] = useState(''); const [createdRange, setCreatedRange] = useState<[Dayjs, Dayjs] | null>(null); @@ -158,6 +199,7 @@ export default function FeedbacksPage() { const search = () => setApplied({ status, + source, user_id: userId.trim() ? Number(userId.trim()) : undefined, content: content.trim() || undefined, created_from: createdRange?.[0] ? createdRange[0].startOf('day').toISOString() : undefined, @@ -166,6 +208,7 @@ export default function FeedbacksPage() { const resetFilters = () => { setStatus(undefined); + setSource(undefined); setUserId(''); setContent(''); setCreatedRange(null); @@ -216,6 +259,12 @@ export default function FeedbacksPage() { width: 120, render: (v: string | null) => v || 无昵称, }, + { + title: '反馈类型', + key: 'source', + width: 110, + render: (_: unknown, f: Feedback) => renderSource(f), + }, { title: '提交版本号', dataIndex: 'app_version', @@ -325,6 +374,14 @@ export default function FeedbacksPage() { { value: 'rejected', label: '未采纳' }, ]} /> + = { + coin_cash: '福利页提现', + invite_cash: '邀请提现', +}; +const SOURCE_COLOR: Record = { + coin_cash: 'blue', + invite_cash: 'purple', +}; +const SOURCE_OPTIONS = [ + { value: 'coin_cash', label: '福利页提现' }, + { value: 'invite_cash', label: '邀请提现' }, +]; const DATE_FIELD_OPTIONS = [ { value: 'created_at', label: '申请时间' }, { value: 'updated_at', label: '更新时间' }, @@ -184,9 +197,11 @@ export default function WithdrawsPage() { const [sortBy, setSortBy] = useState<'created_at' | 'amount_cents'>('created_at'); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); const [quickFilter, setQuickFilter] = useState(undefined); + const [source, setSource] = useState(undefined); const filters: Record = {}; if (activeStatus !== 'all') filters.status = activeStatus; + if (source) filters.source = source; if (keyword.trim()) filters.keyword = keyword.trim(); if (dateRange?.[0]) filters.date_from = dateRange[0].startOf('day').toISOString(); if (dateRange?.[1]) filters.date_to = dateRange[1].endOf('day').toISOString(); @@ -211,6 +226,7 @@ export default function WithdrawsPage() { setSortBy('created_at'); setSortOrder('desc'); setQuickFilter(undefined); + setSource(undefined); }; const applyQuickFilter = (value?: string) => { @@ -481,6 +497,12 @@ export default function WithdrawsPage() { width: 120, render: (v: number) => {yuan(v)}, }, + { + title: '提现类型', + dataIndex: 'source', + width: 110, + render: (v: string) => {SOURCE_LABEL[v] || v}, + }, { title: '累计提现', dataIndex: 'cumulative_success_cents', @@ -724,6 +746,14 @@ export default function WithdrawsPage() { }} onSearch={(value) => setKeyword(value.trim())} /> + `共 ${t} 条`, onChange: onPageChange, }} - scroll={{ x: 1350 }} + scroll={{ x: 1460 }} onRow={(record) => ({ onClick: () => openDetail(record), style: { cursor: 'pointer' }, @@ -881,6 +911,11 @@ export default function WithdrawsPage() { {yuan(detail.order.amount_cents)} + + + {SOURCE_LABEL[detail.order.source] || detail.order.source} + + {detail.order.user_name || '-'} diff --git a/src/lib/types.ts b/src/lib/types.ts index 7c3e587..74b24d2 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -117,6 +117,8 @@ export interface WithdrawOrder { user_id: number; out_bill_no: string; amount_cents: number; + // 提现类型:coin_cash(福利页提现,金币兑换现金) / invite_cash(邀请提现,邀请奖励金) + source: string; user_name: string | null; status: string; // reviewing / pending / success / failed / rejected wechat_state: string | null; @@ -239,12 +241,17 @@ export interface Feedback { user_id: number; content: string; contact: string; + // 反馈来源入口:profile(「我的」页=普通反馈) / comparison(比价结果页=比价反馈) + source: string; + // 比价反馈的问题场景(找错商品/优惠不对…);普通反馈为 null + scene: string | null; images: string[] | null; // pending(审核中) / adopted(已采纳) / rejected(未采纳);历史数据可能为 new status: string; reject_reason: string | null; // 未采纳原因(用户端可见) reward_coins: number | null; // 采纳后发放金币 - review_note: string | null; // 审核批注(采纳要点 / 内部备注) + review_note: string | null; // 审核批注(采纳要点 / 内部备注,用户不可见) + admin_reply: string | null; // 运营给用户的回复留言(用户端可见) reviewed_by_admin_id: number | null; reviewed_at: string | null; created_at: string;