feat(product-detail): 实现商品详情页的收藏/取消收藏功能,包括:
1. 引入收藏相关API和图标组件 2. 添加收藏状态管理和加载状态 3. 实现收藏状态拉取和切换逻辑 4. 新增收藏按钮样式和交互 5. 处理未登录跳转登录的逻辑
This commit is contained in:
parent
54875823e9
commit
f8f326c889
File diff suppressed because it is too large
Load Diff
|
|
@ -10,10 +10,11 @@
|
|||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ShoppingCart, Plus, Minus } from '@element-plus/icons-vue'
|
||||
import { ShoppingCart, Plus, Minus, Star, StarFilled } from '@element-plus/icons-vue'
|
||||
import { useUserStore } from '@/stores/modules/user'
|
||||
import { getProductDetailApi } from '@/api/product'
|
||||
import { addToCartApi } from '@/api/cart'
|
||||
import { addFavoriteApi, cancelFavoriteByProductIdApi, checkFavoriteApi } from '@/api/favorite'
|
||||
import type { ProductDetail, ProductSku } from '@/types/product'
|
||||
import AppHeader from '@/components/layout/AppHeader.vue'
|
||||
import AppFooter from '@/components/layout/AppFooter.vue'
|
||||
|
|
@ -31,6 +32,10 @@ const selectedSku = ref<ProductSku | null>(null)
|
|||
const quantity = ref(1)
|
||||
const addingToCart = ref(false)
|
||||
|
||||
// ==================== 收藏 ====================
|
||||
const favorited = ref(false)
|
||||
const favoriteToggling = ref(false)
|
||||
|
||||
// ==================== 计算 ====================
|
||||
const allImages = computed(() => {
|
||||
if (!product.value) return []
|
||||
|
|
@ -113,6 +118,39 @@ async function handleAddToCart() {
|
|||
}
|
||||
}
|
||||
|
||||
async function loadFavoriteState() {
|
||||
if (!product.value || !userStore.isLoggedIn) return
|
||||
try {
|
||||
favorited.value = await checkFavoriteApi(product.value.id)
|
||||
} catch {
|
||||
/* 未登录 / 接口异常时保持未收藏 */
|
||||
}
|
||||
}
|
||||
|
||||
async function handleToggleFavorite() {
|
||||
if (!product.value) return
|
||||
if (!userStore.isLoggedIn) {
|
||||
router.push(`/login?redirect=${encodeURIComponent(route.fullPath)}`)
|
||||
return
|
||||
}
|
||||
favoriteToggling.value = true
|
||||
try {
|
||||
if (favorited.value) {
|
||||
await cancelFavoriteByProductIdApi(product.value.id)
|
||||
favorited.value = false
|
||||
ElMessage.success('已取消收藏')
|
||||
} else {
|
||||
await addFavoriteApi(product.value.id)
|
||||
favorited.value = true
|
||||
ElMessage.success('收藏成功,可在个人中心查看')
|
||||
}
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
favoriteToggling.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 加载 ====================
|
||||
onMounted(async () => {
|
||||
const id = Number(route.params.id)
|
||||
|
|
@ -127,6 +165,7 @@ onMounted(async () => {
|
|||
if (product.value?.skuList?.length) {
|
||||
selectedSku.value = product.value.skuList[0]
|
||||
}
|
||||
loadFavoriteState()
|
||||
} catch {
|
||||
ElMessage.error('商品不存在或已下架')
|
||||
} finally {
|
||||
|
|
@ -292,6 +331,16 @@ onMounted(async () => {
|
|||
>
|
||||
加入购物车
|
||||
</el-button>
|
||||
<el-button
|
||||
size="large"
|
||||
class="fav-btn"
|
||||
:class="{ active: favorited }"
|
||||
:icon="favorited ? StarFilled : Star"
|
||||
:loading="favoriteToggling"
|
||||
@click="handleToggleFavorite"
|
||||
>
|
||||
{{ favorited ? '已收藏' : '收藏' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -701,6 +750,36 @@ onMounted(async () => {
|
|||
}
|
||||
}
|
||||
|
||||
.fav-btn {
|
||||
min-width: 120px;
|
||||
height: 52px;
|
||||
font-size: $font-size-md;
|
||||
font-weight: 600;
|
||||
border-radius: $radius-full;
|
||||
border: 2px solid $color-border-dark;
|
||||
background: $bg-surface;
|
||||
color: $color-text-regular;
|
||||
box-shadow: 0 4px 0 0 $color-border-dark;
|
||||
transition: transform 0.2s, box-shadow 0.2s, color 0.2s, background 0.2s, border-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: $color-primary;
|
||||
border-color: $color-primary;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:active {
|
||||
box-shadow: 0 1px 0 0 $color-border-dark;
|
||||
transform: translateY(3px);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $color-primary-light;
|
||||
border-color: $color-primary;
|
||||
color: $color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 底部详情 ====================
|
||||
.detail-bottom {
|
||||
margin-top: 56px;
|
||||
|
|
|
|||
Loading…
Reference in New Issue