feat(ui): Redesign user profile page with editable fields

- Add profile editing with avatar, nickname, email, phone fields
    - Add identity providers section for OAuth account binding
    - Add account security section with password change
    - Add new i18n translations for profile and auth pages
    - Update vbase.js with improved error handling and user info refresh
    - Include ico component in default layout
master
veypi 4 weeks ago
parent 78ae8440ef
commit d715445cc0

@ -1,350 +1,354 @@
{ {
"en-US": { "en-US": {
"auth.account": "Username/Email",
"auth.back_to_login": "Back to Login",
"auth.bind_account": "Bind Account",
"auth.bind_exist_desc": "Bind your {provider} to an existing account",
"auth.bind_existing": "Bind Existing",
"auth.bind_login_tip": "To keep connected with us please login with your personal info",
"auth.bind_new_desc": "Create a new account to bind with {provider}",
"auth.bind_register_tip": "Enter your personal details and start journey with us",
"auth.bind_success": "Binding successful",
"auth.change_password": "Change Password",
"auth.code_login": "Code",
"auth.code_sent": "Code sent",
"auth.confirm_password": "Confirm Password",
"auth.create_account": "Create Account",
"auth.create_new": "Create New",
"auth.email": "Email", "auth.email": "Email",
"auth.email_disabled": "Email login is disabled",
"auth.email_or_phone": "Email or Phone",
"auth.fill_all_fields": "Please fill in all required fields",
"auth.fill_required": "Please fill in all required fields",
"auth.forgot_password": "Forgot password?",
"auth.have_account": "Already have an account?",
"auth.hello_friend": "Hello, Friend!",
"auth.keep_connected": "Keep connected with your personal info",
"auth.linked_accounts": "Linked Accounts",
"auth.login": "Login", "auth.login": "Login",
"auth.login_bind": "Login \u0026 Bind",
"auth.login_failed": "Login failed",
"auth.login_success": "Login successful",
"auth.logout": "Logout", "auth.logout": "Logout",
"auth.new_password": "New Password",
"auth.no_account": "Don't have an account?",
"auth.oauth_failed": "OAuth login failed",
"auth.old_password": "Current Password",
"auth.password": "Password", "auth.password": "Password",
"auth.password_changed": "Password changed successfully",
"auth.password_too_short": "Password must be at least 8 characters",
"auth.passwords_not_match": "Passwords do not match",
"auth.phone_placeholder": "Enter your phone number",
"auth.register": "Register", "auth.register": "Register",
"auth.register_failed": "Registration failed",
"auth.register_success": "Registration successful",
"auth.security": "Security",
"auth.sign_up_bind": "Sign Up \u0026 Bind",
"auth.sms_disabled": "SMS login is disabled",
"auth.start_journey": "Enter your personal details and start your journey",
"auth.target_and_code_required": "Email/Phone and Code are required",
"auth.target_required": "Email or Phone is required",
"auth.unbind_confirm": "Are you sure you want to unbind this account?",
"auth.use_account": "or use your account",
"auth.use_info_register": "or use your info to register",
"auth.username": "Username", "auth.username": "Username",
"auth.confirm_password": "Confirm Password", "auth.username_login": "Username",
"auth.username_or_email": "Username or Email", "auth.username_or_email": "Username or Email",
"auth.email_or_phone": "Email or Phone", "auth.username_required": "Username is required",
"auth.phone": "Phone Number", "auth.username_too_short": "Username must be at least 3 characters",
"auth.phone_placeholder": "Enter your phone number",
"auth.verification_code": "Verification Code", "auth.verification_code": "Verification Code",
"auth.send_code": "Send Code",
"auth.code_sent": "Code sent",
"auth.code_login_not_supported": "Code login is not supported yet",
"auth.target_required": "Email or Phone is required",
"auth.target_and_code_required": "Email/Phone and Code are required",
"auth.email_disabled": "Email login is disabled",
"auth.sms_disabled": "SMS login is disabled",
"auth.username_login": "Username",
"auth.code_login": "Code",
"auth.create_account": "Create Account",
"auth.use_info_register": "or use your info to register",
"auth.use_account": "or use your account",
"auth.welcome_back": "Welcome Back!", "auth.welcome_back": "Welcome Back!",
"auth.keep_connected": "Keep connected with your personal info",
"auth.hello_friend": "Hello, Friend!",
"auth.start_journey": "Enter your personal details and start your journey",
"auth.have_account": "Already have an account?",
"auth.no_account": "Don't have an account?",
"auth.forgot_password": "Forgot password?",
"auth.fill_all_fields": "Please fill in all required fields",
"auth.username_too_short": "Username must be at least 3 characters",
"auth.password_too_short": "Password must be at least 8 characters",
"auth.passwords_not_match": "Passwords do not match",
"auth.login_success": "Login successful",
"auth.login_failed": "Login failed",
"auth.register_success": "Registration successful",
"auth.register_failed": "Registration failed",
"auth.invalid_response": "Invalid server response",
"auth.oauth_not_ready": "{provider} login is not ready yet",
"auth.logging_in": "Logging in...",
"auth.back_to_login": "Back to Login",
"auth.bind_account": "Bind Account",
"auth.bind_new_desc": "Create a new account to bind with {provider}",
"auth.sign_up_bind": "Sign Up & Bind",
"auth.bind_existing": "Bind Existing",
"auth.bind_exist_desc": "Bind your {provider} to an existing account",
"auth.account": "Username/Email",
"auth.login_bind": "Login & Bind",
"auth.bind_login_tip": "To keep connected with us please login with your personal info",
"auth.bind_register_tip": "Enter your personal details and start journey with us",
"auth.bind_success": "Binding successful",
"auth.fill_required": "Please fill in all required fields",
"auth.username_required": "Username is required",
"common.processing": "Processing...",
"common.actions": "Actions", "common.actions": "Actions",
"common.back": "Back", "common.bind": "Bind",
"common.cancel": "Cancel", "common.cancel": "Cancel",
"common.confirm": "Confirm",
"common.create": "Create", "common.create": "Create",
"common.delete": "Delete", "common.delete": "Delete",
"common.edit": "Edit", "common.edit": "Edit",
"common.forbidden": "Forbidden", "common.forbidden": "Forbidden",
"common.loading": "Loading...", "common.id": "ID",
"common.not_found": "Not Found", "common.not_found": "Not Found",
"common.processing": "Processing...",
"common.reset": "Reset",
"common.save": "Save", "common.save": "Save",
"common.save_success": "Saved successfully",
"common.saving": "Saving...", "common.saving": "Saving...",
"common.reset": "Reset", "common.success": "Success",
"common.status": "Status", "common.unbind": "Unbind",
"nav.dashboard": "Dashboard", "nav.dashboard": "Dashboard",
"nav.home": "Home", "nav.home": "Home",
"nav.oauth": "OAuth Apps", "nav.oauth": "OAuth Apps",
"nav.oauth_providers": "Identity Providers",
"nav.profile": "Profile",
"nav.roles": "Roles", "nav.roles": "Roles",
"nav.settings": "Settings", "nav.settings": "Settings",
"nav.oauth_providers": "Identity Providers", "nav.users": "Users",
"oauth.provider.code": "Code",
"oauth.provider.name": "Name",
"oauth.provider.client_id": "Client ID", "oauth.provider.client_id": "Client ID",
"oauth.provider.client_secret": "Client Secret", "oauth.provider.client_secret": "Client Secret",
"oauth.provider.redirect_uri": "Redirect URI", "oauth.provider.code": "Code",
"oauth.provider.auth_url": "Auth URL",
"oauth.provider.token_url": "Token URL",
"oauth.provider.user_info_url": "User Info URL",
"oauth.provider.scope": "Scopes",
"oauth.provider.enabled": "Enabled",
"oauth.provider.create": "Add Provider", "oauth.provider.create": "Add Provider",
"oauth.provider.edit": "Edit Provider", "oauth.provider.edit": "Edit Provider",
"oauth.provider.delete_confirm": "Delete this provider?", "oauth.provider.enabled": "Enabled",
"oauth.provider.templates": "Templates", "oauth.provider.name": "Name",
"nav.profile": "Profile", "oauth.provider.redirect_uri": "Redirect URI",
"nav.users": "Users",
"user.email": "Email",
"user.profile": "User Profile",
"user.role": "Role",
"user.username": "Username",
"role.name": "Role Name",
"role.code": "Role Code", "role.code": "Role Code",
"role.description": "Description",
"role.create": "Create Role", "role.create": "Create Role",
"role.edit": "Edit Role",
"role.delete_confirm": "Are you sure you want to delete this role?", "role.delete_confirm": "Are you sure you want to delete this role?",
"role.description": "Description",
"role.edit": "Edit Role",
"role.name": "Role Name",
"role.search_placeholder": "Search roles...", "role.search_placeholder": "Search roles...",
"settings.category.app": "Application",
"settings.category.auth": "Authentication",
"settings.category.security": "Security",
"settings.category.code": "Verification Code",
"settings.category.email": "Email",
"settings.category.sms": "SMS",
"settings.app.name": "App Name",
"settings.app.name_desc": "Display name of the application",
"settings.app.id": "App ID", "settings.app.id": "App ID",
"settings.app.id_desc": "Unique identifier for the application", "settings.app.id_desc": "Unique identifier for the application",
"settings.auth.reg_require_email": "Require Email for Registration", "settings.app.name": "App Name",
"settings.auth.reg_require_email_desc": "Make email mandatory during registration", "settings.app.name_desc": "Display name of the application",
"settings.auth.reg_require_phone": "Require Phone for Registration",
"settings.auth.reg_require_phone_desc": "Make phone number mandatory during registration",
"settings.auth.login_methods": "Login Methods", "settings.auth.login_methods": "Login Methods",
"settings.auth.login_methods_desc": "Available login methods (JSON array)", "settings.auth.login_methods_desc": "Available login methods (JSON array)",
"settings.auth.password_fields": "Password Login Fields", "settings.auth.password_fields": "Password Login Fields",
"settings.auth.password_fields_desc": "Fields allowed for password login", "settings.auth.password_fields_desc": "Fields allowed for password login",
"settings.security.captcha_enabled": "Enable Captcha", "settings.auth.reg_require_email": "Require Email for Registration",
"settings.security.captcha_enabled_desc": "Show captcha verification on login", "settings.auth.reg_require_email_desc": "Make email mandatory during registration",
"settings.security.max_login_attempts": "Max Login Attempts", "settings.auth.reg_require_phone": "Require Phone for Registration",
"settings.security.max_login_attempts_desc": "Maximum failed login attempts before lockout", "settings.auth.reg_require_phone_desc": "Make phone number mandatory during registration",
"settings.security.bcrypt_cost": "Bcrypt Cost", "settings.category.app": "Application",
"settings.security.bcrypt_cost_desc": "Password hashing strength (4-31)", "settings.category.auth": "Authentication",
"settings.category.code": "Verification Code",
"settings.category.email": "Email",
"settings.category.security": "Security",
"settings.category.sms": "SMS",
"settings.code.expiry": "Code Expiry", "settings.code.expiry": "Code Expiry",
"settings.code.expiry_desc": "Verification code validity period (minutes)", "settings.code.expiry_desc": "Verification code validity period (minutes)",
"settings.code.length": "Code Length", "settings.code.length": "Code Length",
"settings.code.length_desc": "Number of digits in verification code", "settings.code.length_desc": "Number of digits in verification code",
"settings.code.max_attempt": "Max Attempts", "settings.code.max_attempt": "Max Attempts",
"settings.code.max_attempt_desc": "Maximum verification code attempts", "settings.code.max_attempt_desc": "Maximum verification code attempts",
"settings.code.send_interval": "Send Interval",
"settings.code.send_interval_desc": "Minimum seconds between code sends",
"settings.code.max_daily_count": "Max Daily Count", "settings.code.max_daily_count": "Max Daily Count",
"settings.code.max_daily_count_desc": "Maximum daily send count (0 to disable, -1 for unlimited)", "settings.code.max_daily_count_desc": "Maximum daily send count (0 to disable, -1 for unlimited)",
"settings.code.send_interval": "Send Interval",
"settings.code.send_interval_desc": "Minimum seconds between code sends",
"settings.email.enabled": "Enable Email", "settings.email.enabled": "Enable Email",
"settings.email.enabled_desc": "Enable email service for notifications", "settings.email.enabled_desc": "Enable email service for notifications",
"settings.email.from": "From Email",
"settings.email.from_desc": "Sender email address",
"settings.email.from_name": "From Name",
"settings.email.from_name_desc": "Sender display name",
"settings.email.provider": "Email Provider", "settings.email.provider": "Email Provider",
"settings.email.provider_desc": "Email service provider", "settings.email.provider_desc": "Email service provider",
"settings.email.smtp_host": "SMTP Host", "settings.email.smtp_host": "SMTP Host",
"settings.email.smtp_host_desc": "SMTP server hostname", "settings.email.smtp_host_desc": "SMTP server hostname",
"settings.email.smtp_pass": "SMTP Password",
"settings.email.smtp_pass_desc": "SMTP authentication password",
"settings.email.smtp_port": "SMTP Port", "settings.email.smtp_port": "SMTP Port",
"settings.email.smtp_port_desc": "SMTP server port", "settings.email.smtp_port_desc": "SMTP server port",
"settings.email.smtp_user": "SMTP Username", "settings.email.smtp_user": "SMTP Username",
"settings.email.smtp_user_desc": "SMTP authentication username", "settings.email.smtp_user_desc": "SMTP authentication username",
"settings.email.smtp_pass": "SMTP Password", "settings.load_failed": "Failed to load settings",
"settings.email.smtp_pass_desc": "SMTP authentication password", "settings.no_changes": "No changes to save",
"settings.email.from": "From Email", "settings.reset_done": "Settings reset to original values",
"settings.email.from_desc": "Sender email address", "settings.save_failed": "Failed to save settings",
"settings.email.from_name": "From Name", "settings.save_success": "Settings saved successfully",
"settings.email.from_name_desc": "Sender display name", "settings.security.bcrypt_cost": "Bcrypt Cost",
"settings.security.bcrypt_cost_desc": "Password hashing strength (4-31)",
"settings.security.captcha_enabled": "Enable Captcha",
"settings.security.captcha_enabled_desc": "Show captcha verification on login",
"settings.security.max_login_attempts": "Max Login Attempts",
"settings.security.max_login_attempts_desc": "Maximum failed login attempts before lockout",
"settings.sms.access_key": "Access Key",
"settings.sms.access_key_desc": "API access key ID",
"settings.sms.access_secret": "Access Secret",
"settings.sms.access_secret_desc": "API access key secret",
"settings.sms.enabled": "Enable SMS", "settings.sms.enabled": "Enable SMS",
"settings.sms.enabled_desc": "Enable SMS service for verification codes", "settings.sms.enabled_desc": "Enable SMS service for verification codes",
"settings.sms.provider": "SMS Provider", "settings.sms.provider": "SMS Provider",
"settings.sms.provider_desc": "SMS service provider",
"settings.sms.provider_aliyun": "Alibaba Cloud", "settings.sms.provider_aliyun": "Alibaba Cloud",
"settings.sms.provider_desc": "SMS service provider",
"settings.sms.provider_tencent": "Tencent Cloud", "settings.sms.provider_tencent": "Tencent Cloud",
"settings.sms.access_key": "Access Key",
"settings.sms.access_key_desc": "API access key ID",
"settings.sms.access_secret": "Access Secret",
"settings.sms.access_secret_desc": "API access key secret",
"settings.sms.sign_name": "SMS Sign Name", "settings.sms.sign_name": "SMS Sign Name",
"settings.sms.sign_name_desc": "Registered SMS signature", "settings.sms.sign_name_desc": "Registered SMS signature",
"settings.sms.template_code": "Template Code", "settings.sms.template_code": "Template Code",
"settings.sms.template_code_desc": "Verification code template ID", "settings.sms.template_code_desc": "Verification code template ID",
"settings.load_failed": "Failed to load settings", "user.avatar_url": "Avatar URL",
"settings.save_success": "Settings saved successfully", "user.create": "Create User",
"settings.save_failed": "Failed to save settings", "user.edit": "Edit User",
"settings.reset_done": "Settings reset to original values", "user.email": "Email",
"settings.no_changes": "No changes to save" "user.info": "Basic Information",
"user.nickname": "Nickname",
"user.phone": "Phone",
"user.profile": "User Profile",
"user.search_placeholder": "Search users...",
"user.username": "Username"
}, },
"zh-CN": { "zh-CN": {
"auth.email": "邮箱", "auth.account": "用户名/邮箱",
"auth.login": "登录", "auth.back_to_login": "返回登录",
"auth.logout": "登出", "auth.bind_account": "绑定账号",
"auth.password": "密码", "auth.bind_exist_desc": "将 {provider} 绑定到现有账号",
"auth.register": "注册", "auth.bind_existing": "绑定已有账号",
"auth.username": "用户名", "auth.bind_login_tip": "请登录您的个人账号以保持连接",
"auth.confirm_password": "确认密码", "auth.bind_new_desc": "创建新账号并绑定 {provider}",
"auth.username_or_email": "用户名或邮箱", "auth.bind_register_tip": "输入您的个人信息,开启您的旅程",
"auth.email_or_phone": "邮箱或手机号", "auth.bind_success": "绑定成功",
"auth.phone": "手机号", "auth.change_password": "修改密码",
"auth.phone_placeholder": "请输入手机号",
"auth.verification_code": "验证码",
"auth.send_code": "发送验证码",
"auth.code_sent": "验证码已发送",
"auth.code_login_not_supported": "验证码登录暂不支持",
"auth.target_required": "请输入邮箱或手机号",
"auth.target_and_code_required": "请输入邮箱/手机号和验证码",
"auth.email_disabled": "邮箱登录未启用",
"auth.sms_disabled": "手机号登录未启用",
"auth.username_login": "用户名",
"auth.code_login": "验证码", "auth.code_login": "验证码",
"auth.code_sent": "验证码已发送",
"auth.confirm_password": "确认密码",
"auth.create_account": "创建账户", "auth.create_account": "创建账户",
"auth.use_info_register": "或使用您的信息进行注册", "auth.email": "邮箱",
"auth.use_account": "或使用您的账户", "auth.email_disabled": "邮箱登录未启用",
"auth.welcome_back": "欢迎回来!", "auth.email_or_phone": "邮箱或手机号",
"auth.keep_connected": "请使用您的个人信息登录,保持连接", "auth.fill_all_fields": "请填写所有必填字段",
"auth.hello_friend": "你好,朋友!", "auth.fill_required": "请填写所有必填项",
"auth.start_journey": "输入您的个人信息,开始您的旅程", "auth.forgot_password": "忘记密码?",
"auth.have_account": "已有账户?", "auth.have_account": "已有账户?",
"auth.hello_friend": "你好,朋友!",
"auth.keep_connected": "请使用您的个人信息登录,保持连接",
"auth.linked_accounts": "账号绑定",
"auth.login": "登录",
"auth.login_bind": "登录并绑定",
"auth.login_failed": "登录失败",
"auth.login_success": "登录成功",
"auth.logout": "登出",
"auth.new_password": "新密码",
"auth.no_account": "还没有账户?", "auth.no_account": "还没有账户?",
"auth.forgot_password": "忘记密码?", "auth.old_password": "当前密码",
"auth.fill_all_fields": "请填写所有必填字段", "auth.password": "密码",
"auth.username_too_short": "用户名至少3个字符", "auth.password_changed": "密码修改成功",
"auth.password_too_short": "密码至少8个字符", "auth.password_too_short": "密码至少8个字符",
"auth.passwords_not_match": "两次输入的密码不一致", "auth.passwords_not_match": "两次输入的密码不一致",
"auth.login_success": "登录成功", "auth.phone_placeholder": "请输入手机号",
"auth.login_failed": "登录失败", "auth.register": "注册",
"auth.register_success": "注册成功",
"auth.register_failed": "注册失败", "auth.register_failed": "注册失败",
"auth.invalid_response": "服务器响应异常", "auth.register_success": "注册成功",
"auth.oauth_not_ready": "{provider} 登录尚未就绪", "auth.security": "安全设置",
"auth.logging_in": "登录中...",
"auth.back_to_login": "返回登录",
"auth.bind_account": "绑定账号",
"auth.bind_new_desc": "创建新账号并绑定 {provider}",
"auth.sign_up_bind": "注册并绑定", "auth.sign_up_bind": "注册并绑定",
"auth.bind_existing": "绑定已有账号", "auth.sms_disabled": "手机号登录未启用",
"auth.bind_exist_desc": "将 {provider} 绑定到现有账号", "auth.start_journey": "输入您的个人信息,开始您的旅程",
"auth.account": "用户名/邮箱", "auth.target_and_code_required": "请输入邮箱/手机号和验证码",
"auth.login_bind": "登录并绑定", "auth.target_required": "请输入邮箱或手机号",
"auth.bind_login_tip": "请登录您的个人账号以保持连接", "auth.unbind_confirm": "确定要解绑该账号吗?",
"auth.bind_register_tip": "输入您的个人信息,开启您的旅程", "auth.use_account": "或使用您的账户",
"auth.bind_success": "绑定成功", "auth.use_info_register": "或使用您的信息进行注册",
"auth.fill_required": "请填写所有必填项", "auth.username": "用户名",
"auth.username_login": "用户名",
"auth.username_or_email": "用户名或邮箱",
"auth.username_required": "用户名必填", "auth.username_required": "用户名必填",
"common.processing": "处理中...", "auth.username_too_short": "用户名至少3个字符",
"auth.verification_code": "验证码",
"auth.welcome_back": "欢迎回来!",
"common.actions": "操作", "common.actions": "操作",
"common.back": "返回",
"common.cancel": "取消", "common.cancel": "取消",
"common.confirm": "确认",
"common.create": "创建", "common.create": "创建",
"common.delete": "删除", "common.delete": "删除",
"common.edit": "编辑", "common.edit": "编辑",
"common.forbidden": "禁止访问", "common.forbidden": "禁止访问",
"common.loading": "加载中...",
"common.not_found": "页面未找到", "common.not_found": "页面未找到",
"common.processing": "处理中...",
"common.reset": "重置",
"common.save": "保存", "common.save": "保存",
"common.save_success": "保存成功",
"common.saving": "保存中...", "common.saving": "保存中...",
"common.reset": "重置", "common.success": "成功",
"common.status": "状态", "common.unbind": "解绑",
"nav.dashboard": "仪表盘", "nav.dashboard": "仪表盘",
"nav.home": "首页", "nav.home": "首页",
"nav.oauth": "OAuth应用", "nav.oauth": "OAuth应用",
"nav.oauth_providers": "身份源管理",
"nav.profile": "个人中心",
"nav.roles": "角色管理", "nav.roles": "角色管理",
"nav.settings": "系统设置", "nav.settings": "系统设置",
"nav.oauth_providers": "身份源管理", "nav.users": "用户管理",
"oauth.provider.code": "代码",
"oauth.provider.name": "名称",
"oauth.provider.client_id": "客户端ID", "oauth.provider.client_id": "客户端ID",
"oauth.provider.client_secret": "客户端密钥", "oauth.provider.client_secret": "客户端密钥",
"oauth.provider.redirect_uri": "回调地址", "oauth.provider.code": "代码",
"oauth.provider.auth_url": "授权地址",
"oauth.provider.token_url": "令牌地址",
"oauth.provider.user_info_url": "用户信息地址",
"oauth.provider.scope": "权限范围",
"oauth.provider.enabled": "启用",
"oauth.provider.create": "添加身份源", "oauth.provider.create": "添加身份源",
"oauth.provider.edit": "编辑身份源", "oauth.provider.edit": "编辑身份源",
"oauth.provider.delete_confirm": "确定删除该身份源吗?", "oauth.provider.enabled": "启用",
"oauth.provider.templates": "模板", "oauth.provider.name": "名称",
"nav.profile": "个人中心", "oauth.provider.redirect_uri": "回调地址",
"nav.users": "用户管理", "oauth.create": "创建",
"user.email": "邮箱", "oauth.create_app": "创建应用",
"user.profile": "个人资料", "org.created": "组织已创建",
"user.role": "角色",
"user.username": "用户名",
"role.name": "角色名称",
"role.code": "角色代码", "role.code": "角色代码",
"role.description": "描述",
"role.create": "创建角色", "role.create": "创建角色",
"role.edit": "编辑角色",
"role.delete_confirm": "确定要删除该角色吗?", "role.delete_confirm": "确定要删除该角色吗?",
"role.description": "描述",
"role.edit": "编辑角色",
"role.name": "角色名称",
"role.search_placeholder": "搜索角色...", "role.search_placeholder": "搜索角色...",
"settings.category.app": "应用配置",
"settings.category.auth": "认证配置",
"settings.category.security": "安全配置",
"settings.category.code": "验证码配置",
"settings.category.email": "邮件配置",
"settings.category.sms": "短信配置",
"settings.app.name": "应用名称",
"settings.app.name_desc": "应用的显示名称",
"settings.app.id": "应用标识", "settings.app.id": "应用标识",
"settings.app.id_desc": "应用的唯一标识符", "settings.app.id_desc": "应用的唯一标识符",
"settings.auth.reg_require_email": "注册需要邮箱", "settings.app.name": "应用名称",
"settings.auth.reg_require_email_desc": "注册时强制要求填写邮箱", "settings.app.name_desc": "应用的显示名称",
"settings.auth.reg_require_phone": "注册需要手机号",
"settings.auth.reg_require_phone_desc": "注册时强制要求填写手机号",
"settings.auth.login_methods": "登录方式", "settings.auth.login_methods": "登录方式",
"settings.auth.login_methods_desc": "支持的登录方式JSON数组", "settings.auth.login_methods_desc": "支持的登录方式JSON数组",
"settings.auth.password_fields": "密码登录字段", "settings.auth.password_fields": "密码登录字段",
"settings.auth.password_fields_desc": "密码登录支持的字段", "settings.auth.password_fields_desc": "密码登录支持的字段",
"settings.security.captcha_enabled": "启用验证码", "settings.auth.reg_require_email": "注册需要邮箱",
"settings.security.captcha_enabled_desc": "登录时显示验证码验证", "settings.auth.reg_require_email_desc": "注册时强制要求填写邮箱",
"settings.security.max_login_attempts": "最大登录尝试次数", "settings.auth.reg_require_phone": "注册需要手机号",
"settings.security.max_login_attempts_desc": "失败登录尝试的最大次数", "settings.auth.reg_require_phone_desc": "注册时强制要求填写手机号",
"settings.security.bcrypt_cost": "Bcrypt强度", "settings.category.app": "应用配置",
"settings.security.bcrypt_cost_desc": "密码哈希强度4-31", "settings.category.auth": "认证配置",
"settings.category.code": "验证码配置",
"settings.category.email": "邮件配置",
"settings.category.security": "安全配置",
"settings.category.sms": "短信配置",
"settings.code.expiry": "验证码有效期", "settings.code.expiry": "验证码有效期",
"settings.code.expiry_desc": "验证码有效时间(分钟)", "settings.code.expiry_desc": "验证码有效时间(分钟)",
"settings.code.length": "验证码长度", "settings.code.length": "验证码长度",
"settings.code.length_desc": "验证码的位数", "settings.code.length_desc": "验证码的位数",
"settings.code.max_attempt": "最大尝试次数", "settings.code.max_attempt": "最大尝试次数",
"settings.code.max_attempt_desc": "验证码最大尝试次数", "settings.code.max_attempt_desc": "验证码最大尝试次数",
"settings.code.send_interval": "发送间隔",
"settings.code.send_interval_desc": "两次发送之间的最小间隔(秒)",
"settings.code.max_daily_count": "单日最大发送次数", "settings.code.max_daily_count": "单日最大发送次数",
"settings.code.max_daily_count_desc": "单日发送验证码最大次数0禁用-1不限制", "settings.code.max_daily_count_desc": "单日发送验证码最大次数0禁用-1不限制",
"settings.code.send_interval": "发送间隔",
"settings.code.send_interval_desc": "两次发送之间的最小间隔(秒)",
"settings.email.enabled": "启用邮件", "settings.email.enabled": "启用邮件",
"settings.email.enabled_desc": "启用邮件服务用于通知", "settings.email.enabled_desc": "启用邮件服务用于通知",
"settings.email.from": "发件人邮箱",
"settings.email.from_desc": "发件人邮箱地址",
"settings.email.from_name": "发件人名称",
"settings.email.from_name_desc": "发件人显示名称",
"settings.email.provider": "邮件服务商", "settings.email.provider": "邮件服务商",
"settings.email.provider_desc": "邮件服务提供商", "settings.email.provider_desc": "邮件服务提供商",
"settings.email.smtp_host": "SMTP服务器", "settings.email.smtp_host": "SMTP服务器",
"settings.email.smtp_host_desc": "SMTP服务器地址", "settings.email.smtp_host_desc": "SMTP服务器地址",
"settings.email.smtp_pass": "SMTP密码",
"settings.email.smtp_pass_desc": "SMTP认证密码",
"settings.email.smtp_port": "SMTP端口", "settings.email.smtp_port": "SMTP端口",
"settings.email.smtp_port_desc": "SMTP服务器端口", "settings.email.smtp_port_desc": "SMTP服务器端口",
"settings.email.smtp_user": "SMTP用户名", "settings.email.smtp_user": "SMTP用户名",
"settings.email.smtp_user_desc": "SMTP认证用户名", "settings.email.smtp_user_desc": "SMTP认证用户名",
"settings.email.smtp_pass": "SMTP密码", "settings.load_failed": "加载设置失败",
"settings.email.smtp_pass_desc": "SMTP认证密码", "settings.no_changes": "没有需要保存的更改",
"settings.email.from": "发件人邮箱", "settings.reset_done": "设置已重置",
"settings.email.from_desc": "发件人邮箱地址", "settings.save_failed": "保存设置失败",
"settings.email.from_name": "发件人名称", "settings.save_success": "设置保存成功",
"settings.email.from_name_desc": "发件人显示名称", "settings.security.bcrypt_cost": "Bcrypt强度",
"settings.security.bcrypt_cost_desc": "密码哈希强度4-31",
"settings.security.captcha_enabled": "启用验证码",
"settings.security.captcha_enabled_desc": "登录时显示验证码验证",
"settings.security.max_login_attempts": "最大登录尝试次数",
"settings.security.max_login_attempts_desc": "失败登录尝试的最大次数",
"settings.sms.access_key": "Access Key",
"settings.sms.access_key_desc": "API访问密钥ID",
"settings.sms.access_secret": "Access Secret",
"settings.sms.access_secret_desc": "API访问密钥密文",
"settings.sms.enabled": "启用短信", "settings.sms.enabled": "启用短信",
"settings.sms.enabled_desc": "启用短信服务用于验证码", "settings.sms.enabled_desc": "启用短信服务用于验证码",
"settings.sms.provider": "短信服务商", "settings.sms.provider": "短信服务商",
"settings.sms.provider_desc": "短信服务提供商",
"settings.sms.provider_aliyun": "阿里云", "settings.sms.provider_aliyun": "阿里云",
"settings.sms.provider_desc": "短信服务提供商",
"settings.sms.provider_tencent": "腾讯云", "settings.sms.provider_tencent": "腾讯云",
"settings.sms.access_key": "Access Key",
"settings.sms.access_key_desc": "API访问密钥ID",
"settings.sms.access_secret": "Access Secret",
"settings.sms.access_secret_desc": "API访问密钥密文",
"settings.sms.sign_name": "短信签名", "settings.sms.sign_name": "短信签名",
"settings.sms.sign_name_desc": "已注册的短信签名", "settings.sms.sign_name_desc": "已注册的短信签名",
"settings.sms.template_code": "模板代码", "settings.sms.template_code": "模板代码",
"settings.sms.template_code_desc": "验证码模板ID", "settings.sms.template_code_desc": "验证码模板ID",
"settings.load_failed": "加载设置失败", "user.avatar_url": "头像链接",
"settings.save_success": "设置保存成功", "user.email": "邮箱",
"settings.save_failed": "保存设置失败", "user.info": "基本信息",
"settings.reset_done": "设置已重置", "user.nickname": "昵称",
"settings.no_changes": "没有需要保存的更改" "user.phone": "手机号",
"user.profile": "个人资料",
"user.username": "用户名"
} }
} }

