Files
cz-web/src/views/cases/index.vue
2026-10-01 10:53:13 +08:00

165 lines
4.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
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.
<script setup lang="ts">
import { onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useRouter } from 'vue-router'
import { PaginationBar } from '@/components'
// 页面容器:只做组件引入、props 分发、事件中转,数据只经 store
import { useCasesStore } from '@/stores/modules/cases'
import type { CaseQuery } from '@/types/business/cases'
import CasesFilter from './cpns/CasesFilter.vue'
import CasesSearchBar from './cpns/CasesSearchBar.vue'
import CasesGrid from './cpns/CasesGrid.vue'
const router = useRouter()
const casesStore = useCasesStore()
const { query, list, total, previewTotal, typeOptions, industryOptions, scaleOptions, caseTotal }
= storeToRefs(casesStore)
function onUpdateQuery(next: CaseQuery): void {
casesStore.setQuery(next)
}
function onUpdateKeyword(keyword: string): void {
casesStore.setQuery({ ...query.value, keyword })
}
function onSearch(): void {
void casesStore.search()
}
function onReset(): void {
void casesStore.resetQuery()
}
function onToggleFavoriteOnly(): void {
void casesStore.toggleFavoriteOnly()
}
function onFavorite(id: string): void {
void casesStore.toggleFavorite(id)
}
function onPageChange(page: number): void {
void casesStore.changePage(page)
}
function onViewDetail(id: string): void {
void router.push(`/cases/${id}`)
}
onMounted(() => {
// 筛选面板选项全部来自接口:业务类型 / 所属行业 / 项目规模三组都走 /categories,完成时间由前端算
void Promise.all([casesStore.fetchCategories(), casesStore.fetchCases()])
})
</script>
<template>
<div class="cases">
<header class="cases__hero">
<div class="container cases__hero-inner">
<h1 class="cases__title">
成功案例库
</h1>
<p class="cases__slogan">
海量软硬件研发与量产落地真实案例,借鉴成熟实践,降低项目试错成本。
</p>
</div>
</header>
<div class="container cases__body">
<CasesFilter
:query="query"
:type-options="typeOptions"
:industry-options="industryOptions"
:scale-options="scaleOptions"
:case-total="caseTotal"
:preview-total="previewTotal"
@update:query="onUpdateQuery"
@search="onSearch"
@reset="onReset"
/>
<div class="cases__main">
<CasesSearchBar
:keyword="query.keyword"
:favorite-only="query.favoriteOnly"
@update:keyword="onUpdateKeyword"
@search="onSearch"
@reset="onReset"
@toggle-favorite-only="onToggleFavoriteOnly"
/>
<CasesGrid
:cases="list"
:total="total"
@view="onViewDetail"
@favorite="onFavorite"
/>
<div class="cases__pager">
<PaginationBar
:page="query.page"
:page-size="query.pageSize"
:total="total"
@page-change="onPageChange"
/>
</div>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.cases {
// 设计实测:页脚顶 y1811,正文最底约 y1733 → 底距 78
padding-bottom: 78px;
// 设计稿页头条 矩形 148284 @(0,73) 1920×179 白底通栏;文案左缩进 112
&__hero {
height: 179px;
box-sizing: border-box;
padding-top: 32px; // 标题 @(112,105) → 73+32
background: @white;
}
// 页头文案与内容区不对齐:设计实测文案在 x112(内容区 x70 + 42)
&__hero-inner {
padding-left: 42px;
}
&__title {
margin: 0;
font-size: 36px; // 设计 @(112,105) 36/700 #121212
font-weight: 700;
line-height: 52px;
color: @cases-hero-title;
}
&__slogan {
margin: 15px 0 0; // 副标题 @(112,172),标题底 105+52=157 → 间距 15
font-size: 24px; // 设计 24/500 #666666
font-weight: 500;
line-height: 37px;
color: @cases-muted;
}
// 设计稿:筛选面板 @(70,272) 516 宽,卡片区 @(606,423) 起 1244 宽,主区间距 20
&__body {
.flex(row, flex-start, flex-start);
gap: 20px;
padding-top: 20px; // 筛选面板顶 272 − 页头条底(73+179) = 20
}
&__main {
.flex(column, flex-start);
flex: 1;
min-width: 0;
gap: 24px;
}
&__pager {
.flex(row, flex-end);
}
}
</style>