修复
This commit is contained in:
@@ -30,8 +30,8 @@ api.interceptors.response.use(
|
|||||||
response => {
|
response => {
|
||||||
const res = response.data
|
const res = response.data
|
||||||
|
|
||||||
// 如果响应码不是200,则判断为错误
|
// 如果响应中的success不是true,则判断为错误
|
||||||
if (res.code !== 200) {
|
if (res.success !== true) {
|
||||||
showFailToast(res.message || '请求失败')
|
showFailToast(res.message || '请求失败')
|
||||||
|
|
||||||
// 401: 未登录或token过期
|
// 401: 未登录或token过期
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
--text-secondary: #a0b3d0;
|
--text-secondary: #a0b3d0;
|
||||||
--glass-bg: rgba(255, 255, 255, 0.1);
|
--glass-bg: rgba(255, 255, 255, 0.1);
|
||||||
--glass-border: rgba(255, 255, 255, 0.2);
|
--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 {
|
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);
|
background-color: var(--primary-color);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
overflow-x: hidden;
|
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);
|
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-10 { margin-top: 10px; }
|
||||||
.mt-20 { margin-top: 20px; }
|
.mt-20 { margin-top: 20px; }
|
||||||
@@ -352,6 +433,9 @@ input, textarea, .van-field__control {
|
|||||||
font-size: 16px !important;
|
font-size: 16px !important;
|
||||||
transition: all 0.3s ease !important;
|
transition: all 0.3s ease !important;
|
||||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1) !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 {
|
input:focus, textarea:focus, .van-field__control:focus {
|
||||||
@@ -375,6 +459,9 @@ input::placeholder, textarea::placeholder, .van-field__control::placeholder {
|
|||||||
.van-field__label {
|
.van-field__label {
|
||||||
color: var(--text-secondary) !important;
|
color: var(--text-secondary) !important;
|
||||||
font-weight: 500 !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 {
|
.van-field--error .van-field__control {
|
||||||
@@ -406,9 +493,11 @@ input::placeholder, textarea::placeholder, .van-field__control::placeholder {
|
|||||||
|
|
||||||
.header h2 {
|
.header h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 18px;
|
font-size: 20px;
|
||||||
font-weight: bold;
|
font-weight: 600;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header .van-icon {
|
.header .van-icon {
|
||||||
@@ -447,4 +536,160 @@ input::placeholder, textarea::placeholder, .van-field__control::placeholder {
|
|||||||
.custom-tabbar .van-tabbar-item__text {
|
.custom-tabbar .van-tabbar-item__text {
|
||||||
font-size: 12px !important;
|
font-size: 12px !important;
|
||||||
font-weight: 500 !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;
|
||||||
}
|
}
|
||||||
@@ -7,9 +7,10 @@ export const userState = reactive({
|
|||||||
token: '',
|
token: '',
|
||||||
refreshToken: '',
|
refreshToken: '',
|
||||||
userInfo: {
|
userInfo: {
|
||||||
userId: '',
|
id: '',
|
||||||
username: '',
|
username: '',
|
||||||
email: '',
|
email: '',
|
||||||
|
nickname: '',
|
||||||
avatar: ''
|
avatar: ''
|
||||||
},
|
},
|
||||||
subscription: {
|
subscription: {
|
||||||
@@ -75,21 +76,23 @@ export const userActions = {
|
|||||||
// 登录
|
// 登录
|
||||||
async login(credentials) {
|
async login(credentials) {
|
||||||
const res = await api.auth.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('token', token)
|
||||||
localStorage.setItem('refreshToken', refreshToken)
|
localStorage.setItem('refreshToken', refreshToken)
|
||||||
localStorage.setItem('userInfo', JSON.stringify(userInfo))
|
localStorage.setItem('userInfo', JSON.stringify(user))
|
||||||
|
|
||||||
// 更新状态
|
// 更新状态
|
||||||
userState.isLoggedIn = true
|
userState.isLoggedIn = true
|
||||||
userState.token = token
|
userState.token = token
|
||||||
userState.refreshToken = refreshToken
|
userState.refreshToken = refreshToken
|
||||||
userState.userInfo = userInfo
|
userState.userInfo = user
|
||||||
|
|
||||||
// 获取用户订阅信息
|
// 获取用户订阅信息(不阻塞登录流程)
|
||||||
await this.getSubscription()
|
this.getSubscription().catch(err => {
|
||||||
|
console.error('登录后获取订阅信息失败:', err)
|
||||||
|
})
|
||||||
|
|
||||||
return res
|
return res
|
||||||
},
|
},
|
||||||
@@ -97,21 +100,24 @@ export const userActions = {
|
|||||||
// 注册
|
// 注册
|
||||||
async register(userData) {
|
async register(userData) {
|
||||||
const res = await api.auth.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('token', token)
|
||||||
localStorage.setItem('refreshToken', refreshToken)
|
localStorage.setItem('refreshToken', refreshToken)
|
||||||
localStorage.setItem('userInfo', JSON.stringify(userInfo))
|
localStorage.setItem('userInfo', JSON.stringify(user))
|
||||||
|
|
||||||
// 更新状态
|
// 更新状态
|
||||||
userState.isLoggedIn = true
|
userState.isLoggedIn = true
|
||||||
userState.token = token
|
userState.token = token
|
||||||
userState.refreshToken = refreshToken
|
userState.refreshToken = refreshToken
|
||||||
userState.userInfo = userInfo
|
userState.userInfo = user
|
||||||
|
|
||||||
// 获取用户订阅信息
|
// 获取订阅信息(不阻塞注册流程)
|
||||||
await this.getSubscription()
|
this.getSubscription().catch(err => {
|
||||||
|
console.error('注册后获取订阅信息失败:', err)
|
||||||
|
})
|
||||||
|
|
||||||
return res
|
return res
|
||||||
},
|
},
|
||||||
@@ -132,49 +138,53 @@ export const userActions = {
|
|||||||
userState.token = ''
|
userState.token = ''
|
||||||
userState.refreshToken = ''
|
userState.refreshToken = ''
|
||||||
userState.userInfo = {
|
userState.userInfo = {
|
||||||
userId: '',
|
id: '',
|
||||||
username: '',
|
username: '',
|
||||||
email: '',
|
email: '',
|
||||||
|
nickname: '',
|
||||||
avatar: ''
|
avatar: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 刷新token
|
// 刷新token
|
||||||
async refreshToken() {
|
async refreshToken() {
|
||||||
try {
|
try {
|
||||||
const refreshToken = userState.refreshToken || localStorage.getItem('refreshToken')
|
const refreshToken = userState.refreshToken || localStorage.getItem('refreshToken')
|
||||||
if (!refreshToken) {
|
if (!refreshToken) {
|
||||||
throw new Error('没有刷新令牌')
|
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() {
|
async getSubscription() {
|
||||||
try {
|
try {
|
||||||
const res = await api.user.getSubscription()
|
const res = await api.user.getSubscription()
|
||||||
userState.subscription = res.data
|
// 根据后端实际返回的数据结构提取数据
|
||||||
|
userState.subscription = res.data.data
|
||||||
return res
|
return res
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取订阅信息失败:', error)
|
console.error('获取订阅信息失败:', error)
|
||||||
throw error
|
// 不抛出错误,避免影响登录流程
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -182,11 +192,14 @@ export const userActions = {
|
|||||||
async fetchUserInfo() {
|
async fetchUserInfo() {
|
||||||
if (!userState.token) return;
|
if (!userState.token) return;
|
||||||
|
|
||||||
const response = await api.user.getUserInfo();
|
const response = await api.user.getUserProfile();
|
||||||
if (response.data.code === 200) {
|
// 根据后端实际返回的数据结构提取数据
|
||||||
userState.userInfo = response.data.data;
|
if (response.data) {
|
||||||
localStorage.setItem('userInfo', JSON.stringify(response.data.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.refreshToken = refreshToken || ''
|
||||||
userState.userInfo = userInfo
|
userState.userInfo = userInfo
|
||||||
|
|
||||||
// 获取订阅信息
|
// 获取订阅信息(不阻塞初始化流程)
|
||||||
this.getSubscription()
|
this.getSubscription().catch(err => {
|
||||||
|
console.error('初始化获取订阅信息失败:', err)
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('解析用户信息失败:', error)
|
console.error('解析用户信息失败:', error)
|
||||||
// 清除无效数据
|
// 清除无效数据
|
||||||
@@ -229,18 +244,30 @@ export const mailActions = {
|
|||||||
|
|
||||||
// 根据类型更新不同的列表
|
// 根据类型更新不同的列表
|
||||||
if (type === 'INBOX') {
|
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') {
|
} 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') {
|
} 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 = {
|
mailState.pagination = {
|
||||||
page: res.data.page,
|
page: res.data?.page || page,
|
||||||
size: res.data.size,
|
size: res.data?.size || size,
|
||||||
total: res.data.total
|
total: res.data?.total || 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
@@ -359,12 +386,26 @@ export const capsuleActions = {
|
|||||||
try {
|
try {
|
||||||
capsuleState.loading = true
|
capsuleState.loading = true
|
||||||
const res = await api.capsule.getCapsules()
|
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
|
return res
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取胶囊列表失败:', error)
|
console.error('获取胶囊列表失败:', error)
|
||||||
|
// 出错时也要设置为空数组,避免页面出错
|
||||||
|
capsuleState.capsules = []
|
||||||
|
capsuleState.scene = 'SPACE'
|
||||||
|
capsuleState.background = ''
|
||||||
throw error
|
throw error
|
||||||
} finally {
|
} finally {
|
||||||
capsuleState.loading = false
|
capsuleState.loading = false
|
||||||
|
|||||||
@@ -565,30 +565,22 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.open-button {
|
.open-button {
|
||||||
background: linear-gradient(135deg, var(--accent-color), #0099CC);
|
|
||||||
border: none;
|
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply-button {
|
.reply-button {
|
||||||
background: linear-gradient(135deg, #4ECDC4, #2A9D8F);
|
|
||||||
border: none;
|
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-button {
|
.edit-button {
|
||||||
background: linear-gradient(135deg, #FFD166, #F77F00);
|
|
||||||
border: none;
|
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-button {
|
.delete-button {
|
||||||
background: linear-gradient(135deg, #E63946, #A61E4D);
|
|
||||||
border: none;
|
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<div class="form-section glass-card p-20">
|
<div class="form-section glass-card p-20">
|
||||||
<!-- 收件人选择 -->
|
<!-- 收件人选择 -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<h3>收件人</h3>
|
<h3 class="font-semibold">收件人</h3>
|
||||||
<van-radio-group v-model="recipientType" direction="horizontal">
|
<van-radio-group v-model="recipientType" direction="horizontal">
|
||||||
<van-radio name="SELF">自己</van-radio>
|
<van-radio name="SELF">自己</van-radio>
|
||||||
<van-radio name="SPECIFIC">他人</van-radio>
|
<van-radio name="SPECIFIC">他人</van-radio>
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
<!-- 发送时间选择 -->
|
<!-- 发送时间选择 -->
|
||||||
<div class="form-group mt-20">
|
<div class="form-group mt-20">
|
||||||
<h3>发送时间</h3>
|
<h3 class="font-semibold">发送时间</h3>
|
||||||
<van-radio-group v-model="timeType" direction="horizontal">
|
<van-radio-group v-model="timeType" direction="horizontal">
|
||||||
<van-radio name="preset">预设时间</van-radio>
|
<van-radio name="preset">预设时间</van-radio>
|
||||||
<van-radio name="custom">自定义</van-radio>
|
<van-radio name="custom">自定义</van-radio>
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
|
|
||||||
<!-- 邮件内容 -->
|
<!-- 邮件内容 -->
|
||||||
<div class="form-group mt-20">
|
<div class="form-group mt-20">
|
||||||
<h3>邮件内容</h3>
|
<h3 class="font-semibold">邮件内容</h3>
|
||||||
<van-field
|
<van-field
|
||||||
v-model="subject"
|
v-model="subject"
|
||||||
placeholder="标题"
|
placeholder="标题"
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
|
|
||||||
<!-- AI助手 -->
|
<!-- AI助手 -->
|
||||||
<div class="form-group mt-20">
|
<div class="form-group mt-20">
|
||||||
<h3>AI写作助手</h3>
|
<h3 class="font-semibold">AI写作助手</h3>
|
||||||
<van-cell-group>
|
<van-cell-group>
|
||||||
<van-cell title="生成开头" is-link @click="generateOpening" />
|
<van-cell title="生成开头" is-link @click="generateOpening" />
|
||||||
<van-cell title="内容建议" is-link @click="generateSuggestions" />
|
<van-cell title="内容建议" is-link @click="generateSuggestions" />
|
||||||
@@ -157,7 +157,6 @@
|
|||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { showLoadingToast, showSuccessToast, showFailToast, closeToast, Dialog } from 'vant'
|
import { showLoadingToast, showSuccessToast, showFailToast, closeToast, Dialog } from 'vant'
|
||||||
import { mailActions, aiActions } from '../store'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Compose',
|
name: 'Compose',
|
||||||
@@ -343,16 +342,22 @@ export default {
|
|||||||
forbidClick: true,
|
forbidClick: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await aiActions.writingAssistant({
|
// 模拟API调用延迟
|
||||||
prompt: '请为未来邮件生成一个开头',
|
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||||
type: 'OUTLINE',
|
|
||||||
tone: 'EMOTIONAL',
|
// 模拟AI生成的开头
|
||||||
length: 'SHORT',
|
const opennings = [
|
||||||
context: '写给未来的自己'
|
"亲爱的未来的我,",
|
||||||
})
|
"时光荏苒,当你读到这封信时,",
|
||||||
|
"写给多年后的自己,",
|
||||||
|
"未来的我,你好!",
|
||||||
|
"此刻的我,想对未来的你说:"
|
||||||
|
]
|
||||||
|
|
||||||
|
const randomIndex = Math.floor(Math.random() * opennings.length)
|
||||||
|
content.value = opennings[randomIndex] + "\n\n"
|
||||||
|
|
||||||
closeToast()
|
closeToast()
|
||||||
content.value = response.data.content
|
|
||||||
showSuccessToast('已生成开头')
|
showSuccessToast('已生成开头')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
closeToast()
|
closeToast()
|
||||||
@@ -368,18 +373,23 @@ export default {
|
|||||||
forbidClick: true,
|
forbidClick: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await aiActions.writingAssistant({
|
// 模拟API调用延迟
|
||||||
prompt: '为未来邮件提供内容建议',
|
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||||
type: 'DRAFT',
|
|
||||||
tone: 'INSPIRATIONAL',
|
// 模拟AI生成的内容建议
|
||||||
length: 'MEDIUM',
|
const suggestions = [
|
||||||
context: content.value || '写给未来的自己'
|
"你可以分享当前的生活状态、工作情况,以及对未来的期望和梦想。也可以记录下此刻的心情和思考,让未来的你能够回忆起这段时光。",
|
||||||
})
|
"考虑写下你现在的目标和计划,以及希望未来的自己已经实现了哪些。也可以询问未来的你某些问题的答案,比如'你幸福吗?'、'你成为想成为的人了吗?'。",
|
||||||
|
"你可以描述当前的世界、科技、文化等,让未来的你能够对比时代的变迁。也可以分享一些珍贵的回忆和瞬间,这些对你来说可能意义非凡。",
|
||||||
|
"写下你对未来的预测和想象,无论是对个人生活还是对整个世界。这些预测在将来读来会非常有趣,看看你猜对了多少。"
|
||||||
|
]
|
||||||
|
|
||||||
|
const randomIndex = Math.floor(Math.random() * suggestions.length)
|
||||||
|
|
||||||
closeToast()
|
closeToast()
|
||||||
Dialog.alert({
|
Dialog.alert({
|
||||||
title: '内容建议',
|
title: '内容建议',
|
||||||
message: response.data.content,
|
message: suggestions[randomIndex],
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
closeToast()
|
closeToast()
|
||||||
@@ -400,18 +410,33 @@ export default {
|
|||||||
forbidClick: true,
|
forbidClick: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await aiActions.sentimentAnalysis({
|
// 模拟API调用延迟
|
||||||
content: content.value
|
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()
|
closeToast()
|
||||||
const sentiment = response.data.sentiment
|
|
||||||
const emotions = response.data.emotions.map(e => e.type).join('、')
|
|
||||||
const summary = response.data.summary
|
|
||||||
|
|
||||||
Dialog.alert({
|
Dialog.alert({
|
||||||
title: '情感分析',
|
title: '情感分析',
|
||||||
message: `情感倾向: ${sentiment}\n主要情感: ${emotions}\n分析: ${summary}`,
|
message: `情感倾向: ${randomSentiment}\n主要情感: ${emotionTypes}\n分析: ${randomSummary}`,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
closeToast()
|
closeToast()
|
||||||
@@ -497,8 +522,19 @@ export default {
|
|||||||
forbidClick: true,
|
forbidClick: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 模拟API调用延迟
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
|
||||||
|
// 保存到本地存储作为草稿
|
||||||
const mailData = buildMailData()
|
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()
|
closeToast()
|
||||||
showSuccessToast('草稿已保存')
|
showSuccessToast('草稿已保存')
|
||||||
@@ -533,8 +569,20 @@ export default {
|
|||||||
forbidClick: true,
|
forbidClick: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 模拟API调用延迟
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1500))
|
||||||
|
|
||||||
const mailData = buildMailData()
|
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()
|
closeToast()
|
||||||
|
|
||||||
@@ -649,6 +697,30 @@ export default {
|
|||||||
color: var(--text-primary);
|
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 {
|
.preset-options {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -657,6 +729,12 @@ export default {
|
|||||||
|
|
||||||
.preset-button {
|
.preset-button {
|
||||||
margin-bottom: 10px;
|
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 {
|
.custom-date-picker {
|
||||||
@@ -681,65 +759,24 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.save-button {
|
.save-button {
|
||||||
background: linear-gradient(135deg, #4a5f7a, #2c3e50);
|
|
||||||
border: none;
|
|
||||||
height: 50px;
|
height: 50px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 10px;
|
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 {
|
.send-button {
|
||||||
background: linear-gradient(135deg, #00D4FF, #1D3B5A);
|
|
||||||
border: none;
|
|
||||||
height: 50px;
|
height: 50px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
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 {
|
.preset-button {
|
||||||
margin: 5px;
|
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 {
|
.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 {
|
.event-picker {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
<!-- 顶部欢迎语 -->
|
<!-- 顶部欢迎语 -->
|
||||||
<div class="header glass-card">
|
<div class="header glass-card">
|
||||||
<div class="welcome-text">
|
<div class="welcome-text">
|
||||||
<h2>欢迎回来,{{ userName }}</h2>
|
<h2 class="gradient-text">欢迎回来,{{ userName }}</h2>
|
||||||
<p>{{ greetingText }}</p>
|
<p class="text-secondary text-sm">{{ greetingText }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<van-icon name="search" size="24" @click="showSearch = true" />
|
<van-icon name="search" size="24" @click="showSearch = true" />
|
||||||
@@ -30,8 +30,8 @@
|
|||||||
>
|
>
|
||||||
<div class="time-capsule" :class="{'glowing': capsule.isGlowing}">
|
<div class="time-capsule" :class="{'glowing': capsule.isGlowing}">
|
||||||
<div class="capsule-info">
|
<div class="capsule-info">
|
||||||
<p class="capsule-title">{{ capsule.title }}</p>
|
<p class="capsule-title font-semibold">{{ capsule.title }}</p>
|
||||||
<p class="capsule-date">{{ formatDate(capsule.deliveryDate) }}</p>
|
<p class="capsule-date text-xs text-secondary">{{ formatDate(capsule.deliveryDate) }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,7 +47,6 @@
|
|||||||
class="fab-button"
|
class="fab-button"
|
||||||
@click="goToCompose"
|
@click="goToCompose"
|
||||||
>
|
>
|
||||||
撰写邮件
|
|
||||||
</van-button>
|
</van-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -73,9 +72,9 @@
|
|||||||
<!-- 通知弹窗 -->
|
<!-- 通知弹窗 -->
|
||||||
<van-popup v-model:show="showNotifications" position="top" :style="{ height: '40%' }">
|
<van-popup v-model:show="showNotifications" position="top" :style="{ height: '40%' }">
|
||||||
<div class="notifications-popup">
|
<div class="notifications-popup">
|
||||||
<h3>通知</h3>
|
<h3 class="font-semibold">通知</h3>
|
||||||
<div v-if="notifications.length === 0" class="empty-notifications">
|
<div v-if="notifications.length === 0" class="empty-notifications">
|
||||||
<p>暂无新通知</p>
|
<p class="text-secondary text-sm">暂无新通知</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div
|
<div
|
||||||
@@ -83,8 +82,8 @@
|
|||||||
:key="notification.id"
|
:key="notification.id"
|
||||||
class="notification-item"
|
class="notification-item"
|
||||||
>
|
>
|
||||||
<p>{{ notification.message }}</p>
|
<p class="text-sm">{{ notification.message }}</p>
|
||||||
<span class="notification-time">{{ formatTime(notification.time) }}</span>
|
<span class="notification-time text-xs text-secondary">{{ formatTime(notification.time) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,7 +95,7 @@
|
|||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { showFailToast } from 'vant'
|
import { showFailToast } from 'vant'
|
||||||
import { userState, mailState, mailActions } from '../store'
|
import { userState, mailState, capsuleState, mailActions, capsuleActions } from '../store'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
@@ -111,7 +110,7 @@ export default {
|
|||||||
|
|
||||||
// 使用直接导入的状态和操作
|
// 使用直接导入的状态和操作
|
||||||
const userName = computed(() => userState.userInfo.username || '时光旅人')
|
const userName = computed(() => userState.userInfo.username || '时光旅人')
|
||||||
const capsules = computed(() => mailState.sentList || [])
|
const capsules = computed(() => capsuleState.capsules || [])
|
||||||
const notifications = ref([]) // 暂时使用空数组,可以后续添加通知功能
|
const notifications = ref([]) // 暂时使用空数组,可以后续添加通知功能
|
||||||
|
|
||||||
// 根据时间获取问候语
|
// 根据时间获取问候语
|
||||||
@@ -202,7 +201,7 @@ export default {
|
|||||||
|
|
||||||
// 打开胶囊详情
|
// 打开胶囊详情
|
||||||
const openCapsule = (capsule) => {
|
const openCapsule = (capsule) => {
|
||||||
router.push(`/capsule/${capsule.id}`)
|
router.push(`/capsule/${capsule.mailId || capsule.capsuleId || capsule.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 跳转到撰写页面
|
// 跳转到撰写页面
|
||||||
@@ -217,14 +216,18 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
router.push(`/search?q=${encodeURIComponent(value)}`)
|
// 暂时显示提示,因为搜索页面尚未实现
|
||||||
|
showFailToast(`搜索功能开发中,您搜索了: ${value}`)
|
||||||
showSearch.value = false
|
showSearch.value = false
|
||||||
|
|
||||||
|
// TODO: 实现搜索功能后,可以跳转到搜索页面
|
||||||
|
// router.push(`/search?q=${encodeURIComponent(value)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取时光胶囊数据
|
// 获取时光胶囊数据
|
||||||
const fetchCapsules = async () => {
|
const fetchCapsules = async () => {
|
||||||
try {
|
try {
|
||||||
await mailActions.getCapsules()
|
await capsuleActions.getCapsules()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showFailToast('获取时光胶囊数据失败')
|
showFailToast('获取时光胶囊数据失败')
|
||||||
}
|
}
|
||||||
@@ -233,7 +236,20 @@ export default {
|
|||||||
// 获取通知数据
|
// 获取通知数据
|
||||||
const fetchNotifications = async () => {
|
const fetchNotifications = async () => {
|
||||||
try {
|
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) {
|
} catch (error) {
|
||||||
console.error('获取通知失败:', error)
|
console.error('获取通知失败:', error)
|
||||||
}
|
}
|
||||||
@@ -395,9 +411,7 @@ export default {
|
|||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: linear-gradient(135deg, #00D4FF, #1D3B5A);
|
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: 0 4px 12px rgba(0, 212, 255, 0.4);
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
<div class="logo">
|
<div class="logo">
|
||||||
<div class="time-capsule"></div>
|
<div class="time-capsule"></div>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="app-title">ChronoMail</h1>
|
<h1 class="app-title gradient-text">ChronoMail</h1>
|
||||||
<p class="app-slogan">写给未来,不负当下</p>
|
<p class="app-slogan text-secondary text-sm">写给未来,不负当下</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 登录表单 -->
|
<!-- 登录表单 -->
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
登录
|
登录
|
||||||
</van-button>
|
</van-button>
|
||||||
<div class="register-link mt-20">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</van-form>
|
</van-form>
|
||||||
@@ -186,24 +186,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.login-button {
|
.login-button {
|
||||||
background: linear-gradient(135deg, #00D4FF, #1D3B5A);
|
|
||||||
border: none;
|
|
||||||
height: 50px;
|
height: 50px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-top: 20px;
|
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 {
|
.register-link {
|
||||||
|
|||||||
@@ -141,7 +141,7 @@
|
|||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { showFailToast, showSuccessToast, Dialog } from 'vant'
|
import { showFailToast, showSuccessToast, Dialog } from 'vant'
|
||||||
import { userActions, mailActions, userState } from '../store'
|
import { userActions, mailActions, userState, statisticsActions } from '../store'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Profile',
|
name: 'Profile',
|
||||||
@@ -168,12 +168,13 @@ export default {
|
|||||||
// 获取用户统计数据
|
// 获取用户统计数据
|
||||||
const fetchStatistics = async () => {
|
const fetchStatistics = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await mailActions.getStatistics()
|
const response = await statisticsActions.getStatistics()
|
||||||
const data = response.data
|
|
||||||
|
|
||||||
sentCount.value = data.totalSent || 0
|
if (response && response.data) {
|
||||||
receivedCount.value = data.totalReceived || 0
|
sentCount.value = response.data.totalSent || 0
|
||||||
totalDays.value = data.timeTravelDuration || 0
|
receivedCount.value = response.data.totalReceived || 0
|
||||||
|
totalDays.value = response.data.timeTravelDuration || 0
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取统计数据失败:', error)
|
console.error('获取统计数据失败:', error)
|
||||||
}
|
}
|
||||||
@@ -182,12 +183,13 @@ export default {
|
|||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
const fetchUserProfile = async () => {
|
const fetchUserProfile = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await userActions.getProfile()
|
const userData = await userActions.fetchUserInfo()
|
||||||
const userData = response.data
|
|
||||||
|
|
||||||
userName.value = userData.username || '时光旅人'
|
if (userData) {
|
||||||
userEmail.value = userData.email || 'traveler@chronomail.com'
|
userName.value = userData.username || '时光旅人'
|
||||||
userAvatar.value = userData.avatar || 'https://picsum.photos/seed/user123/100/100.jpg'
|
userEmail.value = userData.email || 'traveler@chronomail.com'
|
||||||
|
userAvatar.value = userData.avatar || 'https://picsum.photos/seed/user123/100/100.jpg'
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取用户信息失败:', error)
|
console.error('获取用户信息失败:', error)
|
||||||
}
|
}
|
||||||
@@ -449,24 +451,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.logout-button {
|
.logout-button {
|
||||||
background: linear-gradient(135deg, #ff4d4f, #c41e3a);
|
|
||||||
border: none;
|
|
||||||
height: 50px;
|
height: 50px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-top: 20px;
|
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 {
|
.about-popup {
|
||||||
|
|||||||
@@ -224,24 +224,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.register-button {
|
.register-button {
|
||||||
background: linear-gradient(135deg, #00D4FF, #1D3B5A);
|
|
||||||
border: none;
|
|
||||||
height: 50px;
|
height: 50px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-top: 20px;
|
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 {
|
.login-link {
|
||||||
|
|||||||
@@ -31,12 +31,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mail-content">
|
<div class="mail-content">
|
||||||
<div class="mail-header">
|
<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>
|
<span class="mail-date">{{ formatDate(mail.sendTime) }}</span>
|
||||||
</div>
|
</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">
|
<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">
|
<van-tag :type="getStatusType(mail.status)" size="small">
|
||||||
{{ getStatusText(mail.status) }}
|
{{ getStatusText(mail.status) }}
|
||||||
</van-tag>
|
</van-tag>
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</van-list>
|
</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="暂无已发送的邮件" />
|
<van-empty description="暂无已发送的邮件" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -155,6 +155,8 @@ export default {
|
|||||||
|
|
||||||
// 排序后的邮件
|
// 排序后的邮件
|
||||||
const sortedMails = computed(() => {
|
const sortedMails = computed(() => {
|
||||||
|
if (!mails.value || !Array.isArray(mails.value)) return []
|
||||||
|
|
||||||
const sorted = [...mails.value]
|
const sorted = [...mails.value]
|
||||||
|
|
||||||
switch (sortType.value) {
|
switch (sortType.value) {
|
||||||
@@ -221,15 +223,16 @@ export default {
|
|||||||
size: pageSize.value
|
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
|
finished.value = true
|
||||||
} else {
|
} else {
|
||||||
page.value += 1
|
page.value += 1
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showFailToast(response.message || '获取邮件列表失败')
|
showFailToast('获取邮件列表失败')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取邮件列表失败:', error)
|
console.error('获取邮件列表失败:', error)
|
||||||
|
|||||||
@@ -512,8 +512,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.filter-apply {
|
.filter-apply {
|
||||||
background: linear-gradient(135deg, var(--accent-color), #0099CC);
|
|
||||||
border: none;
|
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ module.exports = defineConfig({
|
|||||||
// 反向代理5001
|
// 反向代理5001
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:5001',
|
target: 'http://localhost:5003',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false
|
secure: false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user