@ -146,6 +146,7 @@
toggleCollapse = () => { toggleCollapse = () => {
collapsed = !collapsed; collapsed = !collapsed;
}; };
console.log($vbase.PermAdmin("*"))
</script> </script>
</html> </html>

@ -10,12 +10,14 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
padding-top: var(--spacing-xl); padding-top: var(--spacing-xl);
padding-bottom: var(--spacing-xl);
box-sizing: border-box; box-sizing: border-box;
min-height: 100vh;
} }
.profile-container { .profile-container {
width: 100%; width: 100%;
max-width: 600px; max-width: 800px;
padding: var(--spacing-xl); padding: var(--spacing-xl);
background: var(--bg-color-secondary); background: var(--bg-color-secondary);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
@ -23,21 +25,33 @@
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--spacing-lg); gap: var(--spacing-xl);
height: fit-content; height: fit-content;
} }
.section-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--color-text);
margin-bottom: var(--spacing-md);
padding-bottom: var(--spacing-xs);
border-bottom: 1px solid var(--border-color);
}
.avatar-section { .avatar-section {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; gap: var(--spacing-lg);
flex-direction: column; }
gap: var(--spacing-md);
.avatar-wrapper {
position: relative;
cursor: pointer;
} }
.avatar { .avatar {
width: 100px; width: 80px;
height: 100px; height: 80px;
border-radius: 50%; border-radius: 50%;
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary)); background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
display: flex; display: flex;
@ -46,45 +60,188 @@
font-size: 32px; font-size: 32px;
color: var(--color-primary-text); color: var(--color-primary-text);
font-weight: bold; font-weight: bold;
overflow: hidden;
}
.avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
opacity: 0;
transition: opacity 0.2s;
}
.avatar-wrapper:hover .avatar-overlay {
opacity: 1;
}
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--spacing-md);
}
.form-full {
grid-column: 1 / -1;
} }
.form-content { .oauth-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--spacing-sm);
}
.oauth-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-sm);
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
}
.oauth-info {
display: flex;
align-items: center;
gap: var(--spacing-md); gap: var(--spacing-md);
} }
.oauth-icon {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
background: var(--bg-color-secondary);
border-radius: 50%;
}
.danger-zone {
margin-top: var(--spacing-lg);
padding-top: var(--spacing-lg);
border-top: 1px solid var(--border-color);
}
</style> </style>
</head> </head>
<body> <body>
<div class="profile-container"> <div class="profile-container">
<h2 style="text-align: center;">{{ $t('user.profile') }}</h2> <div style="display: flex; justify-content: space-between; align-items: center;">
<h2 style="margin: 0;">{{ $t('user.profile') }}</h2>
<v-btn color="danger" variant="outline" size="sm" :click="handleLogout">
{{ $t('auth.logout') }}
</v-btn>
</div>
<div class="avatar-section"> <!-- Basic Info -->
<div class="avatar"> <div>
{{ user.username ? user.username.charAt(0).toUpperCase() : 'U' }} <div class="section-title">{{ $t('user.info') || 'Basic Information' }}</div>
<div class="avatar-section">
<div class="avatar-wrapper" @click="triggerAvatarUpload">
<div class="avatar">
<img v-if="user.avatar" :src="user.avatar" alt="Avatar">
<span v-else>{{ user.nickname ? user.nickname.charAt(0).toUpperCase() : (user.username ? user.username.charAt(0).toUpperCase() : 'U') }}</span>
</div>
<div class="avatar-overlay">
<i class="fas fa-edit"></i>
</div>
</div>
<div style="flex: 1;">
<div style="font-weight: bold; font-size: 1.2rem;">{{ user.nickname || user.username }}</div>
<div style="color: var(--color-text-light); font-size: 0.9rem;">{{ $t('common.id') }}: {{ user.id }}</div>
</div>
</div> </div>
</div>
<form class="form-content" @submit.prevent="updateProfile"> <form style="margin-top: var(--spacing-lg);" @submit.prevent="updateProfile">
<v-input label="Username" v:value="user.username" disabled></v-input> <div class="form-grid">
<v-input label="Email" type="email" v:value="user.email" required></v-input> <v-input label="Username" :label="$t('user.username')" v:value="user.username" disabled></v-input>
<v-input label="Phone" type="tel" v:value="user.phone"></v-input> <v-input label="Nickname" :label="$t('user.nickname')" v:value="user.nickname"></v-input>
<v-input label="Email" :label="$t('user.email')" type="email" v:value="user.email"></v-input>
<v-input label="Phone" :label="$t('user.phone')" type="tel" v:value="user.phone"></v-input>
<v-input class="form-full" label="Avatar URL" :label="$t('user.avatar_url')" v:value="user.avatar"></v-input>
</div>
<div style="margin-top: var(--spacing-md); display: flex; justify-content: flex-end;">
<v-btn type="submit" color="primary" :loading="loading">
{{ $t('common.save') }}
</v-btn>
</div>
</form>
</div>
<v-btn type="submit" color="primary" block style="margin-top: var(--spacing-sm);"> <!-- Security -->
{{ $t('common.save') }} <div>
</v-btn> <div class="section-title">{{ $t('auth.security') || 'Security' }}</div>
</form> <form @submit.prevent="changePassword">
<div class="form-grid">
<v-input class="form-full" :label="$t('auth.old_password')" type="password" v:value="passwordForm.old_password"></v-input>
<v-input class="form-full" :label="$t('auth.new_password')" type="password" v:value="passwordForm.new_password"></v-input>
</div>
<div style="margin-top: var(--spacing-md); display: flex; justify-content: flex-end;">
<v-btn type="submit" variant="outline" :loading="pwdLoading" :disabled="!passwordForm.new_password || !passwordForm.old_password">
{{ $t('auth.change_password') || 'Update Password' }}
</v-btn>
</div>
</form>
</div>
<div style="border-top: 1px solid var(--border-color); padding-top: var(--spacing-lg);"> <!-- Linked Accounts -->
<v-btn color="danger" variant="outline" block :click="handleLogout"> <div>
{{ $t('auth.logout') }} <div class="section-title">{{ $t('auth.linked_accounts') || 'Linked Accounts' }}</div>
</v-btn> <div class="oauth-list">
<div class="oauth-item" v-for="provider in providers">
<div class="oauth-info">
<div class="oauth-icon">
<i class="fab" :class="'fa-' + provider.code"></i>
</div>
<div>
<div style="font-weight: 500;">{{ provider.name }}</div>
<div style="font-size: 0.8rem; color: var(--color-text-light);" v-if="isBound(provider.code)">
{{ getBindInfo(provider.code) }}
</div>
<div style="font-size: 0.8rem; color: var(--color-text-light);" v-else>
{{ $t('auth.not_linked') }}
</div>
</div>
</div>
<div>
<v-btn v-if="isBound(provider.code)" size="sm" variant="outline" color="danger" :click="() => unbind(provider.code)">
{{ $t('common.unbind') }}
</v-btn>
<v-btn v-else size="sm" variant="outline" :click="() => bind(provider.code)">
{{ $t('common.bind') }}
</v-btn>
</div>
</div>
</div>
</div> </div>
</div> </div>
</body> </body>
<script setup> <script setup>
user = $env.$vbase.user || {}; user = $env.$vbase.user || {};
loading = false;
pwdLoading = false;
passwordForm = { old_password: '', new_password: '' };
// Bindings
providers = [];
bindings = [];
// Fetch fresh data // Fetch fresh data
loadUser = async () => { loadUser = async () => {
@ -95,25 +252,117 @@
} }
}; };
loadBindings = async () => {
try {
// Get enabled providers
const providersRes = await $axios.get('/api/auth/login-methods');
providers = providersRes.methods.filter(p => p.type !== 'password' && p.type !== 'code' && p.type !== 'email_code' && p.type !== 'phone_code').map(p => ({
code: p.type,
name: p.name
}));
// Get current bindings
bindings = await $axios.get('/api/auth/me/bindings');
} catch (e) {
console.error(e);
}
};
updateProfile = async () => { updateProfile = async () => {
loading = true;
try { try {
await $axios.patch('/api/auth/me', { await $axios.patch('/api/auth/me', {
nickname: user.nickname,
email: user.email, email: user.email,
phone: user.phone phone: user.phone,
avatar: user.avatar
});
$message.success($t('common.save_success'));
await loadUser();
} catch (e) {
$message.error(e.message);
} finally {
loading = false;
}
};
changePassword = async () => {
if (!passwordForm.new_password || !passwordForm.old_password) return;
pwdLoading = true;
try {
await $axios.post('/api/auth/me/change-password', {
old_password: passwordForm.old_password,
new_password: passwordForm.new_password
}); });
$message.success("Profile updated"); $message.success($t('auth.password_changed'));
loadUser(); passwordForm.old_password = '';
passwordForm.new_password = '';
} catch (e) { } catch (e) {
$message.error(e.message); $message.error(e.message);
} finally {
pwdLoading = false;
} }
}; };
handleLogout = async () => { handleLogout = async () => {
await $env.$vbase.logout('/login'); await $env.$vbase.logout('/login');
}; };
// OAuth Helpers
isBound = (code) => {
return bindings.some(b => b.provider === code);
};
getBindInfo = (code) => {
const b = bindings.find(b => b.provider === code);
return b ? (b.provider_name || b.email || $t('auth.linked')) : '';
};
bind = async (provider) => {
try {
// Fetch authorization URL
const res = await $axios.get('/api/auth/authorize/thirdparty', {
params: {
provider: provider,
redirect: window.location.origin + '/callback/' + provider,
bind_mode: true
}
});
// Redirect to provider auth page
if (res.auth_url) {
window.location.href = res.auth_url;
}
} catch (e) {
$message.error(e.message);
}
};
unbind = async (provider) => {
try {
await $message.confirm($t('auth.unbind_confirm'));
await $axios.delete(`/api/auth/me/bindings/${provider}`);
$message.success($t('common.success'));
loadBindings();
} catch (e) {
if (e !== 'cancel') $message.error(e.message);
}
};
triggerAvatarUpload = async () => {
try {
const url = await $message.prompt($t('user.avatar_url'), user.avatar || '');
if (url) {
user.avatar = url;
}
} catch (e) {
// Cancelled
}
};
</script> </script>
<script> <script>
$data.loadUser(); $data.loadUser();
$data.loadBindings();
</script> </script>
</html> </html>
Loading…
Cancel
Save