All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m20s
v0.9.5 — 账号安全管控 + 内容资产页: - 首次登录强制改密(must_change_password + ForceChangePasswordModal) - 并发会话限制(ActiveSession + SessionJWT认证,可配置桌面/移动端会话数) - Token生命周期缩短(access 30min, refresh 1天) - 登录IP记录(LoginRecord模型,为异常检测打基础) - 内容资产页(超管三级折叠/团队管两级折叠,按需懒加载) v0.9.6 — UI修缮: - 侧栏导航排序(内容资产移到用户管理下方) - 视频网格高度调整(440px,3行+暗示可滚动) - 秒数单位统一(不再换算为分钟/小时) - 提示词标签溢出修复 + 弹窗方向自适应 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
98 lines
3.4 KiB
TypeScript
98 lines
3.4 KiB
TypeScript
import { useState, useCallback } from 'react';
|
|
import { useAuthStore } from '../store/auth';
|
|
import { authApi } from '../lib/api';
|
|
import logoImg from '../assets/logo_32.png';
|
|
import styles from './ForceChangePasswordModal.module.css';
|
|
|
|
interface Props {
|
|
onSuccess: () => void;
|
|
}
|
|
|
|
export function ForceChangePasswordModal({ onSuccess }: Props) {
|
|
const clearMustChangePassword = useAuthStore((s) => s.clearMustChangePassword);
|
|
const [oldPassword, setOldPassword] = useState('');
|
|
const [newPassword, setNewPassword] = useState('');
|
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
const [error, setError] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const handleSubmit = useCallback(async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setError('');
|
|
|
|
if (!oldPassword) { setError('请输入当前密码'); return; }
|
|
if (newPassword.length < 8) { setError('新密码至少8位'); return; }
|
|
if (newPassword !== confirmPassword) { setError('两次输入的新密码不一致'); return; }
|
|
if (oldPassword === newPassword) { setError('新密码不能与当前密码相同'); return; }
|
|
|
|
setLoading(true);
|
|
try {
|
|
await authApi.changePassword(oldPassword, newPassword);
|
|
clearMustChangePassword();
|
|
onSuccess();
|
|
} catch (err: any) {
|
|
const msg = err.response?.data?.message || err.response?.data?.error || '密码修改失败,请重试';
|
|
setError(msg);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [oldPassword, newPassword, confirmPassword, clearMustChangePassword, onSuccess]);
|
|
|
|
return (
|
|
<div className={styles.overlay}>
|
|
<div className={styles.panel}>
|
|
<div className={styles.header}>
|
|
<img src={logoImg} alt="" className={styles.headerLogo} />
|
|
<span className={styles.headerTitle}>Air Drama</span>
|
|
</div>
|
|
|
|
<p className={styles.notice}>
|
|
首次登录请修改密码后继续使用
|
|
</p>
|
|
|
|
<form onSubmit={handleSubmit} className={styles.form}>
|
|
<div className={styles.field}>
|
|
<label className={styles.label}>当前密码</label>
|
|
<input
|
|
type="password"
|
|
className={styles.input}
|
|
value={oldPassword}
|
|
onChange={(e) => setOldPassword(e.target.value)}
|
|
placeholder="请输入当前密码"
|
|
autoFocus
|
|
/>
|
|
</div>
|
|
|
|
<div className={styles.field}>
|
|
<label className={styles.label}>新密码</label>
|
|
<input
|
|
type="password"
|
|
className={styles.input}
|
|
value={newPassword}
|
|
onChange={(e) => setNewPassword(e.target.value)}
|
|
placeholder="至少8位"
|
|
/>
|
|
</div>
|
|
|
|
<div className={styles.field}>
|
|
<label className={styles.label}>确认新密码</label>
|
|
<input
|
|
type="password"
|
|
className={styles.input}
|
|
value={confirmPassword}
|
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
placeholder="再次输入新密码"
|
|
/>
|
|
</div>
|
|
|
|
{error && <div className={styles.error}>{error}</div>}
|
|
|
|
<button type="submit" className={styles.submitBtn} disabled={loading}>
|
|
{loading ? '修改中...' : '修改密码'}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|