Files
ai-dimension/client/src/pages/favorites/favorites.vue
T

250 lines
5.1 KiB
Vue

<template>
<view class="page-favorites page-layout">
<view class="status-bar-placeholder"></view>
<!-- 顶部导航 -->
<view class="header">
<text class="title">我的收藏</text>
<text class="count-badge">{{ favorites.length }}</text>
</view>
<!-- 收藏列表 -->
<view class="favorites-list" v-if="favorites.length > 0">
<view
v-for="(item, idx) in favorites"
:key="item.id || idx"
class="fav-item"
@tap="goDetail(item.id)"
>
<!-- 维度标签 -->
<view
class="dim-tag"
:style="{ color: dimColor(item.dimensionId) }"
>
{{ dimName(item.dimensionId) }}
</view>
<!-- 标题 -->
<text class="fav-title">{{ item.title }}</text>
<!-- 删除按钮 -->
<view class="delete-btn" @tap.stop="removeFav(item.id, idx)">
<text></text>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-else>
<text class="empty-icon"></text>
<text class="empty-text">你还没有收藏任何内容</text>
<text class="empty-sub">去首页发现有趣的 AI 知识吧</text>
<view class="empty-btn" @tap="goHome">
<text>开始探索</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { knowledgeApi } from '@/utils/api'
import { getDimension } from '@/utils/dimensions'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const favorites = ref([])
const loading = ref(true)
onMounted(() => {
if (!userStore.isLoggedIn) {
uni.showToast({ title: '请先登录', icon: 'none' })
uni.navigateTo({ url: '/pages/login/login' })
return
}
loadFavorites()
})
async function loadFavorites() {
loading.value = true
try {
const list = await knowledgeApi.collected(userStore.token)
favorites.value = list || []
} catch (e) {
console.error('[favorites] load failed:', e)
favorites.value = []
} finally {
loading.value = false
}
}
function dimName(id) {
const dim = getDimension(id)
return dim ? dim.name : `维度 ${id}`
}
function dimColor(id) {
const dim = getDimension(id)
return dim ? dim.color : 'rgba(255,255,255,0.4)'
}
function goDetail(id) {
if (!id) return
uni.navigateTo({ url: `/pages/detail/detail?id=${id}` })
}
async function removeFav(id, idx) {
uni.showModal({
title: '取消收藏',
content: '确定要取消收藏这篇文章吗?',
success: async (res) => {
if (!res.confirm) return
try {
await knowledgeApi.uncollect(id, userStore.token)
favorites.value.splice(idx, 1)
uni.showToast({ title: '已取消收藏', icon: 'success' })
} catch (e) {
console.error('[favorites] uncollect failed:', e)
uni.showToast({ title: '操作失败,请重试', icon: 'none' })
}
}
})
}
function goHome() {
uni.switchTab({ url: '/pages/index/index' })
}
</script>
<style lang="scss" scoped>
.page-favorites {
min-height: 100vh;
padding: 0 20px 40px;
background: var(--bg-primary);
}
.status-bar-placeholder { height: 44px; }
// 顶部导航
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0 18px;
}
.title {
font-size: 22px;
font-weight: 600;
color: var(--text-primary);
}
.count-badge {
font-size: 13px;
padding: 3px 12px;
border-radius: 12px;
background: rgba(179, 136, 255, 0.12);
color: var(--dim1-color);
}
// 收藏列表
.favorites-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.fav-item {
position: relative;
display: flex;
flex-direction: column;
gap: 8px;
padding: 16px;
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: 14px;
backdrop-filter: blur(8px);
}
.fav-item:active {
opacity: 0.7;
}
.dim-tag {
font-size: 12px;
padding: 2px 10px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.05);
align-self: flex-start;
}
.fav-title {
font-size: 15px;
color: var(--text-primary);
line-height: 1.5;
}
.delete-btn {
position: absolute;
top: 12px;
right: 12px;
width: 28px;
height: 28px;
border-radius: 50%;
background: rgba(239, 83, 80, 0.12);
border: 1px solid rgba(239, 83, 80, 0.2);
display: flex;
align-items: center;
justify-content: center;
color: #ef5350;
font-size: 14px;
font-weight: 600;
}
.delete-btn:active {
opacity: 0.6;
}
// 空状态
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 0;
text-align: center;
}
.empty-icon {
font-size: 56px;
margin-bottom: 16px;
opacity: 0.6;
}
.empty-text {
font-size: 16px;
color: var(--text-tertiary);
display: block;
margin-bottom: 6px;
}
.empty-sub {
font-size: 13px;
color: var(--text-muted);
display: block;
margin-bottom: 28px;
}
.empty-btn {
padding: 12px 32px;
border-radius: 22px;
background: linear-gradient(135deg, var(--dim1-color), var(--dim3-color));
font-size: 14px;
color: #fff;
font-weight: 500;
box-shadow: 0 0 24px rgba(179, 136, 255, 0.2);
}
.empty-btn:active {
opacity: 0.7;
}
</style>