Files
smt/pages/profile/index.vue
T
你çšnepiedg d101515d8d refactor: rename AI summary page and update navigation path
- Changed the path of the AI summary page from "pages/ai-summary/index" to "pages/ai_summary/index"
- Updated the navigation in the profile page to reflect the new path for the AI summary page
2026-03-16 15:34:37 +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>