feat(feedback): 移除联系方式步骤,登录态用户身份已知
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

之前第三步要求填写微信/钉钉等联系方式,但用户已登录,后端已经
记录 user_id / user_name(与水印取的同一份),可以直接通过内部
渠道触达,无需再问。

流程从 4 步收紧为 3 步:
  1) 选类型
  2) 写内容 + 截图 + 板块
  3) 成功页

提交概览页一并删除(信息不变就直接提交,少一次点击)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-04-30 14:14:54 +08:00
parent 90b1266fe5
commit 90b34b681e

View File

@@ -71,11 +71,10 @@ export default function FeedbackFab({ module: moduleProp }: Props = {}) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [historyOpen, setHistoryOpen] = useState(false); const [historyOpen, setHistoryOpen] = useState(false);
const [menuOpen, setMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
const [step, setStep] = useState<1 | 2 | 3 | 4>(1); const [step, setStep] = useState<1 | 2 | 3>(1); // 1=选类型, 2=写内容, 3=成功页
const [type, setType] = useState<FeedbackType | null>(null); const [type, setType] = useState<FeedbackType | null>(null);
const [mod, setMod] = useState<string>(''); const [mod, setMod] = useState<string>('');
const [content, setContent] = useState(''); const [content, setContent] = useState('');
const [contact, setContact] = useState('');
const [shots, setShots] = useState<UploadedImg[]>([]); const [shots, setShots] = useState<UploadedImg[]>([]);
const [uploading, setUploading] = useState(false); const [uploading, setUploading] = useState(false);
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
@@ -125,7 +124,6 @@ export default function FeedbackFab({ module: moduleProp }: Props = {}) {
setStep(1); setStep(1);
setType(null); setType(null);
setContent(''); setContent('');
setContact('');
setShots([]); setShots([]);
setError(null); setError(null);
}; };
@@ -147,12 +145,11 @@ export default function FeedbackFab({ module: moduleProp }: Props = {}) {
type, type,
module: mod || null, module: mod || null,
content: content.trim(), content: content.trim(),
contact: contact.trim() || null,
screenshots: shots.map(s => s.url), screenshots: shots.map(s => s.url),
userAgent: navigator.userAgent.slice(0, 500), userAgent: navigator.userAgent.slice(0, 500),
}), }),
}); });
setStep(4); setStep(3);
} catch (e) { } catch (e) {
setError(e instanceof Error ? e.message : String(e)); setError(e instanceof Error ? e.message : String(e));
} finally { } finally {
@@ -162,21 +159,17 @@ export default function FeedbackFab({ module: moduleProp }: Props = {}) {
const next = () => { const next = () => {
if (step === 1 && !type) return; if (step === 1 && !type) return;
if (step === 2 && !content.trim()) { if (step === 2) {
taRef.current?.focus(); if (!content.trim()) { taRef.current?.focus(); return; }
return;
}
if (step === 3) {
submit(); submit();
return; return;
} }
setStep((step + 1) as typeof step);
}; };
const back = () => setStep((Math.max(1, step - 1)) as typeof step); const back = () => setStep((Math.max(1, step - 1)) as typeof step);
const canNext = step === 1 ? !!type : step === 2 ? content.trim().length > 0 : true; const canNext = step === 1 ? !!type : step === 2 ? content.trim().length > 0 : true;
const progress = ((step === 4 ? 4 : step) / 4) * 100; const progress = step >= 3 ? 100 : (step / 2) * 100;
return ( return (
<> <>
@@ -265,13 +258,12 @@ export default function FeedbackFab({ module: moduleProp }: Props = {}) {
</div> </div>
<div> <div>
<div className="text-sm font-black text-slate-800 leading-tight"> <div className="text-sm font-black text-slate-800 leading-tight">
{step === 4 ? '收到啦~' : '提个建议'} {step === 3 ? '收到啦~' : '提个建议'}
</div> </div>
<div className="text-[10px] text-slate-400 font-bold"> <div className="text-[10px] text-slate-400 font-bold">
{step === 1 && '第一步 / 共 3 步'} {step === 1 && '第一步 / 共 2 步'}
{step === 2 && '第二步 / 共 3 步'} {step === 2 && '第二步 / 共 2 步'}
{step === 3 && '第三步 / 共 3 步'} {step === 3 && '感谢你的反馈'}
{step === 4 && '感谢你的反馈'}
</div> </div>
</div> </div>
</div> </div>
@@ -411,32 +403,7 @@ export default function FeedbackFab({ module: moduleProp }: Props = {}) {
)} )}
{step === 3 && ( {step === 3 && (
<motion.div key="s3" initial={{ opacity: 0, x: 12 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -12 }} transition={{ duration: 0.2 }} className="space-y-3"> <motion.div key="s3" initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.3 }} className="text-center py-6">
<p className="text-[12px] font-bold text-slate-600">便</p>
<p className="text-[11px] text-slate-400 font-bold"></p>
<input
type="text"
value={contact}
onChange={(e) => setContact(e.target.value)}
autoFocus
maxLength={120}
placeholder="微信 / 钉钉 / 邮箱 / 手机号 都行"
className="w-full bg-slate-50 border-none rounded-xl p-3 text-[12px] text-slate-700 outline-none focus:ring-2 focus:ring-blue-500/20"
/>
<div className="bg-slate-50 rounded-xl p-3 mt-1">
<div className="text-[10px] font-bold text-slate-400 uppercase mb-1.5"></div>
<div className="text-[11px] text-slate-600 space-y-1">
<div><span className="font-bold text-slate-800">{TYPE_OPTIONS.find(t => t.key === type)?.label}</span></div>
<div><span className="font-bold text-slate-800">{MODULE_LABELS[mod] || '通用'}</span></div>
<div className="break-words"><span className="font-bold text-slate-800 line-clamp-3">{content}</span></div>
</div>
</div>
{error && <div className="text-[11px] text-rose-500 font-bold">{error}</div>}
</motion.div>
)}
{step === 4 && (
<motion.div key="s4" initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.3 }} className="text-center py-6">
<motion.div <motion.div
initial={{ scale: 0, rotate: -180 }} initial={{ scale: 0, rotate: -180 }}
animate={{ scale: 1, rotate: 0 }} animate={{ scale: 1, rotate: 0 }}
@@ -446,14 +413,14 @@ export default function FeedbackFab({ module: moduleProp }: Props = {}) {
<Check size={28} strokeWidth={3} className="text-white" /> <Check size={28} strokeWidth={3} className="text-white" />
</motion.div> </motion.div>
<div className="text-base font-black text-slate-800 mb-1"> </div> <div className="text-base font-black text-slate-800 mb-1"> </div>
<div className="text-[12px] text-slate-500 font-bold leading-relaxed"><br /></div> <div className="text-[12px] text-slate-500 font-bold leading-relaxed"><br /></div>
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>
</div> </div>
{/* Footer */} {/* Footer */}
{step < 4 && ( {step < 3 && (
<div className="px-4 py-3 border-t border-slate-100 flex items-center gap-2"> <div className="px-4 py-3 border-t border-slate-100 flex items-center gap-2">
{step > 1 && ( {step > 1 && (
<button <button
@@ -469,12 +436,12 @@ export default function FeedbackFab({ module: moduleProp }: Props = {}) {
disabled={!canNext || submitting} disabled={!canNext || submitting}
className="px-4 py-2 rounded-xl text-[12px] font-bold bg-blue-600 text-white shadow shadow-blue-100 disabled:bg-slate-200 disabled:text-slate-400 disabled:shadow-none flex items-center gap-1" className="px-4 py-2 rounded-xl text-[12px] font-bold bg-blue-600 text-white shadow shadow-blue-100 disabled:bg-slate-200 disabled:text-slate-400 disabled:shadow-none flex items-center gap-1"
> >
{submitting ? '提交中…' : step === 3 ? '提交' : '下一步'} {submitting ? '提交中…' : step === 2 ? '提交' : '下一步'}
{!submitting && step !== 3 && <ChevronRight size={14} />} {!submitting && step !== 2 && <ChevronRight size={14} />}
</button> </button>
</div> </div>
)} )}
{step === 4 && ( {step === 3 && (
<div className="px-4 py-3 border-t border-slate-100"> <div className="px-4 py-3 border-t border-slate-100">
<button <button
onClick={close} onClick={close}