R25-1 R25-2 R25-3: 修复登录改密并收口静态缓存
这个提交包含在:
@@ -4,8 +4,6 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@300,0&display=swap"
|
|
||||||
rel="stylesheet" />
|
|
||||||
<title>海音达工作台</title>
|
<title>海音达工作台</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
+7
@@ -8,6 +8,7 @@
|
|||||||
"name": "client",
|
"name": "client",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@material-symbols/font-400": "^0.42.3",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
@@ -2145,6 +2146,12 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@material-symbols/font-400": {
|
||||||
|
"version": "0.42.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@material-symbols/font-400/-/font-400-0.42.3.tgz",
|
||||||
|
"integrity": "sha512-HWpHvDapOOkUAwO5607ixoxQHAI38qrXO+A7/y7l5cYzD6lcEsoGPh9SEh0We3//dd7H/j/NCiRua4kMgMELyw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@material-symbols/font-400": "^0.42.3",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
|
|||||||
@@ -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(
|
export function mapApiErrorMessage(
|
||||||
code: string,
|
code: string,
|
||||||
details?: Record<string, unknown>,
|
details?: Record<string, unknown>,
|
||||||
@@ -75,6 +85,15 @@ export function mapApiErrorMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (code === "VALID_001") {
|
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") {
|
if (details?.payment === "already_paid_conflict") {
|
||||||
return "该报销单已支付且付款信息冲突,请刷新后确认。";
|
return "该报销单已支付且付款信息冲突,请刷新后确认。";
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
- Extracted from Stitch MCP AI-generated Design Preview (B2B SaaS Theme)
|
- 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";
|
@import "./styles/mobile-touch.css";
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ import { Button } from "../components/ui/button";
|
|||||||
import { Input } from "../components/ui/input";
|
import { Input } from "../components/ui/input";
|
||||||
import { Label } from "../components/ui/label";
|
import { Label } from "../components/ui/label";
|
||||||
|
|
||||||
|
const PASSWORD_REQUIREMENTS = [
|
||||||
|
"至少 12 位",
|
||||||
|
"包含大写字母、小写字母和数字",
|
||||||
|
"不要使用公司名、常见顺序或过于简单的组合",
|
||||||
|
];
|
||||||
|
|
||||||
export function ChangePasswordPage() {
|
export function ChangePasswordPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { changePassword } = useAuth();
|
const { changePassword } = useAuth();
|
||||||
@@ -29,10 +35,10 @@ export function ChangePasswordPage() {
|
|||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
try {
|
try {
|
||||||
await changePassword(oldPassword, newPassword);
|
await changePassword(oldPassword, newPassword);
|
||||||
navigate("/dashboard", { replace: true });
|
navigate("/portal", { replace: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isApiError(err)) {
|
if (isApiError(err)) {
|
||||||
setError(`${err.code}: ${mapApiErrorMessage(err.code, err.details, err.message)}`);
|
setError(mapApiErrorMessage(err.code, err.details, err.message));
|
||||||
} else {
|
} else {
|
||||||
setError("修改密码失败,请稍后重试");
|
setError("修改密码失败,请稍后重试");
|
||||||
}
|
}
|
||||||
@@ -76,6 +82,16 @@ export function ChangePasswordPage() {
|
|||||||
type="password"
|
type="password"
|
||||||
value={newPassword}
|
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>
|
||||||
|
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ export function LoginPage() {
|
|||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const { login } = useAuth();
|
const { login } = useAuth();
|
||||||
|
|
||||||
const [identifier, setIdentifier] = useState("[email protected]");
|
const [identifier, setIdentifier] = useState("");
|
||||||
const [password, setPassword] = useState("EmployeePassword1234");
|
const [password, setPassword] = useState("");
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ export function LoginPage() {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isApiError(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) {
|
} else if (err instanceof TypeError) {
|
||||||
setError("无法连接后端服务,请确认 server 已启动(http://127.0.0.1:3000)。");
|
setError("无法连接后端服务,请确认 server 已启动(http://127.0.0.1:3000)。");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+24
-2
@@ -18,8 +18,30 @@ export default {
|
|||||||
border: "#e4e4e7" // zinc-200
|
border: "#e4e4e7" // zinc-200
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ["Inter", "sans-serif"],
|
sans: [
|
||||||
display: ["Inter", "sans-serif"]
|
"system-ui",
|
||||||
|
"-apple-system",
|
||||||
|
"BlinkMacSystemFont",
|
||||||
|
"Segoe UI",
|
||||||
|
"PingFang SC",
|
||||||
|
"Hiragino Sans GB",
|
||||||
|
"Microsoft YaHei",
|
||||||
|
"Noto Sans CJK SC",
|
||||||
|
"Noto Sans SC",
|
||||||
|
"sans-serif"
|
||||||
|
],
|
||||||
|
display: [
|
||||||
|
"system-ui",
|
||||||
|
"-apple-system",
|
||||||
|
"BlinkMacSystemFont",
|
||||||
|
"Segoe UI",
|
||||||
|
"PingFang SC",
|
||||||
|
"Hiragino Sans GB",
|
||||||
|
"Microsoft YaHei",
|
||||||
|
"Noto Sans CJK SC",
|
||||||
|
"Noto Sans SC",
|
||||||
|
"sans-serif"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
DEFAULT: "0.25rem",
|
DEFAULT: "0.25rem",
|
||||||
|
|||||||
@@ -32,6 +32,21 @@ server {
|
|||||||
|
|
||||||
client_max_body_size 20m;
|
client_max_body_size 20m;
|
||||||
|
|
||||||
|
location ^~ /assets/ {
|
||||||
|
proxy_hide_header Cache-Control;
|
||||||
|
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||||||
|
proxy_pass http://hyb_oa_upstream;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto https;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port 443;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_send_timeout 300s;
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
limit_req zone=oa_general burst=20 nodelay;
|
limit_req zone=oa_general burst=20 nodelay;
|
||||||
proxy_pass http://hyb_oa_upstream;
|
proxy_pass http://hyb_oa_upstream;
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户