From b7197f1744bf726129cdf2b068e7803a14da231e Mon Sep 17 00:00:00 2001
From: aaron <>
Date: Fri, 9 May 2025 15:51:52 +0800
Subject: [PATCH] update
---
docker-compose.yml | 2 +-
src/App.vue | 593 +++++++++++++++++++++++++++++++++++++-
src/views/AIAgentView.vue | 4 -
src/views/HomeView.vue | 27 +-
4 files changed, 594 insertions(+), 32 deletions(-)
diff --git a/docker-compose.yml b/docker-compose.yml
index 6cc8dbf..d2dc58d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,7 +5,7 @@ services:
build:
context: .
dockerfile: Dockerfile
- image: tradus-web:1.0.16
+ image: tradus-web:1.0.17
container_name: tradus-web
ports:
- '6000:80'
diff --git a/src/App.vue b/src/App.vue
index c4f8796..5caa5f0 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -2,12 +2,179 @@
import { RouterView } from 'vue-router'
import { computed, ref, onMounted, onUnmounted } from 'vue'
import { useUserStore } from './stores/user'
+import { authApi } from './services/api'
const userStore = useUserStore()
const isAuthenticated = computed(() => userStore.isAuthenticated)
const userInfo = computed(() => userStore.userInfo)
const showMobileMenu = ref(false)
const showUserMenu = ref(false)
+const showLoginModal = ref(false)
+const loginMode = ref('login') // 'login' 或 'register'
+
+// 表单数据
+const formData = ref({
+ email: '',
+ password: '',
+ confirmPassword: '',
+ nickname: '',
+ verificationCode: '',
+})
+
+// 表单错误信息
+const formErrors = ref({
+ email: '',
+ password: '',
+ confirmPassword: '',
+ nickname: '',
+ verificationCode: '',
+})
+
+// 验证码相关状态
+const sendingCode = ref(false)
+const countdown = ref(0)
+
+// 加载状态
+const isLoading = ref(false)
+
+// 发送验证码
+const sendVerificationCode = async () => {
+ if (!formData.value.email) {
+ formErrors.value.email = '请输入邮箱'
+ return
+ }
+
+ try {
+ sendingCode.value = true
+ formErrors.value.email = ''
+
+ await authApi.sendVerificationCode(formData.value.email)
+
+ countdown.value = 60
+
+ // 倒计时
+ const timer = setInterval(() => {
+ countdown.value--
+ if (countdown.value <= 0) {
+ clearInterval(timer)
+ }
+ }, 1000)
+ } catch (err) {
+ formErrors.value.email = err instanceof Error ? err.message : '发送验证码失败,请重试'
+ console.error('发送验证码失败:', err)
+ } finally {
+ sendingCode.value = false
+ }
+}
+
+// 验证表单
+const validateForm = () => {
+ let isValid = true
+ formErrors.value = {
+ email: '',
+ password: '',
+ confirmPassword: '',
+ nickname: '',
+ verificationCode: '',
+ }
+
+ // 验证邮箱
+ if (!formData.value.email) {
+ formErrors.value.email = '请输入邮箱'
+ isValid = false
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.value.email)) {
+ formErrors.value.email = '请输入有效的邮箱地址'
+ isValid = false
+ }
+
+ // 验证密码
+ if (!formData.value.password) {
+ formErrors.value.password = '请输入密码'
+ isValid = false
+ } else if (formData.value.password.length < 6) {
+ formErrors.value.password = '密码长度不能小于6位'
+ isValid = false
+ }
+
+ // 注册模式下的额外验证
+ if (loginMode.value === 'register') {
+ // 验证确认密码
+ if (!formData.value.confirmPassword) {
+ formErrors.value.confirmPassword = '请确认密码'
+ isValid = false
+ } else if (formData.value.confirmPassword !== formData.value.password) {
+ formErrors.value.confirmPassword = '两次输入的密码不一致'
+ isValid = false
+ }
+
+ // 验证昵称
+ if (!formData.value.nickname) {
+ formErrors.value.nickname = '请输入昵称'
+ isValid = false
+ }
+
+ // 验证验证码
+ if (!formData.value.verificationCode) {
+ formErrors.value.verificationCode = '请输入验证码'
+ isValid = false
+ }
+ }
+
+ return isValid
+}
+
+// 处理表单提交
+const handleSubmit = async () => {
+ if (!validateForm()) return
+
+ isLoading.value = true
+ try {
+ if (loginMode.value === 'login') {
+ await authApi.login({
+ mail: formData.value.email,
+ password: formData.value.password,
+ })
+ } else {
+ await authApi.register({
+ mail: formData.value.email,
+ nickname: formData.value.nickname,
+ password: formData.value.password,
+ verification_code: formData.value.verificationCode,
+ })
+ // 注册成功后自动登录
+ await authApi.login({
+ mail: formData.value.email,
+ password: formData.value.password,
+ })
+ }
+ closeAuthModal()
+ } catch (error) {
+ console.error('认证失败:', error)
+ formErrors.value.email = error instanceof Error ? error.message : '认证失败,请检查输入信息'
+ } finally {
+ isLoading.value = false
+ }
+}
+
+const closeAuthModal = () => {
+ showLoginModal.value = false
+ loginMode.value = 'login'
+ // 重置表单和错误信息
+ formData.value = {
+ email: '',
+ password: '',
+ confirmPassword: '',
+ nickname: '',
+ verificationCode: '',
+ }
+ formErrors.value = {
+ email: '',
+ password: '',
+ confirmPassword: '',
+ nickname: '',
+ verificationCode: '',
+ }
+}
const handleLogout = () => {
userStore.logout()
@@ -38,6 +205,11 @@ const closeMenus = (event: MouseEvent) => {
}
}
+const openAuthModal = (mode: 'login' | 'register') => {
+ loginMode.value = mode
+ showLoginModal.value = true
+}
+
onMounted(() => {
document.addEventListener('click', closeMenus)
})
@@ -147,8 +319,8 @@ onUnmounted(() => {
- 登录
- 注册
+
+
@@ -191,12 +363,8 @@ onUnmounted(() => {
- 登录
- 注册
+
+
@@ -207,9 +375,142 @@ onUnmounted(() => {
-
+
+
+
+
+
+
+
+
+
+ {{ loginMode === 'login' ? '还没有账号?' : '已有账号?' }}
+
+
+
+
+
@@ -912,4 +1213,278 @@ body {
display: block;
}
}
+
+/* 认证按钮样式 */
+.auth-button {
+ width: 100%;
+ padding: 0.75rem;
+ border: 1px solid var(--color-accent);
+ border-radius: var(--border-radius);
+ font-size: 0.95rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ margin: 0.5rem 0;
+ background: none;
+ color: var(--color-accent);
+}
+
+.auth-button:hover {
+ background-color: rgba(51, 85, 255, 0.04);
+}
+
+.auth-button.register {
+ background-color: var(--color-accent);
+ color: white;
+}
+
+.auth-button.register:hover {
+ background-color: var(--color-accent-hover);
+}
+
+/* 模态框样式 */
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 2000;
+ backdrop-filter: blur(4px);
+}
+
+.modal-container {
+ background-color: var(--color-bg-primary);
+ border-radius: var(--border-radius);
+ width: 90%;
+ max-width: 400px;
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);
+ animation: modalSlideIn 0.3s ease;
+}
+
+.modal-header {
+ padding: 1.5rem;
+ border-bottom: 1px solid var(--color-border);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.modal-header h2 {
+ font-size: 1.5rem;
+ font-weight: 600;
+ color: var(--color-text-primary);
+ margin: 0;
+}
+
+.close-button {
+ background: none;
+ border: none;
+ color: var(--color-text-secondary);
+ cursor: pointer;
+ padding: 0.5rem;
+ border-radius: 50%;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.close-button:hover {
+ background-color: var(--color-bg-hover);
+ color: var(--color-text-primary);
+}
+
+.modal-content {
+ padding: 1.5rem;
+}
+
+.auth-form {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.form-group {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.form-group label {
+ font-size: 0.95rem;
+ font-weight: 500;
+ color: var(--color-text-primary);
+}
+
+.form-group input {
+ padding: 0.75rem;
+ border: 1px solid var(--color-border);
+ border-radius: var(--border-radius);
+ font-size: 1rem;
+ transition: all 0.2s ease;
+ background-color: var(--color-bg-secondary);
+ color: var(--color-text-primary);
+}
+
+.form-group input:focus {
+ outline: none;
+ border-color: var(--color-accent);
+ box-shadow: 0 0 0 2px rgba(51, 85, 255, 0.1);
+}
+
+.submit-button {
+ margin-top: 1rem;
+ padding: 0.875rem;
+ background-color: var(--color-accent);
+ color: white;
+ border: none;
+ border-radius: var(--border-radius);
+ font-size: 1rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.submit-button:hover {
+ background-color: var(--color-accent-hover);
+}
+
+.auth-switch {
+ margin-top: 1.5rem;
+ text-align: center;
+ color: var(--color-text-secondary);
+ font-size: 0.95rem;
+}
+
+.switch-button {
+ background: none;
+ border: none;
+ color: var(--color-accent);
+ cursor: pointer;
+ font-weight: 500;
+ padding: 0;
+ margin-left: 0.5rem;
+}
+
+.switch-button:hover {
+ text-decoration: underline;
+}
+
+@keyframes modalSlideIn {
+ from {
+ transform: translateY(-20px);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
+
+/* 移动端适配 */
+@media (max-width: 768px) {
+ .modal-container {
+ width: 95%;
+ }
+
+ .modal-header {
+ padding: 1.25rem;
+ }
+
+ .modal-content {
+ padding: 1.25rem;
+ }
+}
+
+/* 表单错误样式 */
+.form-group input.error {
+ border-color: #ff4d4f;
+}
+
+.form-group input.error:focus {
+ box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.1);
+}
+
+.error-message {
+ color: #ff4d4f;
+ font-size: 0.85rem;
+ margin-top: 0.25rem;
+}
+
+/* 加载动画 */
+.loading-spinner {
+ display: inline-block;
+ width: 1.25rem;
+ height: 1.25rem;
+ border: 2px solid #ffffff;
+ border-top-color: transparent;
+ border-radius: 50%;
+ animation: spin 0.8s linear infinite;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+/* 禁用状态 */
+.submit-button:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+
+/* 输入框聚焦状态 */
+.form-group input:focus {
+ outline: none;
+ border-color: var(--color-accent);
+ box-shadow: 0 0 0 2px rgba(51, 85, 255, 0.1);
+}
+
+/* 验证码按钮样式 */
+.input-with-button {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.input-with-button input {
+ flex: 1;
+}
+
+.code-button {
+ padding: 0 1rem;
+ border-radius: var(--border-radius);
+ background-color: var(--color-bg-elevated);
+ border: 1px solid var(--color-border);
+ color: var(--color-text-secondary);
+ font-size: 0.9rem;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.code-button:hover:not(:disabled) {
+ background-color: var(--color-bg-secondary);
+ color: var(--color-text-primary);
+}
+
+.code-button:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+
+@media (max-width: 480px) {
+ .input-with-button {
+ flex-direction: column;
+ }
+
+ .code-button {
+ padding: 0.8rem;
+ }
+}
diff --git a/src/views/AIAgentView.vue b/src/views/AIAgentView.vue
index 96f8267..1ba2880 100644
--- a/src/views/AIAgentView.vue
+++ b/src/views/AIAgentView.vue
@@ -347,10 +347,6 @@ const getIconPath = (agent: Agent) => {
需要登录
请登录或注册账号以使用AI Agent功能
-
- 登录
- 注册
-
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue
index 924b0ae..6a5461a 100644
--- a/src/views/HomeView.vue
+++ b/src/views/HomeView.vue
@@ -5,6 +5,12 @@ import { useRouter } from 'vue-router'
const userStore = useUserStore()
const isAuthenticated = computed(() => userStore.isAuthenticated)
+
+const emit = defineEmits(['openAuth'])
+
+const openAuthModal = (mode: 'login' | 'register') => {
+ emit('openAuth', mode)
+}
@@ -13,25 +19,10 @@ const isAuthenticated = computed(() => userStore.isAuthenticated)
tradus
A intelligent trading assistant
-
-
-
- 登录
- 注册
-
+
+
+
-
-