Files
hjc-web/app/components/RecommendProductsSection.vue
gxwebsoft 2b69686795 feat(app): 添加多模板关于我们页面及相关路由和404页面
- 新增404页面,优化未找到页面体验,避免被搜索引擎索引
- 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理
- 实现/article、/case、/product及/page动态路由兼容列表与详情展示
- 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置
- 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持
- 模板增强支持CMS单页内容加载及SEO信息动态设置
- 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
2026-09-08 12:13:44 +08:00

110 lines
3.9 KiB
Vue
Raw Permalink 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.
<template>
<!--
推荐/置顶产品区块共享组件
数据useRecommend('product') —— 读取后台置顶 top>0 的产品全站跨栏目精选
显隐由父级首页通过后台首页区块开关 v-if 控制无数据时显示空状态占位
复用其他模板 import 本组件并传入各自主题色即可
-->
<section class="py-16 lg:py-20 bg-white">
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-12">
<h2 class="text-2xl sm:text-3xl font-bold mb-4" :style="{ color: titleColor }" data-reveal>{{ title }}</h2>
<p v-if="subtitle" class="max-w-2xl mx-auto" :style="{ color: subtitleColor }" data-reveal data-reveal-delay="100">{{ subtitle }}</p>
</div>
<div v-if="items.length > 0" :class="['grid gap-6', gridClass]">
<article
v-for="(item, index) in items"
:key="item.id || item.productId"
class="bg-white rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-shadow anim-card"
data-reveal
:data-reveal-delay="index * 100"
>
<NuxtLink :to="`/product/${item.id || item.productId}`">
<div class="aspect-video bg-gray-100 flex items-center justify-center anim-card-media overflow-hidden">
<img
v-if="item.cover || item.photo"
:src="productImage(item)"
:alt="item.productName"
class="w-full h-full object-cover"
loading="lazy"
>
<span v-else class="text-gray-400">暂无图片</span>
</div>
</NuxtLink>
<div class="p-5">
<h3 class="text-lg font-semibold mb-2 line-clamp-2 transition-colors" :style="{ color: titleColor }">
<NuxtLink :to="`/product/${item.id || item.productId}`" class="hover:opacity-80">{{ item.productName }}</NuxtLink>
</h3>
<p class="text-sm line-clamp-2" :style="{ color: subtitleColor }">{{ stripHtml(item.description || item.subtitle) }}</p>
</div>
</article>
</div>
<div v-else class="text-center text-gray-400 py-12">
<p>暂无推荐内容</p>
</div>
<div v-if="moreLink" class="text-center mt-10">
<NuxtLink
:to="moreLink"
class="inline-flex items-center font-semibold hover:opacity-80 transition-opacity"
:style="{ color: accentColor }"
>
{{ moreText }}
<svg class="w-4 h-4 ml-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</NuxtLink>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRecommend } from '~/composables/useRecommend'
import { useFileUrl } from '~/composables/useFileUrl'
import { stripHtml } from '~/utils'
import type { Product } from '~/types'
const props = withDefaults(
defineProps<{
title?: string
subtitle?: string
accentColor?: string
titleColor?: string
subtitleColor?: string
columns?: number
limit?: number
moreLink?: string
moreText?: string
}>(),
{
title: '推荐产品',
subtitle: '了解我们的核心产品与解决方案',
accentColor: '#3b82f6',
titleColor: '#111827',
subtitleColor: '#4b5563',
columns: 3,
limit: 3,
moreLink: '',
moreText: '查看更多'
}
)
const { fileUrl } = useFileUrl()
const { items } = useRecommend<Product>('product', { limit: props.limit })
const gridClass = computed(() => {
if (props.columns >= 4) return 'sm:grid-cols-2 lg:grid-cols-4'
if (props.columns === 2) return 'md:grid-cols-2'
return 'md:grid-cols-3'
})
function productImage(item: Product) {
const raw = item.cover || item.photo || ''
return raw ? fileUrl(raw) : ''
}
</script>