今日文章将讲述如何实现一个博客首页的各种卡片组件,包括基础卡片、专题卡片、文章卡片、热搜词条卡片、七日热门卡片、网站浏览卡片、动态卡片以及版本更新卡片。这些组件使用了Vue 3和Nuxt 3框架的特性,例如Composition API、<script setup>语法糖、响应式引用(ref)和具名插槽。
上一张传送门 @ Nuxt3重写博客(一):首页设计和Vue3导航封装
【设计图镇楼】
基础卡片
- 用途:作为其他所有卡片的基础容器。
- 关键点:支持标题、更多链接和底部分隔线的可选显示。
<template>
<div class="base-card">
<div
v-if="title || path"
class="base-card-title"
:class="[lineShow ? 'title-line-show' : '']"
>
<div v-if="title" class="title">
{{ title }}
</div>
<div v-if="path" class="more">更多</div>
</div>
<div class="p2" v-if="!(title || path)"></div>
<div class="base-card-text">
<slot> </slot>
</div>
</div>
</template>
<script lang="ts" setup>
const props = withDefaults(
defineProps<{
title?: string;
path?: string;
lineShow?: boolean;
}>(),
{
lineShow: false,
}
);
</script>
<style>
.base-card {
background-color: #fff;
border-radius: 5px;
width: 100%;
box-shadow: 0 1px 4px rgb(21 26 48 / 8%);
box-sizing: border-box;
}
.base-card + .base-card {
margin-top: 15px;
}
.base-card-title {
margin: 0 15px;
display: flex;
align-items: center;
justify-content: space-between;
height: 60px;
}
.base-card-title .title {
font-size: 16px;
font-weight: 600;
line-height: 24px;
padding: 20px 0 16px 0;
color: #292929;
}
.title-line-show {
border-bottom: 1px solid #e0e0e0;
}
.base-card-text {
margin: 0 15px;
}
.base-card .more {
cursor: pointer;
}
.p2 {
width: 100%;
height: 2rem;
line-height: 2rem;
}
</style>
入参
1. title
- 类型:
string(可选) - 用途: 用于显示卡片的标题。
- 示例:
在这个示例中,卡片的标题会显示为“卡片标题”。<BaseCard title="卡片标题"> <p>这是卡片的内容。</p> </BaseCard>
2. path
- 类型:
string(可选) - 用途: 用于显示一个“更多”链接,通常用于导航到更多内容。
- 示例:
在这个示例中,卡片的右上角会显示一个“更多”链接,点击后会导航到<BaseCard title="卡片标题" path="/more"> <p>这是卡片的内容。</p> </BaseCard>/more路径。
3. lineShow
- 类型:
boolean(可选) - 默认值:
false - 用途: 控制标题下方是否显示一条分隔线。
- 示例:
在这个示例中,卡片标题下方会显示一条分隔线,因为<BaseCard title="卡片标题" :lineShow="true"> <p>这是卡片的内容。</p> </BaseCard>lineShow被设置为true。
组合示例
以下是一个组合使用所有三个属性的示例:
<template>
<div class="home">
<div class="home-left">
<card-base title="滚动推荐">
<ul style="padding: 0 10px 10px 10px">
<li>推荐轮播</li>
<li>推荐轮播</li>
<li>推荐轮播</li>
<li>推荐轮播</li>
<li>推荐轮播</li>
<li>推荐轮播</li>
<li>推荐轮播</li>
</ul>
</card-base>
<card-base title="活动/专栏/专题">
<ul style="padding: 0 10px 10px 10px">
<li>活动1</li>
<li>专栏1</li>
<li>专题1</li>
</ul>
</card-base>
<card-base title="最新文章" path="/article">
<ul style="padding: 0 10px 10px 10px">
<li>文章1</li>
<li>文章1</li>
<li>文章1</li>
<li>文章1</li>
<li>文章1</li>
<li>文章1</li>
</ul>
</card-base>
</div>
<div class="home-right">
<card-base title="用户工作台">
<ul style="padding: 15px 20px">
用户工作台
</ul>
</card-base>
<card-base title="热搜词条">
<ul style="padding: 15px 20px">
热搜词条
</ul>
</card-base>
<card-base title="网站流量">
<ul style="padding: 15px 20px">
网站流量
</ul>
</card-base>
<card-base title="动态">
<ul style="padding: 15px 20px">
动态
</ul>
</card-base>
<card-base title="更新">
<ul style="padding: 15px 20px">
更新
</ul>
</card-base>
</div>
</div>
</template>
<style>
/* 全局样式 */
.pc .home {
display: flex;
justify-content: space-between;
}
.home-left {
flex: 0 0 960px;
}
.home-right {
flex: 0 0 300px;
}
</style>
在这个示例中:
title属性设置为"示例卡片",所以卡片会显示这个标题。path属性设置为"/more",所以卡片右上角会显示一个“更多”链接,点击后会导航到/more路径。lineShow属性设置为true,所以卡片标题下方会显示一条分隔线。
视觉效果总结
- 如果
title存在,卡片顶部会显示标题。 - 如果
path存在,标题右侧会显示一个“更多”链接。 - 如果
lineShow为true,标题下方会显示一条分隔线。 - 如果
title和path都不存在,标题区域会被一个占位符替代。
这些属性使得 BaseCard 组件非常灵活,可以根据不同的需求进行配置和使用。
专题卡片
- 用途:展示特定主题的媒体(图片或视频)、标题和一系列相关文章。
- 关键点:媒体类型的动态处理,文章列表的渲染。
<template>
<div class="topic-card">
<div class="media-container">
<img
v-if="topic.mediaType === 'image'"
:src="topic.mediaSrc"
alt="topic media"
/>
<video v-if="topic.mediaType === 'video'" controls>
<source :src="topic.mediaSrc" type="video/mp4" />
Your browser does not support the video tag.
</video>
<div class="duration" v-if="topic.duration">{{ topic.duration }}</div>
<div class="topic-title-overlay">{{ topic.title }}</div>
</div>
<div class="topic-content">
<ul class="article-list">
<li v-for="(article, index) in topic.articles.slice(0, 3)" :key="index">
<p class="topic-description">
<iconpark-icon
class="text-blue margin-right-l"
name="dot"
></iconpark-icon>
<span class="article-title">{{ article.title }}</span>
</p>
<div class="topic-author">
作者: <span>{{ article.author }}</span>
</div>
</li>
</ul>
</div>
</div>
</template>
<script lang="ts" setup>
interface Article {
title: string;
link: string;
author: string;
}
interface Topic {
mediaType: "image" | "video";
mediaSrc: string;
title: string;
description: string;
author: string;
duration?: string;
articles: Article[];
}
const props = defineProps<{
topic: Topic;
}>();
</script>
<style scoped>
.topic-card {
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
width: 320px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.2s;
}
.topic-card:hover {
transform: translateY(-5px);
}
.media-container {
position: relative;
width: 100%;
height: 180px;
overflow: hidden;
}
.media-container img,
.media-container video {
width: 100%;
height: 100%;
object-fit: cover;
}
.duration {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0, 0, 0, 0.7);
color: #fff;
padding: 2px 5px;
border-radius: 3px;
font-size: 12px;
}
.topic-title-overlay {
position: absolute;
bottom: 0;
width: 100%;
background: rgba(0, 0, 0, 0.6);
color: #fff;
text-align: center;
padding: 10px;
font-size: 16px;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.topic-content {
padding: 15px;
}
.topic-description {
font-size: 14px;
margin: 0 0 10px;
color: #555;
display: flex;
align-items: center;
line-height: 1.2rem;
}
.topic-author {
font-size: 12px;
color: #999;
margin-bottom: 10px;
margin-left: 12px;
}
.article-list {
list-style-type: none;
padding: 0;
margin: 0;
}
.article-list li {
margin-bottom: 10px;
}
.article-title {
display: block;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
cursor: pointer;
}
.article-title:hover {
text-decoration: underline;
}
</style>
文章卡片
- 用途:在之前的文章中已经介绍,用于展示新闻或文章摘要。
- 关键点:插槽的使用,使得组件内容高度可定制。
这卡片就是之前文章vue3 插槽一览和封装新闻卡片中的,就不费劲搬过来了
热搜词条卡片
- 用途:展示热门搜索词条,支持输入框搜索。
- 关键点:热门词条的动态样式处理,搜索功能的实现。
<template>
<div class="search-card">
<div>
<input
v-model="searchQuery"
type="text"
placeholder="Search..."
class="search-input"
/>
</div>
<ul class="hot-search">
<li
v-for="item in hotSearchItems"
:key="item.name"
:class="{ hot: item.hot }"
class="hot-item"
>
<span v-if="item.hot">🔥</span> {{ item.name }}
</li>
</ul>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
interface HotSearchItem {
name: string;
hot: boolean;
}
const searchQuery = ref("");
const hotSearchItems = ref<HotSearchItem[]>([
{ name: "Redis", hot: true },
{ name: "Node.js", hot: false },
{ name: "Vue.js", hot: true },
{ name: "Python", hot: false },
{ name: "JavaScript", hot: true },
]);
</script>
<style scoped>
.search-card {
padding: 10px;
}
.search-input {
width: 100%;
padding: 5px;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 3px;
}
.hot-search {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.hot-search li {
line-height: 2rem;
}
.hot-item {
margin-right: 10px;
display: flex;
align-items: center;
}
.hot-item.hot {
color: red;
}
</style>
七日热门卡片
- 用途:展示一周内热门文章或内容。
- 关键点:列表渲染,数据的动态处理。
<template>
<div class="hot-articles-card">
<ul>
<li v-for="(article, index) in articles" :key="index">
<p>【{{ article.type }}】</p>
{{ article.title }}
</li>
</ul>
</div>
</template>
<script lang="ts" setup>
interface Article {
type: string;
title: string;
link: string;
}
withDefaults(
defineProps<{
articles?: Article[];
}>(),
{
articles: () => [
{ type: "Node.js", title: "Redis 12.0 发布", link: "#" },
{ type: "Python", title: "Python 在科学计算中的应用", link: "#" },
{ type: "JavaScript", title: "Vue 8.0 发布", link: "#" },
{ type: "JavaScript", title: "Deno 2.0 正式版发布", link: "#" },
{ type: "Python", title: "TensorFlow 3.0 发布预告", link: "#" },
],
}
);
</script>
<style scoped>
.hot-articles-card {
padding: 5px 0 10px 0;
}
.hot-articles-card ul {
list-style-type: none;
padding: 0;
}
.hot-articles-card ul li {
max-width: 270px;
margin-bottom: 5px;
display: flex;
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;
white-space: nowrap;
}
.hot-articles-card li p {
text-decoration: none;
color: #007bff;
margin-right: 5px;
}
</style>
网站流量卡片
- 用途:展示网站的流量统计信息。
- 关键点:统计信息的布局和样式处理。
<template>
<div class="traffic-stats">
<div class="stats">
<div class="stat">
<h3>今日 PV</h3>
<p class="text-blue">{{ todayPV }}</p>
</div>
<div class="stat">
<h3>今日 UV</h3>
<p class="text-cyan">{{ todayUV }}</p>
</div>
<div class="stat">
<h3>10分钟在线</h3>
<p class="text-green">{{ tenMinOnline }}</p>
</div>
<div class="stat">
<h3>历史</h3>
<p>
<span class="text-blue">{{ historicalPV }}</span
>/<span class="text-cyan">{{ historicalUV }}</span>
</p>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { withDefaults, defineProps } from "vue";
const props = withDefaults(
defineProps<{
todayPV?: number;
todayUV?: number;
tenMinOnline?: number;
historicalPV?: number;
historicalUV?: number;
}>(),
{
todayPV: 0,
todayUV: 0,
tenMinOnline: 0,
historicalPV: 0,
historicalUV: 0,
}
);
</script>
<style scoped>
.traffic-stats {
padding: 10px 0;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.stats {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.stat {
flex: 1 1 100px;
text-align: center;
}
h2 {
margin-bottom: 20px;
}
h3 {
margin-bottom: 10px;
}
p {
font-size: 1.5em;
font-weight: bold;
}
</style>
动态卡片
- 用途:展示网站或用户的最新动态。
- 关键点:动态类型的样式区分,链接的处理。
<template>
<div class="activity-card">
<ul>
<li v-for="(activity, index) in activities" :key="index">
<div class="type" :class="activity.type">
{{ activity.type }}
</div>
<a
:href="activity.link"
:aria-label="`${activity.type}: ${activity.content}`"
>
{{ activity.content }}
</a>
</li>
</ul>
</div>
</template>
<script lang="ts" setup>
interface Activity {
type: string;
content: string;
link: string;
}
withDefaults(
defineProps<{
activities?: Activity[];
}>(),
{
activities: () => [
{ type: "更新文章", content: "Redis 6.0 发布", link: "#" },
{ type: "发布文章", content: "Python 在科学计算中的应用", link: "#" },
{ type: "添加分类", content: "JavaScript 分类", link: "#" },
{ type: "添加标签", content: "Deno 标签", link: "#" },
{ type: "更新文章", content: "TensorFlow 3.0 发布预告", link: "#" },
],
}
);
</script>
<style scoped>
.activity-card {
padding: 0;
font-family: Arial, sans-serif;
}
.activity-card ul {
list-style-type: none;
padding: 0;
}
.activity-card ul li {
line-height: 2rem;
display: flex;
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;
white-space: nowrap;
width: 270px;
}
.activity-card a {
text-decoration: none;
color: #007bff;
}
.activity-card a:hover {
text-decoration: underline;
}
.type {
font-weight: bold;
margin-right: 5px;
}
.type.更新文章 {
color: #28a745;
}
.type.发布文章 {
color: #17a2b8;
}
.type.添加分类 {
color: #ffc107;
}
.type.添加标签 {
color: #fd7e14;
}
</style>
版本更新卡片
- 用途:公告网站或应用的最新版本更新信息。
- 关键点:列表渲染更新内容,标题和日期的展示。
<template>
<div class="update-card">
<h2>{{ title }}</h2>
<p>{{ date }}</p>
<ul>
<li v-for="(item, index) in updates" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: "UpdateCard",
props: {
title: {
type: String,
default: "个人博客开源 V0.0.2",
},
date: {
type: String,
default: "2024-05-17",
},
updates: {
type: Array,
default: () => [
"添加导航",
"添加基础卡片和各种模块卡片UI封装",
"引入了color-ui的配色方案",
"重构了布局",
],
},
},
};
</script>
<style>
.update-card {
padding: 10px;
font-family: Arial, sans-serif;
总结
【成品镇楼图】
今天这些组件共同构成了一个功能丰富、视觉吸引的博客首页。通过组件化的方式,每个部分都可以独立开发和维护,极大地提高了代码的可重用性和项目的可维护性。在实现这些组件时,关键在于理解Vue 3的Composition API、组件传参、插槽使用等核心概念,以及如何将设计稿转化为实际的代码实现。
希望这篇文章能够帮助你学习和掌握这些重要的前端开发技能。