R25-1 R25-2 R25-3: 修复登录改密并收口静态缓存
这个提交包含在:
@@ -1,3 +1,13 @@
|
||||
function readStringList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return value
|
||||
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
||||
.filter((item) => item.length > 0);
|
||||
}
|
||||
|
||||
export function mapApiErrorMessage(
|
||||
code: string,
|
||||
details?: Record<string, unknown>,
|
||||
@@ -75,6 +85,15 @@ export function mapApiErrorMessage(
|
||||
}
|
||||
|
||||
if (code === "VALID_001") {
|
||||
const passwordErrors = readStringList(details?.password);
|
||||
if (passwordErrors.length) {
|
||||
return `新密码不符合要求:${passwordErrors.join(";")}。`;
|
||||
}
|
||||
|
||||
if (details?.["x-device-fingerprint"] === "missing") {
|
||||
return "当前设备校验信息缺失,请刷新页面后重试。";
|
||||
}
|
||||
|
||||
if (details?.payment === "already_paid_conflict") {
|
||||
return "该报销单已支付且付款信息冲突,请刷新后确认。";
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
- Extracted from Stitch MCP AI-generated Design Preview (B2B SaaS Theme)
|
||||
*/
|
||||
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap");
|
||||
@import "@material-symbols/font-400/outlined.css";
|
||||
@import "./styles/mobile-touch.css";
|
||||
|
||||
@tailwind base;
|
||||
|
||||
@@ -7,6 +7,12 @@ import { Button } from "../components/ui/button";
|
||||
import { Input } from "../components/ui/input";
|
||||
import { Label } from "../components/ui/label";
|
||||
|
||||
const PASSWORD_REQUIREMENTS = [
|
||||
"至少 12 位",
|
||||
"包含大写字母、小写字母和数字",
|
||||
"不要使用公司名、常见顺序或过于简单的组合",
|
||||
];
|
||||
|
||||
export function ChangePasswordPage() {
|
||||
const navigate = useNavigate();
|
||||
const { changePassword } = useAuth();
|
||||
@@ -29,10 +35,10 @@ export function ChangePasswordPage() {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await changePassword(oldPassword, newPassword);
|
||||
navigate("/dashboard", { replace: true });
|
||||
navigate("/portal", { replace: true });
|
||||
} catch (err) {
|
||||
if (isApiError(err)) {
|
||||
setError(`${err.code}: ${mapApiErrorMessage(err.code, err.details, err.message)}`);
|
||||
setError(mapApiErrorMessage(err.code, err.details, err.message));
|
||||
} else {
|
||||
setError("修改密码失败,请稍后重试");
|
||||
}
|
||||
@@ -76,6 +82,16 @@ export function ChangePasswordPage() {
|
||||
type="password"
|
||||
value={newPassword}
|
||||
/>
|
||||
<p className="text-xs text-zinc-500">密码要求:{PASSWORD_REQUIREMENTS.join(";")}。</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-xs text-zinc-600">
|
||||
<p className="font-medium text-zinc-700">建议的新密码规则</p>
|
||||
<ul className="mt-1 list-disc space-y-1 pl-4">
|
||||
{PASSWORD_REQUIREMENTS.map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
|
||||
@@ -27,8 +27,8 @@ export function LoginPage() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const { login } = useAuth();
|
||||
|
||||
const [identifier, setIdentifier] = useState("[email protected]");
|
||||
const [password, setPassword] = useState("EmployeePassword1234");
|
||||
const [identifier, setIdentifier] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
@@ -60,7 +60,7 @@ export function LoginPage() {
|
||||
}
|
||||
} catch (err) {
|
||||
if (isApiError(err)) {
|
||||
setError(`${err.code}: ${mapApiErrorMessage(err.code, err.details, err.message)}`);
|
||||
setError(mapApiErrorMessage(err.code, err.details, err.message));
|
||||
} else if (err instanceof TypeError) {
|
||||
setError("无法连接后端服务,请确认 server 已启动(http://127.0.0.1:3000)。");
|
||||
} else {
|
||||
|
||||
在新工单中引用
屏蔽一个用户