feat:消息中心和案例库

This commit is contained in:
李伊帆
2026-09-14 10:32:21 +08:00
parent 173cd6b975
commit f5d634bbaf
41 changed files with 3484 additions and 48 deletions

View File

@@ -1,41 +1,259 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import AppTabBar from '@/components/AppTabBar.vue'
import { DEFAULT_CASE_FILTER } from '@/constants/case-library'
import {
PROJECT_CATEGORIES,
PROJECT_CENTER_LIST,
} from '@/constants/project-center'
import filterIcon from '@/static/icons/icon-filter.svg'
import type { CaseFilterValues } from '@/types/case-library'
import type { ProjectCategory } from '@/types/project-center'
import { cloneCaseFilter, matchCaseFilter } from '@/utils/case-filter'
import CaseFilterPanel from '../case-library/components/CaseFilterPanel.vue'
import ProjectCenterCard from './components/ProjectCenterCard.vue'
const keyword = ref('')
const activeCategory = ref<ProjectCategory>('全部')
const projectList = ref(PROJECT_CENTER_LIST.map(item => ({ ...item })))
const filterVisible = ref(false)
const appliedFilter = ref<CaseFilterValues>(cloneCaseFilter(DEFAULT_CASE_FILTER))
const filteredProjects = computed(() => {
const text = keyword.value.trim()
return projectList.value.filter((item) => {
const matchCategory = activeCategory.value === '全部' || item.category === activeCategory.value
const matchKeyword = !text
|| item.title.includes(text)
|| item.category.includes(text)
|| item.projectNo.includes(text)
|| item.status.includes(text)
|| item.industry.includes(text)
const matchFilter = matchCaseFilter(item, appliedFilter.value)
return matchCategory && matchKeyword && matchFilter
})
})
function handleSelectCategory(category: ProjectCategory) {
activeCategory.value = category
}
function handleOpenFilter() {
filterVisible.value = true
}
function handleCloseFilter() {
filterVisible.value = false
}
function handleConfirmFilter(value: CaseFilterValues) {
appliedFilter.value = cloneCaseFilter(value)
}
function handleViewDetail(id: string) {
uni.navigateTo({
url: `/pages/project-center/detail/index?id=${encodeURIComponent(id)}`,
})
}
</script>
<template>
<view class="placeholder-page">
<text class="placeholder-page__title">
项目中心
</text>
<text class="placeholder-page__desc">
功能开发中
</text>
<view class="project-center">
<view class="project-center__header">
<text class="project-center__title">
项目中心
</text>
<view class="project-center__search">
<image
class="project-center__search-icon"
src="/static/icons/icon-search.svg"
mode="aspectFit"
/>
<input
v-model="keyword"
class="project-center__search-input"
placeholder="搜索案例标题、描述、标签..."
placeholder-class="project-center__search-placeholder"
>
</view>
<scroll-view
class="project-center__tabs"
scroll-x
:show-scrollbar="false"
>
<view class="project-center__tabs-inner">
<text
v-for="category in PROJECT_CATEGORIES"
:key="category"
class="project-center__tab"
:class="{ 'project-center__tab--active': activeCategory === category }"
@tap="handleSelectCategory(category)"
>
{{ category }}
</text>
</view>
</scroll-view>
<view class="project-center__toolbar">
<text class="project-center__count">
共 {{ filteredProjects.length }}
</text>
<view
class="project-center__filter"
@tap="handleOpenFilter"
>
<image
class="project-center__filter-icon"
:src="filterIcon"
mode="aspectFit"
/>
<text class="project-center__filter-text">
筛选
</text>
</view>
</view>
</view>
<scroll-view
class="project-center__scroll"
scroll-y
:show-scrollbar="false"
>
<view class="project-center__list">
<ProjectCenterCard
v-for="item in filteredProjects"
:key="item.id"
:item="item"
@detail="handleViewDetail"
/>
</view>
</scroll-view>
<AppTabBar active="project" />
<CaseFilterPanel
:visible="filterVisible"
:cases="projectList"
:value="appliedFilter"
@close="handleCloseFilter"
@confirm="handleConfirmFilter"
/>
</view>
</template>
<style scoped lang="scss">
@import '@/styles/variables.scss';
.placeholder-page {
.project-center {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding-bottom: calc(120rpx + env(safe-area-inset-bottom));
height: 100vh;
overflow: hidden;
background: $color-bg-section;
}
.project-center__header {
flex-shrink: 0;
padding: calc(var(--status-bar-height, 44px) + 60rpx) 24rpx 0;
background: $color-bg-page;
}
.placeholder-page__title {
.project-center__title {
display: block;
font-size: 36rpx;
font-weight: 600;
color: $color-text-primary;
text-align: center;
}
.placeholder-page__desc {
margin-top: 16rpx;
.project-center__search {
display: flex;
align-items: center;
height: 72rpx;
margin-top: 24rpx;
padding: 0 24rpx;
background: $color-bg-section;
border-radius: 36rpx;
}
.project-center__search-icon {
flex-shrink: 0;
width: 32rpx;
height: 32rpx;
margin-right: 12rpx;
}
.project-center__search-input {
flex: 1;
font-size: 26rpx;
color: $color-text-primary;
}
.project-center__search-placeholder {
color: $color-text-placeholder;
}
.project-center__tabs {
margin-top: 24rpx;
white-space: nowrap;
}
.project-center__tabs-inner {
display: inline-flex;
gap: 32rpx;
padding-right: 24rpx;
}
.project-center__tab {
flex-shrink: 0;
padding-bottom: 12rpx;
font-size: 28rpx;
color: $color-text-secondary;
&--active {
font-weight: 600;
color: $color-text-primary;
border-bottom: 4rpx solid $color-primary;
}
}
.project-center__toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 0 24rpx;
}
.project-center__count {
font-size: 24rpx;
color: $color-text-secondary;
}
.project-center__filter {
display: flex;
align-items: center;
}
.project-center__filter-icon {
width: 28rpx;
height: 28rpx;
margin-right: 8rpx;
}
.project-center__filter-text {
font-size: 26rpx;
color: #666666;
}
.project-center__scroll {
flex: 1;
min-height: 0;
}
.project-center__list {
padding: 0 24rpx calc(140rpx + env(safe-area-inset-bottom));
}
</style>