From 98cea13d515adec5e0e47745038d3679bf9494be Mon Sep 17 00:00:00 2001 From: zhuzihao Date: Mon, 29 Jun 2026 23:11:07 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=8F=8D=E9=A6=88/=E4=BD=8E?= =?UTF-8?q?=E4=BB=B7=E5=AE=A1=E6=A0=B8=20admin=20=E9=A1=B5=E6=94=B9?= =?UTF-8?q?=E7=89=88(=E9=87=8D=E5=91=BD=E5=90=8D+=E4=BA=8C=E7=BB=B4?= =?UTF-8?q?=E7=A0=81=E6=8C=AA=E4=BD=8D+=E7=BB=9F=E8=AE=A1=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D+=E5=88=97=E5=A2=9E=E5=BC=BA)=20(#28)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重命名:菜单与页面标题「反馈工单→用户反馈」「上报审核→低价审核」 - 反馈页「加群二维码」卡挪到系统配置页,新增「反馈二维码」tab - 修复反馈四态统计恒为 0:对接后端新增的 /admin/api/feedbacks/summary - 两页表格增强:用户列拆为 用户ID/手机号/用户昵称;新增 提交版本号、机型/OS版本 列; 点手机号弹抽屉看该用户全部记录,抽屉顶部汇总 提交/上报总计 + 奖励总计 - 低价审核:提交时间列可排序、审核结果与操作拆两列、顶部统计改卡片式、 Trace 列对齐比价记录页(有 trace_url 即可点) - 修复反馈时间显示快 8 小时:反馈时间为北京 wall-clock,改用 formatWallTime Co-Authored-By: Claude Opus 4.8 --------- Co-authored-by: zzhyyyyy <2685922758@qq.com> Reviewed-on: https://gitea.shaguabijia.com/WonderableAI/shaguabijia-admin-web/pulls/28 Co-authored-by: zhuzihao Co-committed-by: zhuzihao --- .../FeedbackQrConfig.tsx | 2 +- src/app/(main)/config/page.tsx | 2 + .../(main)/feedbacks/FeedbackHandleDrawer.tsx | 8 +- src/app/(main)/feedbacks/page.tsx | 204 ++++++++++--- src/app/(main)/layout.tsx | 272 ++---------------- src/app/(main)/price-reports/page.tsx | 268 ++++++++++++----- src/components/UserRecordsDrawer.tsx | 111 +++++++ src/lib/types.ts | 104 ++----- 8 files changed, 538 insertions(+), 433 deletions(-) rename src/app/(main)/{feedbacks => config}/FeedbackQrConfig.tsx (98%) create mode 100644 src/components/UserRecordsDrawer.tsx diff --git a/src/app/(main)/feedbacks/FeedbackQrConfig.tsx b/src/app/(main)/config/FeedbackQrConfig.tsx similarity index 98% rename from src/app/(main)/feedbacks/FeedbackQrConfig.tsx rename to src/app/(main)/config/FeedbackQrConfig.tsx index c2eb881..07eb077 100644 --- a/src/app/(main)/feedbacks/FeedbackQrConfig.tsx +++ b/src/app/(main)/config/FeedbackQrConfig.tsx @@ -12,7 +12,7 @@ const { Text } = Typography; const ACCEPT = ['image/jpeg', 'image/png', 'image/webp']; -/** 反馈页「加群二维码」卡配置。改完 App 意见反馈页下次进入即同步。「反馈工单」页的一个区块。 */ +/** 反馈页「加群二维码」卡配置。改完 App 意见反馈页下次进入即同步。「系统配置 → 反馈二维码」tab 的一个区块。 */ export default function FeedbackQrConfig() { const [cfg, setCfg] = useState(null); const [loading, setLoading] = useState(true); diff --git a/src/app/(main)/config/page.tsx b/src/app/(main)/config/page.tsx index ea90007..4d2070d 100644 --- a/src/app/(main)/config/page.tsx +++ b/src/app/(main)/config/page.tsx @@ -5,6 +5,7 @@ import { App, Button, Card, Input, InputNumber, Space, Spin, Switch, Tabs, Tag, import { api, errMsg } from '@/lib/api'; import HomeMarqueeSeeds from './HomeMarqueeSeeds'; import HomeStatsConfig from './HomeStatsConfig'; +import FeedbackQrConfig from './FeedbackQrConfig'; interface ConfigItem { key: string; @@ -162,6 +163,7 @@ export default function ConfigPage() { ), }, { key: 'welfare', label: '福利页', children: welfareConfig }, + { key: 'feedback-qr', label: '反馈二维码', children: }, ]} /> diff --git a/src/app/(main)/feedbacks/FeedbackHandleDrawer.tsx b/src/app/(main)/feedbacks/FeedbackHandleDrawer.tsx index 7761b0b..3621b7b 100644 --- a/src/app/(main)/feedbacks/FeedbackHandleDrawer.tsx +++ b/src/app/(main)/feedbacks/FeedbackHandleDrawer.tsx @@ -19,7 +19,7 @@ import { } from 'antd'; import { api, errMsg } from '@/lib/api'; import { canDo } from '@/lib/auth'; -import { formatUtcTime } from '@/lib/format'; +import { formatWallTime } from '@/lib/format'; import { mediaUrl } from '@/lib/media'; import type { CursorPage, Feedback } from '@/lib/types'; @@ -153,7 +153,7 @@ export default function FeedbackHandleDrawer({ feedback, open, onClose, onDone } {/* 本条反馈 */}
- 用户 {feedback.user_id} · {formatUtcTime(feedback.created_at)} + 用户 {feedback.user_id} · {formatWallTime(feedback.created_at)} {feedback.content || '-'} @@ -185,7 +185,7 @@ export default function FeedbackHandleDrawer({ feedback, open, onClose, onDone }
状态:{statusTag(feedback.status)} {feedback.reviewed_at ? ( - · {formatUtcTime(feedback.reviewed_at)} + · {formatWallTime(feedback.reviewed_at)} ) : null}
{feedback.status === 'adopted' && ( @@ -292,7 +292,7 @@ export default function FeedbackHandleDrawer({ feedback, open, onClose, onDone } >
- #{h.id} · {formatUtcTime(h.created_at)} + #{h.id} · {formatWallTime(h.created_at)} {statusTag(h.status)}
diff --git a/src/app/(main)/feedbacks/page.tsx b/src/app/(main)/feedbacks/page.tsx index fe4d3ad..9447e92 100644 --- a/src/app/(main)/feedbacks/page.tsx +++ b/src/app/(main)/feedbacks/page.tsx @@ -1,26 +1,29 @@ 'use client'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import type { ColumnsType } from 'antd/es/table'; import type { SorterResult } from 'antd/es/table/interface'; import { Button, + Card, DatePicker, Image, Input, Select, Space, + Statistic, Table, Tag, Typography, } from 'antd'; import type { Dayjs } from 'dayjs'; +import { api } from '@/lib/api'; import { canDo } from '@/lib/auth'; -import { formatUtcTime } from '@/lib/format'; +import { formatWallTime } from '@/lib/format'; import { usePagedList } from '@/lib/usePagedList'; -import type { Feedback } from '@/lib/types'; -import FeedbackQrConfig from './FeedbackQrConfig'; +import type { Feedback, FeedbackSummary } from '@/lib/types'; import FeedbackHandleDrawer from './FeedbackHandleDrawer'; +import UserRecordsDrawer from '@/components/UserRecordsDrawer'; const { Text, Paragraph } = Typography; const { RangePicker } = DatePicker; @@ -41,6 +44,75 @@ const STATUS_META: Record = { rejected: { label: '未采纳', color: 'red' }, }; +const statusTag = (s: string) => { + const m = STATUS_META[s] ?? { label: s, color: 'default' }; + return {m.label}; +}; + +// 审核结果展示(采纳=金币+批注 / 未采纳=原因 / 待审核=-),主表与「该用户全部反馈」抽屉共用 +const renderReview = (f: Feedback) => { + if (f.status === 'adopted') { + return ( + + +{f.reward_coins ?? 0} 金币 + {f.review_note ? ( + + {f.review_note} + + ) : null} + + ); + } + if (f.status === 'rejected') { + return ( + + 未采纳:{f.reject_reason || '-'} + + ); + } + return -; +}; + +// 机型 / OS版本(提交反馈时的端环境;改版前历史反馈无 → -) +const renderDeviceOs = (f: Feedback) => { + const os = [f.android_version ? `Android ${f.android_version}` : null, f.rom_name] + .filter(Boolean) + .join(' · '); + if (!f.device_model && !os) return -; + return ( + + {f.device_model || '-'} + {os ? ( + + {os} + + ) : null} + + ); +}; + +// 点手机号弹出的「该用户全部反馈」抽屉列(精简版:不含操作/手机号,避免抽屉里再套抽屉) +const RECORD_COLUMNS: ColumnsType = [ + { title: '提交时间', dataIndex: 'created_at', width: 150, render: (v: string) => formatWallTime(v) }, + { title: '状态', dataIndex: 'status', width: 80, render: statusTag }, + { + title: '内容', + dataIndex: 'content', + render: (v: string) => + v ? ( + + {v} + + ) : ( + - + ), + }, + { title: '审核结果', key: 'review', width: 160, render: (_: unknown, f: Feedback) => renderReview(f) }, +]; + export default function FeedbacksPage() { // 筛选草稿:点「查询」才应用(避免输入即刷新) const [status, setStatus] = useState(); @@ -58,6 +130,19 @@ export default function FeedbacksPage() { const canReview = canDo(['operator']); + const [summary, setSummary] = useState(null); + const loadSummary = async () => { + try { + const { data } = await api.get('/admin/api/feedbacks/summary'); + setSummary(data); + } catch { + /* 统计失败不阻塞列表 */ + } + }; + useEffect(() => { + loadSummary(); + }, []); + // 审核/查看抽屉(采纳发金币 / 拒绝填原因 + 看该用户历史反馈) const [drawerFb, setDrawerFb] = useState(null); const [drawerOpen, setDrawerOpen] = useState(false); @@ -66,6 +151,10 @@ export default function FeedbacksPage() { setDrawerOpen(true); }; + // 点手机号:看该用户全部反馈 + const [recordsUser, setRecordsUser] = useState<{ userId: number; phone: string | null } | null>(null); + const openUserRecords = (f: Feedback) => setRecordsUser({ userId: f.user_id, phone: f.phone }); + const search = () => setApplied({ status, @@ -104,8 +193,41 @@ export default function FeedbacksPage() { sortBy === field ? (sortOrder === 'asc' ? 'ascend' : 'descend') : null; const columns: ColumnsType = [ - { title: 'ID', dataIndex: 'id', width: 70, sorter: true, sortOrder: sortOrderOf('id') }, - { title: '用户', dataIndex: 'user_id', width: 80 }, + { + title: '用户ID', + dataIndex: 'user_id', + width: 80, + render: (v: number) => #{v}, + }, + { + title: '手机号', + dataIndex: 'phone', + width: 130, + render: (_: unknown, f: Feedback) => + f.phone ? ( + openUserRecords(f)}>{f.phone} + ) : ( + 无手机号 + ), + }, + { + title: '用户昵称', + dataIndex: 'nickname', + width: 120, + render: (v: string | null) => v || 无昵称, + }, + { + title: '提交版本号', + dataIndex: 'app_version', + width: 100, + render: (v: string | null) => v || -, + }, + { + title: '机型 / OS版本', + key: 'device', + width: 150, + render: (_: unknown, f: Feedback) => renderDeviceOs(f), + }, { title: '内容', dataIndex: 'content', @@ -145,49 +267,20 @@ export default function FeedbacksPage() { ), }, - { - title: '状态', - dataIndex: 'status', - width: 90, - render: (s: string) => { - const m = STATUS_META[s] ?? { label: s, color: 'default' }; - return {m.label}; - }, - }, + { title: '状态', dataIndex: 'status', width: 90, render: statusTag }, { title: '审核结果', key: 'review', width: 170, - render: (_: unknown, f: Feedback) => { - if (f.status === 'adopted') { - return ( - - +{f.reward_coins ?? 0} 金币 - {f.review_note ? ( - - {f.review_note} - - ) : null} - - ); - } - if (f.status === 'rejected') { - return ( - - 未采纳:{f.reject_reason || '-'} - - ); - } - return -; - }, + render: (_: unknown, f: Feedback) => renderReview(f), }, { - title: '时间', + title: '提交时间', dataIndex: 'created_at', width: 170, sorter: true, sortOrder: sortOrderOf('created_at'), - render: (v: string) => formatUtcTime(v), + render: (v: string) => formatWallTime(v), }, { title: '操作', @@ -204,8 +297,21 @@ export default function FeedbacksPage() { return (
-

反馈工单

- +

用户反馈

+
+ + + + + + + + + + + + +