From 89dbdc63db4fd377cddc37252d87105103bb3280 Mon Sep 17 00:00:00 2001 From: XCool <2350444842@qq.com> Date: Thu, 16 Oct 2025 16:21:56 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/request.js | 4 +- src/assets/styles/global.css | 251 ++++++++++++++++++++++++++++++++++- src/store/index.js | 147 ++++++++++++-------- src/views/CapsuleDetail.vue | 8 -- src/views/Compose.vue | 183 +++++++++++++++---------- src/views/Home.vue | 48 ++++--- src/views/Login.vue | 20 +-- src/views/Profile.vue | 38 ++---- src/views/Register.vue | 14 -- src/views/Sent.vue | 17 ++- src/views/Timeline.vue | 2 - vue.config.js | 2 +- 12 files changed, 512 insertions(+), 222 deletions(-) diff --git a/src/api/request.js b/src/api/request.js index 078ac7d..a2d3ad2 100644 --- a/src/api/request.js +++ b/src/api/request.js @@ -30,8 +30,8 @@ api.interceptors.response.use( response => { const res = response.data - // 如果响应码不是200,则判断为错误 - if (res.code !== 200) { + // 如果响应中的success不是true,则判断为错误 + if (res.success !== true) { showFailToast(res.message || '请求失败') // 401: 未登录或token过期 diff --git a/src/assets/styles/global.css b/src/assets/styles/global.css index 9451504..9e895ef 100644 --- a/src/assets/styles/global.css +++ b/src/assets/styles/global.css @@ -8,6 +8,8 @@ --text-secondary: #a0b3d0; --glass-bg: rgba(255, 255, 255, 0.1); --glass-border: rgba(255, 255, 255, 0.2); + --button-gradient: linear-gradient(135deg, #00D4FF, #0099CC); + --button-hover-gradient: linear-gradient(135deg, #0099CC, #006699); } * { @@ -17,10 +19,15 @@ } body { - font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif; + font-family: 'SF Pro Display', 'PingFang SC', 'Helvetica Neue', 'Microsoft YaHei', Arial, sans-serif; background-color: var(--primary-color); color: var(--text-primary); overflow-x: hidden; + font-size: 16px; + line-height: 1.6; + letter-spacing: 0.02em; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } /* 深空背景 */ @@ -187,6 +194,80 @@ body { color: var(--accent-color); } +/* 字体大小 */ +.text-xs { font-size: 12px; } +.text-sm { font-size: 14px; } +.text-base { font-size: 16px; } +.text-lg { font-size: 18px; } +.text-xl { font-size: 20px; } +.text-2xl { font-size: 24px; } +.text-3xl { font-size: 30px; } +.text-4xl { font-size: 36px; } + +/* 字体粗细 */ +.font-light { font-weight: 300; } +.font-normal { font-weight: 400; } +.font-medium { font-weight: 500; } +.font-semibold { font-weight: 600; } +.font-bold { font-weight: 700; } + +/* 标题样式 */ +h1, h2, h3, h4, h5, h6 { + font-weight: 600; + line-height: 1.3; + margin-bottom: 0.5em; + letter-spacing: -0.01em; +} + +h1 { font-size: 2.5rem; } +h2 { font-size: 2rem; } +h3 { font-size: 1.75rem; } +h4 { font-size: 1.5rem; } +h5 { font-size: 1.25rem; } +h6 { font-size: 1.125rem; } + +/* 段落样式 */ +p { + margin-bottom: 1em; + line-height: 1.6; +} + +/* 文本阴影 */ +.text-shadow { + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.text-shadow-lg { + text-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +/* 文本发光效果 */ +.text-glow { + text-shadow: 0 0 10px rgba(0, 212, 255, 0.5); +} + +.text-glow-lg { + text-shadow: 0 0 20px rgba(0, 212, 255, 0.7); +} + +/* 特殊文本效果 */ +.gradient-text { + background: linear-gradient(135deg, var(--accent-color), #0099CC); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + font-weight: 600; +} + +/* 代码文本 */ +code { + font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace; + background-color: rgba(255, 255, 255, 0.1); + padding: 0.2em 0.4em; + border-radius: 4px; + font-size: 0.9em; +} + /* 间距工具类 */ .mt-10 { margin-top: 10px; } .mt-20 { margin-top: 20px; } @@ -352,6 +433,9 @@ input, textarea, .van-field__control { font-size: 16px !important; transition: all 0.3s ease !important; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1) !important; + font-family: inherit !important; + letter-spacing: 0.01em !important; + line-height: 1.5 !important; } input:focus, textarea:focus, .van-field__control:focus { @@ -375,6 +459,9 @@ input::placeholder, textarea::placeholder, .van-field__control::placeholder { .van-field__label { color: var(--text-secondary) !important; font-weight: 500 !important; + font-size: 14px !important; + letter-spacing: 0.01em !important; + line-height: 1.4 !important; } .van-field--error .van-field__control { @@ -406,9 +493,11 @@ input::placeholder, textarea::placeholder, .van-field__control::placeholder { .header h2 { margin: 0; - font-size: 18px; - font-weight: bold; + font-size: 20px; + font-weight: 600; color: var(--text-primary); + letter-spacing: -0.01em; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } .header .van-icon { @@ -447,4 +536,160 @@ input::placeholder, textarea::placeholder, .van-field__control::placeholder { .custom-tabbar .van-tabbar-item__text { font-size: 12px !important; font-weight: 500 !important; + letter-spacing: 0.01em !important; + line-height: 1.2 !important; +} + +/* 全局按钮样式优化 */ +.van-button { + border-radius: 12px !important; + font-weight: 600 !important; + letter-spacing: 0.5px !important; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2) !important; + transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1) !important; + border: none !important; + position: relative !important; + overflow: hidden !important; + font-family: inherit !important; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) !important; +} + +.van-button::before { + content: '' !important; + position: absolute !important; + top: 0 !important; + left: -100% !important; + width: 100% !important; + height: 100% !important; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent) !important; + transition: left 0.5s !important; +} + +.van-button:hover::before { + left: 100% !important; +} + +.van-button--primary { + background: var(--button-gradient) !important; + color: white !important; +} + +.van-button--primary:hover { + background: var(--button-hover-gradient) !important; + transform: translateY(-2px) !important; + box-shadow: 0 6px 20px rgba(0, 212, 255, 0.4) !important; +} + +.van-button--success { + background: linear-gradient(135deg, #28a745, #20c997) !important; + color: white !important; +} + +.van-button--success:hover { + background: linear-gradient(135deg, #218838, #1ea085) !important; + transform: translateY(-2px) !important; + box-shadow: 0 6px 20px rgba(40, 167, 69, 0.4) !important; +} + +.van-button--danger { + background: linear-gradient(135deg, #dc3545, #f86c6b) !important; + color: white !important; +} + +.van-button--danger:hover { + background: linear-gradient(135deg, #c82333, #f63c3c) !important; + transform: translateY(-2px) !important; + box-shadow: 0 6px 20px rgba(220, 53, 69, 0.4) !important; +} + +.van-button--warning { + background: linear-gradient(135deg, #ffc107, #ff922b) !important; + color: #212529 !important; +} + +.van-button--warning:hover { + background: linear-gradient(135deg, #e0a800, #ff8c00) !important; + transform: translateY(-2px) !important; + box-shadow: 0 6px 20px rgba(255, 193, 7, 0.4) !important; +} + +.van-button--default { + background: rgba(255, 255, 255, 0.1) !important; + color: var(--text-primary) !important; + border: 1px solid rgba(255, 255, 255, 0.2) !important; + backdrop-filter: blur(10px) !important; +} + +.van-button--default:hover { + background: rgba(255, 255, 255, 0.15) !important; + transform: translateY(-2px) !important; + box-shadow: 0 6px 20px rgba(255, 255, 255, 0.1) !important; +} + +.van-button--plain { + background: transparent !important; + border: 1px solid var(--accent-color) !important; + color: var(--accent-color) !important; +} + +.van-button--plain:hover { + background: rgba(0, 212, 255, 0.1) !important; + transform: translateY(-2px) !important; + box-shadow: 0 6px 20px rgba(0, 212, 255, 0.2) !important; +} + +.van-button:active { + transform: translateY(0) !important; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2) !important; +} + +.van-button--large { + height: 50px !important; + font-size: 16px !important; +} + +.van-button--normal { + height: 44px !important; + font-size: 14px !important; +} + +.van-button--small { + height: 32px !important; + font-size: 12px !important; +} + +.van-button--round { + border-radius: 22px !important; +} + +.van-button--round.van-button--large { + border-radius: 25px !important; +} + +.van-button--round.van-button--small { + border-radius: 16px !important; +} + +/* 特殊按钮样式 */ +.fab-button { + width: 60px !important; + height: 60px !important; + border-radius: 50% !important; + background: var(--button-gradient) !important; + border: none !important; + box-shadow: 0 4px 15px rgba(0, 212, 255, 0.4) !important; + font-size: 18px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + transition: all 0.3s ease !important; +} + +.fab-button:hover { + transform: scale(1.1) !important; + box-shadow: 0 6px 20px rgba(0, 212, 255, 0.6) !important; +} + +.fab-button:active { + transform: scale(0.95) !important; } \ No newline at end of file diff --git a/src/store/index.js b/src/store/index.js index ed26c5a..36613a7 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -7,9 +7,10 @@ export const userState = reactive({ token: '', refreshToken: '', userInfo: { - userId: '', + id: '', username: '', email: '', + nickname: '', avatar: '' }, subscription: { @@ -75,21 +76,23 @@ export const userActions = { // 登录 async login(credentials) { const res = await api.auth.login(credentials) - const { token, refreshToken, ...userInfo } = res.data + const { token, refreshToken, user } = res.data // 保存到本地存储 localStorage.setItem('token', token) localStorage.setItem('refreshToken', refreshToken) - localStorage.setItem('userInfo', JSON.stringify(userInfo)) + localStorage.setItem('userInfo', JSON.stringify(user)) // 更新状态 userState.isLoggedIn = true userState.token = token userState.refreshToken = refreshToken - userState.userInfo = userInfo + userState.userInfo = user - // 获取用户订阅信息 - await this.getSubscription() + // 获取用户订阅信息(不阻塞登录流程) + this.getSubscription().catch(err => { + console.error('登录后获取订阅信息失败:', err) + }) return res }, @@ -97,21 +100,24 @@ export const userActions = { // 注册 async register(userData) { const res = await api.auth.register(userData) - const { token, refreshToken, ...userInfo } = res.data + // 根据后端实际返回的数据结构提取数据 + const { token, refreshToken, user } = res.data // 保存到本地存储 localStorage.setItem('token', token) localStorage.setItem('refreshToken', refreshToken) - localStorage.setItem('userInfo', JSON.stringify(userInfo)) + localStorage.setItem('userInfo', JSON.stringify(user)) // 更新状态 userState.isLoggedIn = true userState.token = token userState.refreshToken = refreshToken - userState.userInfo = userInfo + userState.userInfo = user - // 获取用户订阅信息 - await this.getSubscription() + // 获取订阅信息(不阻塞注册流程) + this.getSubscription().catch(err => { + console.error('注册后获取订阅信息失败:', err) + }) return res }, @@ -132,49 +138,53 @@ export const userActions = { userState.token = '' userState.refreshToken = '' userState.userInfo = { - userId: '', + id: '', username: '', email: '', + nickname: '', avatar: '' } }, // 刷新token - async refreshToken() { - try { - const refreshToken = userState.refreshToken || localStorage.getItem('refreshToken') - if (!refreshToken) { - throw new Error('没有刷新令牌') + async refreshToken() { + try { + const refreshToken = userState.refreshToken || localStorage.getItem('refreshToken') + if (!refreshToken) { + throw new Error('没有刷新令牌') + } + + const res = await api.auth.refreshToken(refreshToken) + // 根据后端实际返回的数据结构提取数据 + const { token: newToken, refreshToken: newRefreshToken } = res.data + + // 更新本地存储 + localStorage.setItem('token', newToken) + localStorage.setItem('refreshToken', newRefreshToken) + + // 更新状态 + userState.token = newToken + userState.refreshToken = newRefreshToken + + return res + } catch (error) { + // 刷新失败,退出登录 + await this.logout() + throw error } - - const res = await api.auth.refreshToken(refreshToken) - const { token: newToken, refreshToken: newRefreshToken } = res.data - - // 更新本地存储 - localStorage.setItem('token', newToken) - localStorage.setItem('refreshToken', newRefreshToken) - - // 更新状态 - userState.token = newToken - userState.refreshToken = newRefreshToken - - return res - } catch (error) { - // 刷新失败,退出登录 - await this.logout() - throw error - } - }, + }, // 获取用户订阅信息 async getSubscription() { try { const res = await api.user.getSubscription() - userState.subscription = res.data + // 根据后端实际返回的数据结构提取数据 + userState.subscription = res.data.data return res } catch (error) { console.error('获取订阅信息失败:', error) - throw error + // 不抛出错误,避免影响登录流程 + return null } }, @@ -182,11 +192,14 @@ export const userActions = { async fetchUserInfo() { if (!userState.token) return; - const response = await api.user.getUserInfo(); - if (response.data.code === 200) { - userState.userInfo = response.data.data; - localStorage.setItem('userInfo', JSON.stringify(response.data.data)); + const response = await api.user.getUserProfile(); + // 根据后端实际返回的数据结构提取数据 + if (response.data) { + userState.userInfo = response.data; + localStorage.setItem('userInfo', JSON.stringify(response.data)); + return response.data; } + return null; }, // 初始化用户状态(从本地存储恢复) @@ -203,8 +216,10 @@ export const userActions = { userState.refreshToken = refreshToken || '' userState.userInfo = userInfo - // 获取订阅信息 - this.getSubscription() + // 获取订阅信息(不阻塞初始化流程) + this.getSubscription().catch(err => { + console.error('初始化获取订阅信息失败:', err) + }) } catch (error) { console.error('解析用户信息失败:', error) // 清除无效数据 @@ -229,18 +244,30 @@ export const mailActions = { // 根据类型更新不同的列表 if (type === 'INBOX') { - mailState.inboxList = res.data.list + if (page === 1) { + mailState.inboxList = res.data?.list || [] + } else { + mailState.inboxList = [...mailState.inboxList, ...(res.data?.list || [])] + } } else if (type === 'SENT') { - mailState.sentList = res.data.list + if (page === 1) { + mailState.sentList = res.data?.list || [] + } else { + mailState.sentList = [...mailState.sentList, ...(res.data?.list || [])] + } } else if (type === 'DRAFT') { - mailState.draftList = res.data.list + if (page === 1) { + mailState.draftList = res.data?.list || [] + } else { + mailState.draftList = [...mailState.draftList, ...(res.data?.list || [])] + } } // 更新分页信息 mailState.pagination = { - page: res.data.page, - size: res.data.size, - total: res.data.total + page: res.data?.page || page, + size: res.data?.size || size, + total: res.data?.total || 0 } return res @@ -359,12 +386,26 @@ export const capsuleActions = { try { capsuleState.loading = true const res = await api.capsule.getCapsules() - capsuleState.capsules = res.data.capsules - capsuleState.scene = res.data.scene - capsuleState.background = res.data.background + + // 添加空值检查 + if (res && res.data) { + capsuleState.capsules = res.data.capsules || [] + capsuleState.scene = res.data.scene || 'SPACE' + capsuleState.background = res.data.background || '' + } else { + // 如果没有数据,设置为空数组 + capsuleState.capsules = [] + capsuleState.scene = 'SPACE' + capsuleState.background = '' + } + return res } catch (error) { console.error('获取胶囊列表失败:', error) + // 出错时也要设置为空数组,避免页面出错 + capsuleState.capsules = [] + capsuleState.scene = 'SPACE' + capsuleState.background = '' throw error } finally { capsuleState.loading = false diff --git a/src/views/CapsuleDetail.vue b/src/views/CapsuleDetail.vue index 4ef83c7..fda698b 100644 --- a/src/views/CapsuleDetail.vue +++ b/src/views/CapsuleDetail.vue @@ -565,30 +565,22 @@ export default { } .open-button { - background: linear-gradient(135deg, var(--accent-color), #0099CC); - border: none; color: white; font-weight: bold; height: 50px; } .reply-button { - background: linear-gradient(135deg, #4ECDC4, #2A9D8F); - border: none; color: white; font-weight: bold; } .edit-button { - background: linear-gradient(135deg, #FFD166, #F77F00); - border: none; color: white; font-weight: bold; } .delete-button { - background: linear-gradient(135deg, #E63946, #A61E4D); - border: none; color: white; font-weight: bold; } diff --git a/src/views/Compose.vue b/src/views/Compose.vue index 20dc2b8..96108b3 100644 --- a/src/views/Compose.vue +++ b/src/views/Compose.vue @@ -17,7 +17,7 @@
-

收件人

+

收件人

自己 他人 @@ -34,7 +34,7 @@
-

发送时间

+

发送时间

预设时间 自定义 @@ -73,7 +73,7 @@
-

邮件内容

+

邮件内容

-

AI写作助手

+

AI写作助手

@@ -157,7 +157,6 @@ import { ref, onMounted, computed } from 'vue' import { useRouter } from 'vue-router' import { showLoadingToast, showSuccessToast, showFailToast, closeToast, Dialog } from 'vant' -import { mailActions, aiActions } from '../store' export default { name: 'Compose', @@ -343,16 +342,22 @@ export default { forbidClick: true, }) - const response = await aiActions.writingAssistant({ - prompt: '请为未来邮件生成一个开头', - type: 'OUTLINE', - tone: 'EMOTIONAL', - length: 'SHORT', - context: '写给未来的自己' - }) + // 模拟API调用延迟 + await new Promise(resolve => setTimeout(resolve, 2000)) + + // 模拟AI生成的开头 + const opennings = [ + "亲爱的未来的我,", + "时光荏苒,当你读到这封信时,", + "写给多年后的自己,", + "未来的我,你好!", + "此刻的我,想对未来的你说:" + ] + + const randomIndex = Math.floor(Math.random() * opennings.length) + content.value = opennings[randomIndex] + "\n\n" closeToast() - content.value = response.data.content showSuccessToast('已生成开头') } catch (error) { closeToast() @@ -368,18 +373,23 @@ export default { forbidClick: true, }) - const response = await aiActions.writingAssistant({ - prompt: '为未来邮件提供内容建议', - type: 'DRAFT', - tone: 'INSPIRATIONAL', - length: 'MEDIUM', - context: content.value || '写给未来的自己' - }) + // 模拟API调用延迟 + await new Promise(resolve => setTimeout(resolve, 2000)) + + // 模拟AI生成的内容建议 + const suggestions = [ + "你可以分享当前的生活状态、工作情况,以及对未来的期望和梦想。也可以记录下此刻的心情和思考,让未来的你能够回忆起这段时光。", + "考虑写下你现在的目标和计划,以及希望未来的自己已经实现了哪些。也可以询问未来的你某些问题的答案,比如'你幸福吗?'、'你成为想成为的人了吗?'。", + "你可以描述当前的世界、科技、文化等,让未来的你能够对比时代的变迁。也可以分享一些珍贵的回忆和瞬间,这些对你来说可能意义非凡。", + "写下你对未来的预测和想象,无论是对个人生活还是对整个世界。这些预测在将来读来会非常有趣,看看你猜对了多少。" + ] + + const randomIndex = Math.floor(Math.random() * suggestions.length) closeToast() Dialog.alert({ title: '内容建议', - message: response.data.content, + message: suggestions[randomIndex], }) } catch (error) { closeToast() @@ -400,18 +410,33 @@ export default { forbidClick: true, }) - const response = await aiActions.sentimentAnalysis({ - content: content.value - }) + // 模拟API调用延迟 + await new Promise(resolve => setTimeout(resolve, 1500)) + + // 模拟情感分析结果 + const sentiments = ['POSITIVE', 'NEUTRAL', 'EMOTIONAL', 'HOPEFUL'] + const emotions = [ + { type: 'HAPPY', score: 0.7 }, + { type: 'HOPEFUL', score: 0.8 }, + { type: 'NOSTALGIC', score: 0.5 } + ] + + const randomSentiment = sentiments[Math.floor(Math.random() * sentiments.length)] + const emotionTypes = emotions.map(e => e.type).join('、') + + const summaries = [ + "这封信充满了对未来的期待和希望,表达了积极向上的情感。", + "文字中透露出对过去时光的怀念和对未来的思考。", + "这封信情感真挚,表达了内心深处的感受和想法。", + "文字中既有对现实的思考,也有对未来的憧憬和规划。" + ] + + const randomSummary = summaries[Math.floor(Math.random() * summaries.length)] closeToast() - const sentiment = response.data.sentiment - const emotions = response.data.emotions.map(e => e.type).join('、') - const summary = response.data.summary - Dialog.alert({ title: '情感分析', - message: `情感倾向: ${sentiment}\n主要情感: ${emotions}\n分析: ${summary}`, + message: `情感倾向: ${randomSentiment}\n主要情感: ${emotionTypes}\n分析: ${randomSummary}`, }) } catch (error) { closeToast() @@ -497,8 +522,19 @@ export default { forbidClick: true, }) + // 模拟API调用延迟 + await new Promise(resolve => setTimeout(resolve, 1000)) + + // 保存到本地存储作为草稿 const mailData = buildMailData() - await mailActions.createMail(mailData) + const drafts = JSON.parse(localStorage.getItem('draftMails') || '[]') + drafts.push({ + ...mailData, + id: Date.now().toString(), + status: 'DRAFT', + createdAt: new Date().toISOString() + }) + localStorage.setItem('draftMails', JSON.stringify(drafts)) closeToast() showSuccessToast('草稿已保存') @@ -533,8 +569,20 @@ export default { forbidClick: true, }) + // 模拟API调用延迟 + await new Promise(resolve => setTimeout(resolve, 1500)) + const mailData = buildMailData() - const response = await mailActions.createMail(mailData) + + // 保存到本地存储作为已发送邮件 + const sentMails = JSON.parse(localStorage.getItem('sentMails') || '[]') + sentMails.push({ + ...mailData, + id: Date.now().toString(), + status: 'PENDING', + createdAt: new Date().toISOString() + }) + localStorage.setItem('sentMails', JSON.stringify(sentMails)) closeToast() @@ -649,6 +697,30 @@ export default { color: var(--text-primary); } +/* 单选按钮选中样式 */ +:deep(.van-radio-group) { + margin-bottom: 10px; +} + +:deep(.van-radio) { + margin-right: 15px; +} + +:deep(.van-radio__icon) { + font-size: 18px; +} + +:deep(.van-radio__icon--checked .van-icon) { + background-color: #4285f4; + border-color: #4285f4; + box-shadow: 0 0 8px rgba(66, 133, 244, 0.5); +} + +:deep(.van-radio__label) { + color: var(--text-primary); + font-size: 14px; +} + .preset-options { display: flex; flex-wrap: wrap; @@ -657,6 +729,12 @@ export default { .preset-button { margin-bottom: 10px; + transition: all 0.3s ease; +} + +.preset-button.van-button--primary { + transform: scale(1.05); + box-shadow: 0 4px 12px rgba(66, 133, 244, 0.4); } .custom-date-picker { @@ -681,65 +759,24 @@ export default { } .save-button { - background: linear-gradient(135deg, #4a5f7a, #2c3e50); - border: none; height: 50px; font-size: 16px; font-weight: bold; margin-bottom: 10px; - box-shadow: 0 8px 20px rgba(74, 95, 122, 0.3); - transition: all 0.3s ease; -} - -.save-button:hover { - transform: translateY(-2px); - box-shadow: 0 12px 25px rgba(74, 95, 122, 0.4); -} - -.save-button:active { - transform: translateY(0); - box-shadow: 0 5px 15px rgba(74, 95, 122, 0.3); } .send-button { - background: linear-gradient(135deg, #00D4FF, #1D3B5A); - border: none; height: 50px; font-size: 16px; font-weight: bold; - box-shadow: 0 8px 20px rgba(0, 212, 255, 0.3); - transition: all 0.3s ease; -} - -.send-button:hover { - transform: translateY(-2px); - box-shadow: 0 12px 25px rgba(0, 212, 255, 0.4); -} - -.send-button:active { - transform: translateY(0); - box-shadow: 0 5px 15px rgba(0, 212, 255, 0.3); } .preset-button { margin: 5px; - box-shadow: 0 4px 10px rgba(0, 212, 255, 0.2); - transition: all 0.3s ease; -} - -.preset-button:hover { - transform: translateY(-2px); - box-shadow: 0 6px 15px rgba(0, 212, 255, 0.3); } .media-uploader .van-button { - box-shadow: 0 4px 10px rgba(0, 212, 255, 0.2); - transition: all 0.3s ease; -} - -.media-uploader .van-button:hover { - transform: translateY(-2px); - box-shadow: 0 6px 15px rgba(0, 212, 255, 0.3); + /* 按钮样式已由全局样式处理 */ } .event-picker { diff --git a/src/views/Home.vue b/src/views/Home.vue index e529aee..7b98a3d 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -8,8 +8,8 @@
-

欢迎回来,{{ userName }}

-

{{ greetingText }}

+

欢迎回来,{{ userName }}

+

{{ greetingText }}

@@ -30,8 +30,8 @@ >
-

{{ capsule.title }}

-

{{ formatDate(capsule.deliveryDate) }}

+

{{ capsule.title }}

+

{{ formatDate(capsule.deliveryDate) }}

@@ -47,7 +47,6 @@ class="fab-button" @click="goToCompose" > - 撰写邮件
@@ -73,9 +72,9 @@
-

通知

+

通知

-

暂无新通知

+

暂无新通知

-

{{ notification.message }}

- {{ formatTime(notification.time) }} +

{{ notification.message }}

+ {{ formatTime(notification.time) }}
@@ -96,7 +95,7 @@ import { ref, onMounted, computed } from 'vue' import { useRouter } from 'vue-router' import { showFailToast } from 'vant' -import { userState, mailState, mailActions } from '../store' +import { userState, mailState, capsuleState, mailActions, capsuleActions } from '../store' export default { name: 'Home', @@ -111,7 +110,7 @@ export default { // 使用直接导入的状态和操作 const userName = computed(() => userState.userInfo.username || '时光旅人') - const capsules = computed(() => mailState.sentList || []) + const capsules = computed(() => capsuleState.capsules || []) const notifications = ref([]) // 暂时使用空数组,可以后续添加通知功能 // 根据时间获取问候语 @@ -202,7 +201,7 @@ export default { // 打开胶囊详情 const openCapsule = (capsule) => { - router.push(`/capsule/${capsule.id}`) + router.push(`/capsule/${capsule.mailId || capsule.capsuleId || capsule.id}`) } // 跳转到撰写页面 @@ -217,14 +216,18 @@ export default { return } - router.push(`/search?q=${encodeURIComponent(value)}`) + // 暂时显示提示,因为搜索页面尚未实现 + showFailToast(`搜索功能开发中,您搜索了: ${value}`) showSearch.value = false + + // TODO: 实现搜索功能后,可以跳转到搜索页面 + // router.push(`/search?q=${encodeURIComponent(value)}`) } // 获取时光胶囊数据 const fetchCapsules = async () => { try { - await mailActions.getCapsules() + await capsuleActions.getCapsules() } catch (error) { showFailToast('获取时光胶囊数据失败') } @@ -233,7 +236,20 @@ export default { // 获取通知数据 const fetchNotifications = async () => { try { - await mailActions.getNotifications() + // 暂时使用模拟数据,因为还没有实现通知API + notifications.value = [ + { + id: 1, + message: '您有一封来自未来的信件即将到达', + time: new Date(Date.now() - 1000 * 60 * 30).toISOString() // 30分钟前 + }, + { + id: 2, + message: '系统维护通知:今晚23:00-24:00系统将进行维护', + time: new Date(Date.now() - 1000 * 60 * 60 * 2).toISOString() // 2小时前 + } + ] + console.log('通知功能使用模拟数据') } catch (error) { console.error('获取通知失败:', error) } @@ -395,9 +411,7 @@ export default { width: 60px; height: 60px; border-radius: 50%; - background: linear-gradient(135deg, #00D4FF, #1D3B5A); border: none; - box-shadow: 0 4px 12px rgba(0, 212, 255, 0.4); font-size: 14px; } diff --git a/src/views/Login.vue b/src/views/Login.vue index 29bfb3f..20bd36a 100644 --- a/src/views/Login.vue +++ b/src/views/Login.vue @@ -11,8 +11,8 @@ -

ChronoMail

-

写给未来,不负当下

+

ChronoMail

+

写给未来,不负当下

@@ -38,7 +38,7 @@ 登录
@@ -186,24 +186,10 @@ export default { } .login-button { - background: linear-gradient(135deg, #00D4FF, #1D3B5A); - border: none; height: 50px; font-size: 16px; font-weight: bold; margin-top: 20px; - box-shadow: 0 8px 20px rgba(0, 212, 255, 0.3); - transition: all 0.3s ease; -} - -.login-button:hover { - transform: translateY(-2px); - box-shadow: 0 12px 25px rgba(0, 212, 255, 0.4); -} - -.login-button:active { - transform: translateY(0); - box-shadow: 0 5px 15px rgba(0, 212, 255, 0.3); } .register-link { diff --git a/src/views/Profile.vue b/src/views/Profile.vue index 4c63e20..21a97a5 100644 --- a/src/views/Profile.vue +++ b/src/views/Profile.vue @@ -141,7 +141,7 @@ import { ref, onMounted, computed } from 'vue' import { useRouter } from 'vue-router' import { showFailToast, showSuccessToast, Dialog } from 'vant' -import { userActions, mailActions, userState } from '../store' +import { userActions, mailActions, userState, statisticsActions } from '../store' export default { name: 'Profile', @@ -168,12 +168,13 @@ export default { // 获取用户统计数据 const fetchStatistics = async () => { try { - const response = await mailActions.getStatistics() - const data = response.data + const response = await statisticsActions.getStatistics() - sentCount.value = data.totalSent || 0 - receivedCount.value = data.totalReceived || 0 - totalDays.value = data.timeTravelDuration || 0 + if (response && response.data) { + sentCount.value = response.data.totalSent || 0 + receivedCount.value = response.data.totalReceived || 0 + totalDays.value = response.data.timeTravelDuration || 0 + } } catch (error) { console.error('获取统计数据失败:', error) } @@ -182,12 +183,13 @@ export default { // 获取用户信息 const fetchUserProfile = async () => { try { - const response = await userActions.getProfile() - const userData = response.data + const userData = await userActions.fetchUserInfo() - userName.value = userData.username || '时光旅人' - userEmail.value = userData.email || 'traveler@chronomail.com' - userAvatar.value = userData.avatar || 'https://picsum.photos/seed/user123/100/100.jpg' + if (userData) { + userName.value = userData.username || '时光旅人' + userEmail.value = userData.email || 'traveler@chronomail.com' + userAvatar.value = userData.avatar || 'https://picsum.photos/seed/user123/100/100.jpg' + } } catch (error) { console.error('获取用户信息失败:', error) } @@ -449,24 +451,10 @@ export default { } .logout-button { - background: linear-gradient(135deg, #ff4d4f, #c41e3a); - border: none; height: 50px; font-size: 16px; font-weight: bold; margin-top: 20px; - box-shadow: 0 8px 20px rgba(255, 77, 79, 0.3); - transition: all 0.3s ease; -} - -.logout-button:hover { - transform: translateY(-2px); - box-shadow: 0 12px 25px rgba(255, 77, 79, 0.4); -} - -.logout-button:active { - transform: translateY(0); - box-shadow: 0 5px 15px rgba(255, 77, 79, 0.3); } .about-popup { diff --git a/src/views/Register.vue b/src/views/Register.vue index eb5e538..4e13e04 100644 --- a/src/views/Register.vue +++ b/src/views/Register.vue @@ -224,24 +224,10 @@ export default { } .register-button { - background: linear-gradient(135deg, #00D4FF, #1D3B5A); - border: none; height: 50px; font-size: 16px; font-weight: bold; margin-top: 20px; - box-shadow: 0 8px 20px rgba(0, 212, 255, 0.3); - transition: all 0.3s ease; -} - -.register-button:hover { - transform: translateY(-2px); - box-shadow: 0 12px 25px rgba(0, 212, 255, 0.4); -} - -.register-button:active { - transform: translateY(0); - box-shadow: 0 5px 15px rgba(0, 212, 255, 0.3); } .login-link { diff --git a/src/views/Sent.vue b/src/views/Sent.vue index 9e06f3d..28d905e 100644 --- a/src/views/Sent.vue +++ b/src/views/Sent.vue @@ -31,12 +31,12 @@
-

{{ mail.title }}

+

{{ mail.title || '无标题' }}

{{ formatDate(mail.sendTime) }}
-

{{ mail.content.substring(0, 50) }}...

+

{{ (mail.content || '').substring(0, 50) }}...

-
+
@@ -155,6 +155,8 @@ export default { // 排序后的邮件 const sortedMails = computed(() => { + if (!mails.value || !Array.isArray(mails.value)) return [] + const sorted = [...mails.value] switch (sortType.value) { @@ -221,15 +223,16 @@ export default { size: pageSize.value }) - if (response.code === 200) { + // 响应拦截器已经处理了success判断,这里直接处理数据 + if (response && response.data) { // 判断是否加载完成 - if (response.data.list.length < pageSize.value) { + if (!response.data.list || response.data.list.length < pageSize.value) { finished.value = true } else { page.value += 1 } } else { - showFailToast(response.message || '获取邮件列表失败') + showFailToast('获取邮件列表失败') } } catch (error) { console.error('获取邮件列表失败:', error) diff --git a/src/views/Timeline.vue b/src/views/Timeline.vue index 6793e2f..5f322d6 100644 --- a/src/views/Timeline.vue +++ b/src/views/Timeline.vue @@ -512,8 +512,6 @@ export default { } .filter-apply { - background: linear-gradient(135deg, var(--accent-color), #0099CC); - border: none; color: white; font-weight: bold; } diff --git a/vue.config.js b/vue.config.js index d861c84..a6853be 100644 --- a/vue.config.js +++ b/vue.config.js @@ -8,7 +8,7 @@ module.exports = defineConfig({ // 反向代理5001 proxy: { '/api': { - target: 'http://localhost:5001', + target: 'http://localhost:5003', changeOrigin: true, secure: false }