handleTabChange(activeTab === 'wechat' ? 'phone' : 'wechat')}
+ onClick={() => handleTabChange(getCornerTargetTab())}
>
- {activeTab === 'wechat' ? renderWechatLogin() : renderPhoneForm()}
+ {renderContent()}
{/* 底部其他登录方式 */}
diff --git a/src/views/Settings/SettingsPage.js b/src/views/Settings/SettingsPage.js
index 5e0ef2ad..f0e5d3c5 100644
--- a/src/views/Settings/SettingsPage.js
+++ b/src/views/Settings/SettingsPage.js
@@ -1,5 +1,5 @@
// src/views/Settings/SettingsPage.js
-import React, { useState, useCallback } from "react";
+import React, { useState, useCallback, useEffect } from "react";
import {
Box,
VStack,
@@ -50,6 +50,8 @@ import {
Save,
RotateCcw,
TrendingUp,
+ Lock,
+ KeyRound,
} from "lucide-react";
import { WechatOutlined } from "@ant-design/icons";
import { Form, Input as AntInput, Select as AntSelect, DatePicker, ConfigProvider, Modal as AntModal, Upload, message, Button as AntButton, Space } from "antd";
@@ -268,7 +270,7 @@ const maskEmail = (email) => {
};
/**
- * 账号安全面板(手机/邮箱/微信绑定)
+ * 账号安全面板(手机/邮箱/微信绑定/密码设置)
*/
const SecurityPanel = ({
user,
@@ -278,6 +280,8 @@ const SecurityPanel = ({
setIsLoading,
onPhoneOpen,
onEmailOpen,
+ onPasswordOpen,
+ passwordStatus,
cardBg,
borderColor,
headingColor,
@@ -287,6 +291,48 @@ const SecurityPanel = ({
}) => {
return (
+ {/* 登录密码设置 */}
+
+
+
+ 登录密码
+
+
+
+
+
+
+
+
+ {passwordStatus?.hasPassword ? "已设置密码" : "未设置密码"}
+
+ {passwordStatus?.hasPassword && (
+
+ 已设置
+
+ )}
+
+
+ {passwordStatus?.hasPassword
+ ? "设置密码后可使用账号密码登录"
+ : "设置密码后可使用手机号/邮箱 + 密码登录"}
+
+
+ : }
+ onClick={onPasswordOpen}
+ variant="outline"
+ borderColor="#D4AF37"
+ color="#D4AF37"
+ bg="transparent"
+ _hover={{ bg: "whiteAlpha.100", borderColor: "#F6E5A3", color: "#F6E5A3" }}
+ >
+ {passwordStatus?.hasPassword ? "修改密码" : "设置密码"}
+
+
+
+
+
{/* 手机号绑定 */}
@@ -1107,6 +1153,50 @@ export default function SettingsPage() {
onOpen: onEmailOpen,
onClose: onEmailClose,
} = useDisclosure();
+ const {
+ isOpen: isPasswordOpen,
+ onOpen: onPasswordOpen,
+ onClose: onPasswordClose,
+ } = useDisclosure();
+
+ // 密码状态
+ const [passwordStatus, setPasswordStatus] = useState({
+ hasPassword: false,
+ isWechatUser: false,
+ });
+
+ // 密码表单状态
+ const [passwordForm, setPasswordForm] = useState({
+ currentPassword: "",
+ newPassword: "",
+ confirmPassword: "",
+ });
+
+ // 获取密码状态
+ const fetchPasswordStatus = useCallback(async () => {
+ try {
+ const res = await fetch(getApiBase() + "/api/account/password-status", {
+ method: "GET",
+ credentials: "include",
+ });
+ const data = await res.json();
+ if (res.ok && data.success) {
+ setPasswordStatus({
+ hasPassword: data.data.hasPassword,
+ isWechatUser: data.data.isWechatUser,
+ });
+ }
+ } catch (error) {
+ logger.error("SettingsPage", "fetchPasswordStatus", error);
+ }
+ }, []);
+
+ // 组件挂载时获取密码状态
+ useEffect(() => {
+ if (user) {
+ fetchPasswordStatus();
+ }
+ }, [user, fetchPasswordStatus]);
// 表单状态
const [isLoading, setIsLoading] = useState(false);
@@ -1224,6 +1314,94 @@ export default function SettingsPage() {
setSearchParams({ tab });
};
+ // 修改/设置密码
+ const handlePasswordChange = async () => {
+ const { currentPassword, newPassword, confirmPassword } = passwordForm;
+
+ // 验证新密码
+ if (!newPassword) {
+ toast({
+ title: "请输入新密码",
+ status: "warning",
+ duration: 3000,
+ isClosable: true,
+ });
+ return;
+ }
+
+ if (newPassword.length < 6) {
+ toast({
+ title: "密码至少需要6个字符",
+ status: "warning",
+ duration: 3000,
+ isClosable: true,
+ });
+ return;
+ }
+
+ if (newPassword !== confirmPassword) {
+ toast({
+ title: "两次输入的密码不一致",
+ status: "warning",
+ duration: 3000,
+ isClosable: true,
+ });
+ return;
+ }
+
+ // 如果已有密码且不是微信用户首次设置,需要验证当前密码
+ if (passwordStatus.hasPassword && !passwordStatus.isWechatUser && !currentPassword) {
+ toast({
+ title: "请输入当前密码",
+ status: "warning",
+ duration: 3000,
+ isClosable: true,
+ });
+ return;
+ }
+
+ setIsLoading(true);
+ try {
+ const res = await fetch(getApiBase() + "/api/account/change-password", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ credentials: "include",
+ body: JSON.stringify({
+ currentPassword: currentPassword || undefined,
+ newPassword,
+ isFirstSet: !passwordStatus.hasPassword,
+ }),
+ });
+ const data = await res.json();
+ if (!res.ok) throw new Error(data.error || "操作失败");
+
+ toast({
+ title: passwordStatus.hasPassword ? "密码修改成功" : "密码设置成功",
+ description: "现在可以使用密码登录了",
+ status: "success",
+ duration: 3000,
+ isClosable: true,
+ });
+
+ // 重置表单并关闭模态框
+ setPasswordForm({ currentPassword: "", newPassword: "", confirmPassword: "" });
+ onPasswordClose();
+
+ // 刷新密码状态
+ fetchPasswordStatus();
+ } catch (error) {
+ toast({
+ title: "操作失败",
+ description: error.message,
+ status: "error",
+ duration: 3000,
+ isClosable: true,
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
// 发送验证码
const sendVerificationCode = async (type) => {
setIsLoading(true);
@@ -1405,6 +1583,8 @@ export default function SettingsPage() {
setIsLoading={setIsLoading}
onPhoneOpen={onPhoneOpen}
onEmailOpen={onEmailOpen}
+ onPasswordOpen={onPasswordOpen}
+ passwordStatus={passwordStatus}
profileEvents={profileEvents}
{...commonProps}
/>
@@ -1605,6 +1785,82 @@ export default function SettingsPage() {
+
+ {/* 设置/修改密码模态框 - antd 黑金主题 */}
+ {
+ setPasswordForm({ currentPassword: "", newPassword: "", confirmPassword: "" });
+ onPasswordClose();
+ }}
+ centered
+ closeIcon={×}
+ footer={
+
+ {
+ setPasswordForm({ currentPassword: "", newPassword: "", confirmPassword: "" });
+ onPasswordClose();
+ }}
+ style={{
+ background: "transparent",
+ borderColor: "#666",
+ color: "#999",
+ }}
+ >
+ 取消
+
+
+ {passwordStatus.hasPassword ? "确认修改" : "确认设置"}
+
+
+ }
+ styles={{
+ header: { background: "#0D0D0D", borderBottom: "1px solid #D4AF37", paddingBottom: 16, color: "#D4AF37" },
+ body: { background: "#0D0D0D", padding: "24px 24px 16px" },
+ content: { background: "#0D0D0D", borderRadius: 12, border: "1px solid #D4AF37" },
+ footer: { background: "#0D0D0D", borderTop: "1px solid #333", paddingTop: 16 },
+ }}
+ >
+ 当前密码}>
+ setPasswordForm((prev) => ({ ...prev, currentPassword: e.target.value }))}
+ placeholder="请输入当前密码"
+ />
+
+ )}
+ 新密码}>
+ setPasswordForm((prev) => ({ ...prev, newPassword: e.target.value }))}
+ placeholder="请输入新密码(至少6位)"
+ />
+
+ 确认新密码}>
+ setPasswordForm((prev) => ({ ...prev, confirmPassword: e.target.value }))}
+ placeholder="请再次输入新密码"
+ />
+
+
+ 密码要求:至少6个字符,设置后可使用手机号/邮箱 + 密码登录
+
+
+
);