first1
This commit is contained in:
86
src/views/cases/cpns/CaseHero.vue
Normal file
86
src/views/cases/cpns/CaseHero.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { formatBusinessType } from '@/utils/format'
|
||||
import type { Case } from '@/types/business/cases'
|
||||
|
||||
interface Props {
|
||||
item: Case
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
/** 业务类型标签(设计稿:蓝色实心胶囊 + 白字) */
|
||||
const tag = computed(() => formatBusinessType(props.item.category))
|
||||
|
||||
/**
|
||||
* 封面图。
|
||||
*
|
||||
* <p>toCase 已把「非可访问地址」收口成空串,无封面时回退主题色底,
|
||||
* 避免裂图或空白块。</p>
|
||||
*/
|
||||
const coverStyle = computed(() =>
|
||||
props.item.cover ? { backgroundImage: `url(${props.item.cover})` } : undefined,
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 设计稿:封面通栏(不随内容容器收窄、无圆角),左下叠类型标签 + 案例名 -->
|
||||
<section class="case-hero" :style="coverStyle">
|
||||
<div class="container case-hero__inner">
|
||||
<span v-if="tag" class="case-hero__tag">{{ tag }}</span>
|
||||
<h1 class="case-hero__title">
|
||||
{{ props.item.title }}
|
||||
</h1>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.case-hero {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 260px;
|
||||
background-color: @primary-color;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
// 底部压暗,保证白字可读
|
||||
background: linear-gradient(180deg, rgba(15, 23, 42, 0) 42%, rgba(15, 23, 42, 0.55));
|
||||
}
|
||||
|
||||
&__inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
.flex(column, flex-end, flex-start);
|
||||
gap: @spacing-sm;
|
||||
height: 100%;
|
||||
padding-bottom: @spacing-lg;
|
||||
}
|
||||
|
||||
&__tag {
|
||||
padding: 2px @spacing-sm;
|
||||
border-radius: @radius-round;
|
||||
background: @primary-color;
|
||||
color: @white;
|
||||
font-size: @font-size-sm;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: @white;
|
||||
font-size: @font-size-xxl;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 1px 8px rgba(0, 0, 0, 0.35);
|
||||
// 最多两行,避免超长案例名撑破 hero
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
159
src/views/cases/cpns/CasesSearchBar.vue
Normal file
159
src/views/cases/cpns/CasesSearchBar.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Props {
|
||||
keyword: string
|
||||
/** 是否只看收藏 */
|
||||
favoriteOnly: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:keyword': [keyword: string]
|
||||
search: []
|
||||
reset: []
|
||||
toggleFavoriteOnly: []
|
||||
}>()
|
||||
|
||||
const keyword = computed<string>({
|
||||
get: () => props.keyword,
|
||||
set: (value) => emit('update:keyword', value),
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="case-search">
|
||||
<div class="case-search__field">
|
||||
<svg class="case-search__icon" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<circle cx="11" cy="11" r="7" />
|
||||
<path d="m20 20-3.7-3.7" />
|
||||
</svg>
|
||||
<input
|
||||
v-model="keyword"
|
||||
class="case-search__input"
|
||||
placeholder="关键字搜索"
|
||||
@keyup.enter="emit('search')"
|
||||
>
|
||||
</div>
|
||||
|
||||
<button class="case-search__btn" @click="emit('reset')">
|
||||
重置
|
||||
</button>
|
||||
<button class="case-search__btn case-search__btn--primary" @click="emit('search')">
|
||||
搜索
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="case-search__fav"
|
||||
:class="{ 'is-active': favoriteOnly }"
|
||||
@click="emit('toggleFavoriteOnly')"
|
||||
>
|
||||
<svg class="case-search__fav-icon" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M12 20.5s-7.2-4.6-8.4-9A5.1 5.1 0 0 1 12 5.6a5.1 5.1 0 0 1 8.4 5.9c-1.2 4.4-8.4 9-8.4 9Z" />
|
||||
</svg>
|
||||
收藏列表
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.case-search {
|
||||
.flex(row, flex-start, center);
|
||||
gap: @spacing-sm;
|
||||
padding: @spacing-base @spacing-lg;
|
||||
border-radius: @radius-base;
|
||||
background: @white;
|
||||
|
||||
&__field {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 12px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transform: translateY(-50%);
|
||||
fill: none;
|
||||
stroke: @text-color-secondary;
|
||||
stroke-width: 1.8;
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
padding: 0 @spacing-base 0 36px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: @radius-base;
|
||||
background: @input-bg;
|
||||
color: @text-color;
|
||||
font-size: @font-size-base;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
|
||||
&::placeholder {
|
||||
color: @text-color-secondary;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__btn {
|
||||
height: 36px;
|
||||
padding: 0 @spacing-lg;
|
||||
border-radius: @radius-base;
|
||||
background: @pill-bg;
|
||||
color: @text-color;
|
||||
font-size: @font-size-base;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
background: @primary-color;
|
||||
color: @white;
|
||||
|
||||
&:hover {
|
||||
background: @primary-hover;
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__fav {
|
||||
.flex(row, center);
|
||||
gap: @spacing-xs;
|
||||
margin-left: auto;
|
||||
color: @heading-color;
|
||||
font-size: @font-size-base;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover,
|
||||
&.is-active {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&.is-active .case-search__fav-icon {
|
||||
fill: @danger-color;
|
||||
stroke: @danger-color;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.8;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user