Files
smt/pages/profile/index.vue
T
你çšnepiedg ff2db1cc07 feat: 更新 AI 页面与个人资料菜单
- 修改 AI 页面标题为 "AI 建议" 和 "AI 总结"
- 在个人资料页面添加 AI 建议和总结的导航项
- 优化 AI 页面结构,移除不必要的加载状态和元素
- 更新样式以提升用户体验
2026-03-14 01:18:57 +08:00

344 lines
7.6 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page">
<view class="user-section">
<image class="avatar" :src="userAvatar" mode="aspectFill"></image>
<text class="user-name">{{ userName }}</text>
</view>
<view class="section">
<view class="menu-list">
<view class="menu-item" @tap="goAISuggest">
<view class="menu-icon menu-icon-green">🤖</view>
<view class="menu-content">
<text class="menu-label">AI 建议</text>
<text class="menu-desc">查看今日 AI 控烟节奏与建议节点</text>
</view>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @tap="goAISummary">
<view class="menu-icon menu-icon-green">📝</view>
<view class="menu-content">
<text class="menu-label">AI 总结</text>
<text class="menu-desc">按日期生成抽烟总结和明日建议</text>
</view>
<text class="menu-arrow"></text>
</view>
<view class="menu-item">
<view class="menu-icon menu-icon-green">🔗</view>
<view class="menu-content">
<text class="menu-label">分享戒烟记录</text>
<text class="menu-desc">{{ shareDesc }}</text>
<view class="menu-actions">
<text class="menu-action" @tap.stop="previewSharePage">预览分享页</text>
<text class="menu-action-sep">·</text>
<text class="menu-action" @tap.stop="handleRefreshShare">刷新分享链接</text>
</view>
</view>
<button class="share-btn" open-type="share" :disabled="shareLoading || !shareToken">
{{ shareLoading ? '生成中' : '分享' }}
</button>
</view>
<view class="menu-item" @tap="goOnboarding">
<view class="menu-icon menu-icon-green">📋</view>
<view class="menu-content">
<text class="menu-label">重新填写问卷</text>
<text class="menu-desc">修改吸烟基线与个人信息</text>
</view>
<text class="menu-arrow"></text>
</view>
</view>
</view>
<view class="section">
<view class="menu-list">
<view class="menu-item" @tap="clearCache">
<view class="menu-icon menu-icon-gray">🗑</view>
<view class="menu-content">
<text class="menu-label">清除缓存</text>
</view>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @tap="copyInfo">
<view class="menu-icon menu-icon-gray">💬</view>
<view class="menu-content">
<text class="menu-label">意见反馈</text>
</view>
<text class="menu-arrow"></text>
</view>
</view>
</view>
<text class="version">版本 1.0.0</text>
</view>
</template>
<script setup>
import { computed, ref } from 'vue'
import { onShareAppMessage, onShow } from '@dcloudio/uni-app'
import * as api from '@/api'
import { useUserStore } from '@/stores/user'
import { useLogin } from '@/hooks/useLogin'
const userStore = useUserStore()
const { waitForLogin } = useLogin()
const shareToken = ref('')
const shareExpireAt = ref('')
const shareLoading = ref(false)
const userName = computed(() => userStore.user?.nickname || '戒烟用户')
const userAvatar = computed(() => userStore.user?.avatar_url || 'https://linghu-wmr.oss-cn-beijing.aliyuncs.com/smt/avatar.png')
const shareDesc = computed(() => {
if (!shareToken.value) {
return shareLoading.value ? '正在生成分享信息...' : '先生成分享令牌后即可分享给朋友'
}
return `有效期至 ${formatExpire(shareExpireAt.value)},仅查看权限`
})
const sharePath = computed(() => {
if (!shareToken.value) {
return 'pages/index/index'
}
return `pages/share/index?share_token=${shareToken.value}`
})
function formatExpire(value) {
if (!value) return '--'
const d = new Date(value)
if (Number.isNaN(d.getTime())) return value
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return `${y}-${m}-${day} ${hh}:${mm}`
}
async function prepareShareToken(showToast = false) {
if (shareLoading.value) return
shareLoading.value = true
try {
const res = await api.createShare({ days: 7 })
shareToken.value = res.data?.share_token || ''
shareExpireAt.value = res.data?.expire_at || ''
if (showToast) {
uni.showToast({ title: '分享链接已刷新', icon: 'success' })
}
} catch (e) {
console.error('prepareShareToken error:', e)
if (showToast) {
uni.showToast({ title: '生成分享失败', icon: 'none' })
}
} finally {
shareLoading.value = false
}
}
function handleRefreshShare() {
prepareShareToken(true)
}
function previewSharePage() {
if (!shareToken.value) {
uni.showToast({ title: '分享令牌尚未生成', icon: 'none' })
return
}
uni.navigateTo({
url: `/pages/share/index?share_token=${shareToken.value}`
})
}
function goAISuggest() {
uni.navigateTo({ url: '/pages/ai/index' })
}
function goAISummary() {
uni.navigateTo({ url: '/pages/ai-summary/index' })
}
function goOnboarding() {
uni.navigateTo({ url: '/pages/onboarding/index' })
}
function clearCache() {
uni.showModal({
title: '清除缓存',
content: '将清除本地缓存数据,不会影响云端记录',
success: (res) => {
if (res.confirm) {
try {
uni.clearStorageSync()
uni.showToast({ title: '缓存已清除', icon: 'success' })
} catch (e) {
uni.showToast({ title: '清除失败', icon: 'none' })
}
}
}
})
}
function copyInfo() {
uni.setClipboardData({
data: '806669289@qq.com',
success: () => {
uni.showToast({ title: '反馈邮箱已复制', icon: 'success' })
}
})
}
onShareAppMessage(() => {
return {
title: `${userName.value}的戒烟记录(仅查看)`,
path: sharePath.value
}
})
onShow(async () => {
await waitForLogin()
await prepareShareToken(false)
})
</script>
<style scoped>
.page {
min-height: 100vh;
background: linear-gradient(to bottom, #D1FAE5 0%, #F0FDF4 45%, #FFFFFF 100%);
padding: 32rpx;
padding-bottom: 160rpx;
box-sizing: border-box;
}
.user-section {
display: flex;
flex-direction: column;
align-items: center;
padding: 48rpx 0 40rpx;
}
.avatar {
width: 140rpx;
height: 140rpx;
border-radius: 50%;
border: 4rpx solid #A7F3D0;
background-color: #ECFDF5;
margin-bottom: 20rpx;
}
.user-name {
font-size: 38rpx;
font-weight: 700;
color: #111827;
}
.section {
margin-bottom: 24rpx;
}
.menu-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.menu-item {
display: flex;
align-items: center;
gap: 24rpx;
background-color: #FFFFFF;
border-radius: 24rpx;
padding: 28rpx 24rpx;
border: 2rpx solid #ECFDF3;
box-shadow: 0 8rpx 20rpx rgba(16, 185, 129, 0.08);
}
.menu-icon {
width: 64rpx;
height: 64rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
}
.menu-icon-green {
background-color: #DCFCE7;
}
.menu-icon-gray {
background-color: #F3F4F6;
}
.menu-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 4rpx;
}
.menu-label {
font-size: 30rpx;
color: #111827;
}
.menu-desc {
font-size: 24rpx;
color: #6B7280;
}
.menu-actions {
margin-top: 6rpx;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8rpx;
font-size: 24rpx;
color: #10B981;
}
.menu-action {
color: #10B981;
}
.menu-action-sep {
color: #9CA3AF;
}
.menu-arrow {
font-size: 36rpx;
color: #9CA3AF;
}
.share-btn {
margin: 0;
padding: 10rpx 20rpx;
line-height: 1.4;
font-size: 24rpx;
border: none;
border-radius: 999rpx;
color: #FFFFFF;
background: #10B981;
}
.share-btn[disabled] {
background: #9CA3AF;
color: #FFFFFF;
}
.share-btn::after {
border: none;
}
.version {
display: block;
text-align: center;
font-size: 22rpx;
color: #9CA3AF;
margin-top: 32rpx;
}
</style>