Vue 组件定义模板,集合v-for生成界面

尝试使用vue生成一个界面,界面上条目都是同样的模板,效果如下:

每个文章界面结构相同,js代码中提供了文章实体的链表,界面加载的时候根据这些实体自动生成对应UI;

类似WPF上的效果:https://blog.csdn.net/qq_59062726/article/details/138268351?fromshare=blogdetail&sharetype=blogdetail&sharerId=138268351&sharerefer=PC&sharesource=qq_59062726&sharefrom=from_link

1.编写数据提供类:blogData.js

javascript 复制代码
// 博客数据模拟
export const generateBlogList = (count = 10) => {
  const titles = [
    'Vue3响应式原理深入解析',
    'React Hooks最佳实践',
    'Node.js性能优化指南',
    '前端工程化实战',
    '微前端架构设计',
    'Webpack5新特性解析',
    'TypeScript泛型应用',
    'CSS-in-JS方案比较',
    '前端测试策略',
    '浏览器渲染原理'
  ]
  
  const authors = ['张三', '李四', '王五', '赵六', '钱七']
  const tagsPool = ['Vue', 'React', 'JavaScript', 'TypeScript', 'CSS', 'Node.js', '前端', '工程化', '性能优化']
  
  return Array.from({ length: count }, (_, index) => {
    const tagCount = Math.floor(Math.random() * 3) + 2
    const selectedTags = []
    for (let i = 0; i < tagCount; i++) {
      const randomTag = tagsPool[Math.floor(Math.random() * tagsPool.length)]
      if (!selectedTags.includes(randomTag)) {
        selectedTags.push(randomTag)
      }
    }
    
    return {
      id: index + 1,
      title: titles[index % titles.length] + ` (${index + 1})`,
      author: authors[Math.floor(Math.random() * authors.length)],
      publishDate: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
      summary: `这是第${index + 1}篇博客的摘要,这里包含了博客的主要内容和亮点...`,
      content: `这是第${index + 1}篇博客的完整内容...`,
      tags: selectedTags,
      views: Math.floor(Math.random() * 2000),
      likes: Math.floor(Math.random() * 150),
      coverImage: `https://picsum.photos/800/400?random=${index}`
    }
  })
}

// 导出固定数据
export const blogList = generateBlogList(8)

2.定义单个文章组件模板

BlogCard.vue

html 复制代码
<template>
  <article class="blog-card" @click="$emit('view-detail', blog)">
    <div class="blog-header">
      <img v-if="blog.coverImage" :src="blog.coverImage" :alt="blog.title" class="cover-image">
      <h3 class="blog-title">{{ blog.title }}</h3>
    </div>
    
    <div class="blog-meta">
      <span class="author">
        <i class="icon-user"></i>{{ blog.author }}
      </span>
      <span class="date">
        <i class="icon-calendar"></i>{{ formatDate(blog.publishDate) }}
      </span>
      <span class="views">
        <i class="icon-eye"></i>{{ blog.views }} 浏览
      </span>
      <span class="likes">
        <i class="icon-heart"></i>{{ blog.likes }} 点赞
      </span>
    </div>
    
    <p class="blog-summary">{{ blog.summary }}</p>
    
    <div class="blog-tags">
      <span 
        v-for="tag in blog.tags" 
        :key="tag" 
        class="tag"
        :style="{ backgroundColor: getTagColor(tag) }"
      >
        {{ tag }}
      </span>
    </div>
  </article>
</template>

<script>
export default {
  name: 'BlogCard',
  props: {
    blog: {
      type: Object,
      required: true
    }
  },
  methods: {
    formatDate(dateString) {
      return new Date(dateString).toLocaleDateString('zh-CN')
    },
    getTagColor(tag) {
      const colors = {
        'Vue': '#42b883',
        'React': '#61dafb',
        'JavaScript': '#f7df1e',
        'TypeScript': '#3178c6',
        'CSS': '#1572b6',
        'Node.js': '#339933'
      }
      return colors[tag] || '#6c757d'
    }
  }
}
</script>

<style scoped>
.blog-card {
  border: 1px solid #e1e8ed;
  border-radius: 12px;
  padding: 20px;
  margin-bottom: 16px;
  background: white;
  cursor: pointer;
  transition: all 0.3s ease;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.blog-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 16px rgba(0,0,0,0.15);
}

.cover-image {
  width: 100%;
  height: 200px;
  object-fit: cover;
  border-radius: 8px;
  margin-bottom: 12px;
}

.blog-title {
  color: #2c3e50;
  margin: 0 0 12px 0;
  font-size: 1.25rem;
  line-height: 1.4;
}

.blog-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-bottom: 12px;
  font-size: 0.875rem;
  color: #6c757d;
}

.blog-meta span {
  display: flex;
  align-items: center;
  gap: 4px;
}

.blog-summary {
  color: #495057;
  line-height: 1.6;
  margin-bottom: 16px;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.blog-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.tag {
  padding: 4px 8px;
  border-radius: 4px;
  color: white;
  font-size: 0.75rem;
  font-weight: 500;
}

/* 图标样式 */
.icon-user::before { content: '👤 '; }
.icon-calendar::before { content: '📅 '; }
.icon-eye::before { content: '👀 '; }
.icon-heart::before { content: '❤️ '; }
</style>

3.在父容器中使用v-for呈现

html 复制代码
<template>
   <div class="front-container">
    <h2>前端技术文章</h2>
    <div class="blog-list">
      <BlogCard 
        v-for="blog in blogs" 
        :key="blog.id" 
        :blog="blog"
        @view-detail="handleViewDetail"
      />
    </div>
  </div>
</template>

<script>
import { blogList } from '../../mock/blogData'
import BlogCard from '@/components/BlogCard.vue'

export default {
  name: 'BlogList',
  components: {
    BlogCard
  },
  data() {
    return {
      blogs: blogList
    }
  },
  methods: {
    handleViewDetail(blog) {
      console.log('查看博客详情:', blog)
    }
  }
}
</script>

分析:

数据流向:

blogData.js文件提供了blogList属性,在父容器中通过blogs引用了blogList,在父容器的v-for中把blogs中每一个实体又给到了BlogCard.vue的blog属性,完成数据填充。

其中包

相关推荐
sophie旭2 小时前
一道面试题,开始性能优化之旅(3)-- DNS查询+TCP(三)
前端·面试·性能优化
开心不就得了3 小时前
构建工具webpack
前端·webpack·rust
gerrgwg3 小时前
Flutter中实现Hero Page Route效果
前端
不枯石3 小时前
Matlab通过GUI实现点云的ICP配准
linux·前端·图像处理·计算机视觉·matlab
hhzz3 小时前
Pythoner 的Flask项目实践-在web页面实现矢量数据转换工具集功能(附源码)
前端·python·flask
lypzcgf3 小时前
Coze源码分析-资源库-编辑工作流-前端源码-核心流程/API/总结
前端·工作流·coze·coze源码分析·智能体平台·ai应用平台·agent平台
lypzcgf3 小时前
Coze源码分析-资源库-编辑工作流-前端源码-核心组件
前端·工作流·coze·coze源码分析·智能体平台·agent平台
有梦想的攻城狮3 小时前
从0开始学vue:vue和react的比较
前端·vue.js·react.js
FIN66683 小时前
昂瑞微,凭啥?
前端·科技·产品运营·创业创新·制造·射频工程