149 lines
3.8 KiB
Vue
149 lines
3.8 KiB
Vue
<script setup lang="ts">
|
||
import { onMounted, ref } from 'vue'
|
||
import { storeToRefs } from 'pinia'
|
||
import { useRouter } from 'vue-router'
|
||
import { FilterPanel, PaginationBar } from '@/components'
|
||
import type { BusinessType } from '@/types/api'
|
||
import { useProfileStore } from '@/stores/modules/profile'
|
||
import FavoritesGrid from './cpns/FavoritesGrid.vue'
|
||
|
||
interface CategoryOption {
|
||
key: BusinessType | 'all'
|
||
label: string
|
||
}
|
||
|
||
const CATEGORY_OPTIONS: CategoryOption[] = [
|
||
{ key: 'all', label: '全部' },
|
||
{ key: 'internet', label: '互联网软件' },
|
||
{ key: 'hardware', label: '硬件电路' },
|
||
{ key: 'iot', label: '物联网全案' },
|
||
{ key: 'manufacturing', label: '生产制造' },
|
||
]
|
||
|
||
const router = useRouter()
|
||
const profileStore = useProfileStore()
|
||
const { favorites, favoritesTotal, favoritesCounts, favoritesQuery } = storeToRefs(profileStore)
|
||
|
||
// 本地搜索条件(搜索/重置时同步进 store)
|
||
const keyword = ref('')
|
||
const category = ref<BusinessType | 'all'>('all')
|
||
|
||
function countOf(key: BusinessType | 'all'): number {
|
||
if (key === 'all') return favoritesTotal.value
|
||
return favoritesCounts.value[key] ?? 0
|
||
}
|
||
|
||
function onSearch(): void {
|
||
void profileStore.searchFavorites({ keyword: keyword.value, category: category.value })
|
||
}
|
||
|
||
function onReset(): void {
|
||
keyword.value = ''
|
||
category.value = 'all'
|
||
void profileStore.resetFavorites()
|
||
}
|
||
|
||
function onSelectCategory(key: BusinessType | 'all'): void {
|
||
category.value = key
|
||
void profileStore.searchFavorites({ keyword: keyword.value, category: key })
|
||
}
|
||
|
||
function onPageChange(page: number): void {
|
||
void profileStore.changeFavoritesPage(page)
|
||
}
|
||
|
||
function onViewDetail(id: string): void {
|
||
void router.push(`/cases/${id}`)
|
||
}
|
||
|
||
onMounted(() => {
|
||
keyword.value = favoritesQuery.value.keyword
|
||
category.value = favoritesQuery.value.category
|
||
void Promise.all([profileStore.fetchFavorites(), profileStore.fetchFavoritesCounts()])
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="favorites-page">
|
||
<FilterPanel
|
||
v-model:keyword="keyword"
|
||
keyword-placeholder="搜索收藏案例标题"
|
||
@search="onSearch"
|
||
@reset="onReset"
|
||
/>
|
||
|
||
<div class="favorites-page__categories">
|
||
<button
|
||
v-for="item in CATEGORY_OPTIONS"
|
||
:key="item.key"
|
||
class="favorites-page__cat"
|
||
:class="{ 'is-active': category === item.key }"
|
||
@click="onSelectCategory(item.key)"
|
||
>
|
||
{{ item.label }}({{ countOf(item.key) }})
|
||
</button>
|
||
</div>
|
||
|
||
<FavoritesGrid :cases="favorites" @view="onViewDetail" />
|
||
|
||
<PaginationBar
|
||
:page="favoritesQuery.page"
|
||
:page-size="favoritesQuery.pageSize"
|
||
:total="favoritesTotal"
|
||
@page-change="onPageChange"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<style lang="less" scoped>
|
||
// 收藏列表无独立设计稿:与个人中心面板一致 —— 分类条平铺在白面板上,选中态用主色 token
|
||
.favorites-page {
|
||
.flex(column, flex-start);
|
||
gap: @spacing-base;
|
||
width: 100%;
|
||
|
||
&__categories {
|
||
.flex(row, flex-start);
|
||
flex-wrap: wrap;
|
||
gap: @spacing-sm;
|
||
width: 100%;
|
||
padding: 0;
|
||
}
|
||
|
||
&__cat {
|
||
height: 28px;
|
||
padding: 0 @spacing-base;
|
||
border: 1px solid @profile-line;
|
||
border-radius: @radius-round;
|
||
background: @white;
|
||
color: @text-color;
|
||
font-size: 11px;
|
||
transition: all 0.2s;
|
||
|
||
&:hover {
|
||
border-color: @primary-strong;
|
||
color: @primary-strong;
|
||
}
|
||
|
||
&.is-active {
|
||
border-color: @primary-strong;
|
||
background: @profile-nav-active-bg;
|
||
color: @primary-strong;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
// 分页与地址/资质页保持同一激活态(设计 20 实取 #D1E0FE + #024FFF 字)
|
||
:deep(.pagination) {
|
||
justify-content: flex-end;
|
||
padding: 0;
|
||
}
|
||
|
||
:deep(.pagination__btn.is-active) {
|
||
border-color: @profile-page-active-bg;
|
||
background: @profile-page-active-bg;
|
||
color: @primary-strong;
|
||
}
|
||
}
|
||
</style>
|