This commit is contained in:
2025-10-16 16:21:56 +08:00
parent dd3936944b
commit 89dbdc63db
12 changed files with 512 additions and 222 deletions

View File

@@ -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过期

View File

@@ -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;
}

View File

@@ -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,9 +138,10 @@ export const userActions = {
userState.token = ''
userState.refreshToken = ''
userState.userInfo = {
userId: '',
id: '',
username: '',
email: '',
nickname: '',
avatar: ''
}
},
@@ -148,6 +155,7 @@ export const userActions = {
}
const res = await api.auth.refreshToken(refreshToken)
// 根据后端实际返回的数据结构提取数据
const { token: newToken, refreshToken: newRefreshToken } = res.data
// 更新本地存储
@@ -170,11 +178,13 @@ export const userActions = {
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

View File

@@ -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;
}

View File

@@ -17,7 +17,7 @@
<div class="form-section glass-card p-20">
<!-- 收件人选择 -->
<div class="form-group">
<h3>收件人</h3>
<h3 class="font-semibold">收件人</h3>
<van-radio-group v-model="recipientType" direction="horizontal">
<van-radio name="SELF">自己</van-radio>
<van-radio name="SPECIFIC">他人</van-radio>
@@ -34,7 +34,7 @@
<!-- 发送时间选择 -->
<div class="form-group mt-20">
<h3>发送时间</h3>
<h3 class="font-semibold">发送时间</h3>
<van-radio-group v-model="timeType" direction="horizontal">
<van-radio name="preset">预设时间</van-radio>
<van-radio name="custom">自定义</van-radio>
@@ -73,7 +73,7 @@
<!-- 邮件内容 -->
<div class="form-group mt-20">
<h3>邮件内容</h3>
<h3 class="font-semibold">邮件内容</h3>
<van-field
v-model="subject"
placeholder="标题"
@@ -106,7 +106,7 @@
<!-- AI助手 -->
<div class="form-group mt-20">
<h3>AI写作助手</h3>
<h3 class="font-semibold">AI写作助手</h3>
<van-cell-group>
<van-cell title="生成开头" is-link @click="generateOpening" />
<van-cell title="内容建议" is-link @click="generateSuggestions" />
@@ -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 {

View File

@@ -8,8 +8,8 @@
<!-- 顶部欢迎语 -->
<div class="header glass-card">
<div class="welcome-text">
<h2>欢迎回来{{ userName }}</h2>
<p>{{ greetingText }}</p>
<h2 class="gradient-text">欢迎回来{{ userName }}</h2>
<p class="text-secondary text-sm">{{ greetingText }}</p>
</div>
<div class="header-actions">
<van-icon name="search" size="24" @click="showSearch = true" />
@@ -30,8 +30,8 @@
>
<div class="time-capsule" :class="{'glowing': capsule.isGlowing}">
<div class="capsule-info">
<p class="capsule-title">{{ capsule.title }}</p>
<p class="capsule-date">{{ formatDate(capsule.deliveryDate) }}</p>
<p class="capsule-title font-semibold">{{ capsule.title }}</p>
<p class="capsule-date text-xs text-secondary">{{ formatDate(capsule.deliveryDate) }}</p>
</div>
</div>
</div>
@@ -47,7 +47,6 @@
class="fab-button"
@click="goToCompose"
>
撰写邮件
</van-button>
</div>
@@ -73,9 +72,9 @@
<!-- 通知弹窗 -->
<van-popup v-model:show="showNotifications" position="top" :style="{ height: '40%' }">
<div class="notifications-popup">
<h3>通知</h3>
<h3 class="font-semibold">通知</h3>
<div v-if="notifications.length === 0" class="empty-notifications">
<p>暂无新通知</p>
<p class="text-secondary text-sm">暂无新通知</p>
</div>
<div v-else>
<div
@@ -83,8 +82,8 @@
:key="notification.id"
class="notification-item"
>
<p>{{ notification.message }}</p>
<span class="notification-time">{{ formatTime(notification.time) }}</span>
<p class="text-sm">{{ notification.message }}</p>
<span class="notification-time text-xs text-secondary">{{ formatTime(notification.time) }}</span>
</div>
</div>
</div>
@@ -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;
}

View File

@@ -11,8 +11,8 @@
<div class="logo">
<div class="time-capsule"></div>
</div>
<h1 class="app-title">ChronoMail</h1>
<p class="app-slogan">写给未来不负当下</p>
<h1 class="app-title gradient-text">ChronoMail</h1>
<p class="app-slogan text-secondary text-sm">写给未来不负当下</p>
</div>
<!-- 登录表单 -->
@@ -38,7 +38,7 @@
登录
</van-button>
<div class="register-link mt-20">
还没有账号<span @click="goToRegister">立即注册</span>
<span class="text-secondary text-sm">还没有账号</span><span class="text-accent font-medium" @click="goToRegister">立即注册</span>
</div>
</div>
</van-form>
@@ -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 {

View File

@@ -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()
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 {

View File

@@ -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 {

View File

@@ -31,12 +31,12 @@
</div>
<div class="mail-content">
<div class="mail-header">
<h3 class="mail-title">{{ mail.title }}</h3>
<h3 class="mail-title">{{ mail.title || '无标题' }}</h3>
<span class="mail-date">{{ formatDate(mail.sendTime) }}</span>
</div>
<p class="mail-preview">{{ mail.content.substring(0, 50) }}...</p>
<p class="mail-preview">{{ (mail.content || '').substring(0, 50) }}...</p>
<div class="mail-footer">
<span class="mail-recipient">收件人: {{ mail.recipient.username }}</span>
<span class="mail-recipient">收件人: {{ mail.recipient?.username || '未知' }}</span>
<van-tag :type="getStatusType(mail.status)" size="small">
{{ getStatusText(mail.status) }}
</van-tag>
@@ -62,7 +62,7 @@
</div>
</van-list>
<div v-if="mails.value.length === 0 && !loading" class="empty-state">
<div v-if="mails && mails.length === 0 && !loading" class="empty-state">
<van-empty description="暂无已发送的邮件" />
</div>
</div>
@@ -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)

View File

@@ -512,8 +512,6 @@ export default {
}
.filter-apply {
background: linear-gradient(135deg, var(--accent-color), #0099CC);
border: none;
color: white;
font-weight: bold;
}

View File

@@ -8,7 +8,7 @@ module.exports = defineConfig({
// 反向代理5001
proxy: {
'/api': {
target: 'http://localhost:5001',
target: 'http://localhost:5003',
changeOrigin: true,
secure: false
}