feat: [lock:] markdown 锁定(login/reply/password 真锁,服务端剥离 + 解锁接口 + SSR 打码)
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
/**
|
||||
* markdown 锁定块占位(未解锁时渲染,MD3 风格):
|
||||
* - login → 「登录后查看」+ 去登录按钮
|
||||
* - reply → 「评论后可见」+ 去评论按钮(onGoComment 滚动到评论区)
|
||||
* - password → 密码输入 + 解锁按钮(onUnlock(index, password),失败显示错误态)
|
||||
* 键盘:密码框 Enter 提交、Esc 清空;Tab 自然可达。
|
||||
*/
|
||||
export default function LockBlock({ index, type = 'password', onUnlock, onGoComment }) {
|
||||
const [pwd, setPwd] = useState('');
|
||||
const [err, setErr] = useState('');
|
||||
const [busy, setBusy] = useState(false);
|
||||
const pwdRef = useRef(null);
|
||||
|
||||
// 弹窗/焦点管理不适用这里(块内内联控件),但复用 useDialog 的 Esc 习惯:直接监听
|
||||
useEffect(() => {
|
||||
if (type === 'password') {
|
||||
const h = (e) => {
|
||||
if (e.key === 'Escape') { setPwd(''); setErr(''); }
|
||||
};
|
||||
window.addEventListener('keydown', h);
|
||||
return () => window.removeEventListener('keydown', h);
|
||||
}
|
||||
return undefined;
|
||||
}, [type]);
|
||||
|
||||
const tryUnlock = async () => {
|
||||
if (busy) return;
|
||||
if (!pwd) { setErr('请输入密码'); if (pwdRef.current) pwdRef.current.focus(); return; }
|
||||
setBusy(true);
|
||||
setErr('');
|
||||
try {
|
||||
await onUnlock(index, pwd);
|
||||
// 成功后父级重渲染(unlocked/lockContent 更新),本组件被内容替换
|
||||
} catch (e) {
|
||||
setErr((e && e.message) || '密码错误');
|
||||
}
|
||||
setBusy(false);
|
||||
};
|
||||
|
||||
if (type === 'login') {
|
||||
return (
|
||||
<div className="lock-placeholder">
|
||||
<span className="material-icons lock-icon" aria-hidden="true">lock</span>
|
||||
<div className="lock-title">登录后查看</div>
|
||||
<div className="lock-desc">登录后可查看此内容</div>
|
||||
<Link to="/login.html" className="btn btn-filled btn-sm">去登录</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'reply') {
|
||||
return (
|
||||
<div className="lock-placeholder">
|
||||
<span className="material-icons lock-icon" aria-hidden="true">lock</span>
|
||||
<div className="lock-title">评论后可见</div>
|
||||
<div className="lock-desc">发表评论后即可查看此内容</div>
|
||||
<button type="button" className="btn btn-filled btn-sm" onClick={onGoComment}>去评论</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// password
|
||||
return (
|
||||
<div className="lock-placeholder">
|
||||
<span className="material-icons lock-icon" aria-hidden="true">lock</span>
|
||||
<div className="lock-title">密码可见</div>
|
||||
<div className="lock-desc">输入访问密码解锁内容</div>
|
||||
<div className="lock-form">
|
||||
<input
|
||||
ref={pwdRef}
|
||||
type="password"
|
||||
className={'lock-pwd-input' + (err ? ' error' : '')}
|
||||
value={pwd}
|
||||
placeholder="访问密码"
|
||||
aria-label="访问密码"
|
||||
autoComplete="off"
|
||||
onChange={(e) => { setPwd(e.target.value); if (err) setErr(''); }}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') tryUnlock();
|
||||
else if (e.key === 'Escape') { setPwd(''); setErr(''); }
|
||||
}}
|
||||
/>
|
||||
<button type="button" className="btn btn-filled btn-sm" onClick={tryUnlock} disabled={busy}>
|
||||
{busy ? '解锁中…' : '解锁'}
|
||||
</button>
|
||||
</div>
|
||||
{err && <div className="lock-error" role="alert"><span className="material-icons" style={{ fontSize: 14 }}>error_outline</span> {err}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user