Files
hjc-web/app/components/AboutSection.vue
T
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

324 lines
7.9 KiB
Vue
Raw 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>
<!--
关于我们通用组件首页区块
数据源cms_page path=about 的单页记录
链路/api/page/detail?path=about server/api/page/detail.get.ts 上游 /cms/cms-page/getByPath/about
布局左侧标题 + 摘要 + 了解更多链接| 右侧配图
无数据时自动隐藏v-if="ready"),不影响首页其他区块
-->
<section
v-if="ready"
class="about-section"
:class="[customClass, { 'about-section--reversed': reversed }]"
:style="rootStyle"
>
<div :class="containerClass">
<!-- 文字区 -->
<div class="about-section__text">
<h2 v-if="displayTitle" class="about-section__title" :style="{ color: titleColor }">
{{ displayTitle }}
</h2>
<div
v-if="titleUnderline"
class="about-section__underline"
:style="{ backgroundColor: accentColor }"
/>
<p v-if="summaryText" class="about-section__summary" :style="{ color: summaryColor }">
{{ summaryText }}
</p>
<NuxtLink
v-if="aboutLink"
:to="aboutLink"
class="about-section__more"
:style="{ color: linkColor }"
>
{{ moreText }}
<svg
class="about-section__arrow"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
/>
</svg>
</NuxtLink>
</div>
<!-- 图片区 -->
<div class="about-section__image-wrap" :style="imageWrapStyle">
<img
v-if="imageUrl"
:src="imageUrl"
:alt="displayTitle"
class="about-section__image"
:style="imageStyle"
>
<div v-else class="about-section__image-placeholder" :style="placeholderStyle">
{{ placeholderText }}
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import type { PageDetail } from '~/types'
import { stripHtml } from '~/utils'
/**
* AboutSection — 首页「关于我们」通用组件
*
* 从 cms_page (path=about) 读取单页数据,左侧摘要 + 右侧配图。
* 各模板通过 props 定制颜色/圆角/容器等视觉细节。
*
* ## 使用方式
* ```vue
* <!-- 最简调用(使用全部默认值) -->
* <AboutSection />
*
* <!-- template-08 金棕风 -->
* <AboutSection
* title-color="var(--t8-text)"
* accent-color="var(--t8-primary)"
* summary-color="var(--t8-text-secondary)"
* link-color="var(--t8-primary)"
* container-class="container mx-auto px-4 sm:px-6 lg:px-8 grid lg:grid-cols-2 gap-12 items-center"
* />
* ```
*/
// ==================== Props ====================
const props = withDefaults(defineProps<{
/** 区块标题,空字符串时隐藏标题行 */
title?: string
/** 标题颜色 */
titleColor?: string
/** 装饰线 / 强调色(下划线、链接、占位背景) */
accentColor?: string
/** 摘要文字颜色 */
summaryColor?: string
/** "了解更多"链接文字颜色 */
linkColor?: string
/** "了解更多"文案 */
moreText?: string
/** 摘要最大字符数(stripHtml 后截断) */
maxChars?: number
/** 是否显示标题下方装饰线 */
titleUnderline?: boolean
/** 是否左右翻转(图片在左文字在右) */
reversed?: boolean
/** 外层容器额外 class */
customClass?: string
/** 内层网格容器 class(控制布局与间距) */
containerClass?: string
/** 区块背景色 */
bgColor?: string
/** 区块内边距 py / 上下 padding */
paddingY?: string
/** 图片容器圆角 */
imageRadius?: string
/** 图片容器背景色(无图时的占位背景) */
imageBgColor?: string
/** 无图时的占位文字 */
placeholderText?: string
/** 图片 object-fit */
imageObjectFit?: string
/** 图片高度 */
imageHeight?: string
}>(), {
title: '',
titleColor: '',
accentColor: '',
summaryColor: '',
linkColor: '',
moreText: '了解更多',
maxChars: 150,
titleUnderline: true,
reversed: false,
customClass: '',
containerClass: '',
bgColor: '#ffffff',
paddingY: 'py-16 lg:py-20',
imageRadius: 'rounded-2xl',
imageBgColor: '',
placeholderText: '',
imageObjectFit: 'object-contain',
imageHeight: '',
})
// ==================== 数据获取 ====================
const { siteInfo } = useSite()
const { fileUrl } = useFileUrl()
/** 单页详情(key 与 About.vue 一致,复用 SSR payload */
const { data: pageData } = await useFetch<PageDetail>('/api/page/detail', {
key: 'page-about',
query: { path: 'about' },
})
// ==================== 计算属性 ====================
/** 是否有足够数据渲染 */
const ready = computed(() => {
const p = pageData.value
if (!p) return false
// ok/empty 都展示(empty 时用 description 或站点简介兜底)
return p.status === 'ok' || p.status === 'empty'
})
/** 显示标题 */
const displayTitle = computed(() => {
if (props.title) return props.title
return pageData.value?.title?.trim() || '关于我们'
})
/**
* 摘要文本
* 优先级:pageData.description > stripHtml(pageData.content) > siteInfo.comments
*/
const summaryText = computed(() => {
const p = pageData.value
if (!p) return ''
// 1. 单页 SEO 描述
const desc = (p.description || '').trim()
if (desc) return desc.slice(0, props.maxChars)
// 2. 正文截取
const content = (p.content || '').trim()
if (content) {
const text = stripHtml(content).slice(0, props.maxChars)
if (text) return text
}
// 3. 站点简介兜底
const comments = (siteInfo.value?.comments || '').trim()
return comments.slice(0, props.maxChars) || ''
})
/** 配图 URL(经 fileUrl 转换) */
const imageUrl = computed(() => {
const photo = pageData.value?.photo
if (!photo) return ''
return fileUrl(photo)
})
/** "了解更多"链接目标 */
const aboutLink = computed(() => '/about')
// ==================== 内联样式(props → style ====================
const rootStyle = computed(() => ({
backgroundColor: props.bgColor || undefined,
paddingTop: undefined,
paddingBottom: undefined,
}))
const imageWrapStyle = computed(() => ({
borderRadius: undefined, // 由 class 控制
backgroundColor: props.imageBgColor || undefined,
minHeight: props.imageHeight || undefined,
}))
const imageStyle = computed(() => ({
objectFit: props.imageObjectFit as any || undefined,
maxHeight: props.imageHeight || undefined,
}))
const placeholderStyle = computed(() => ({
color: props.accentColor || undefined,
}))
</script>
<style scoped>
.about-section {
position: relative;
}
.about-section__text {
display: flex;
flex-direction: column;
justify-content: center;
}
.about-section__title {
font-size: 1.5rem;
font-weight: 700;
line-height: 1.3;
margin-bottom: 0.75rem;
}
.about-section__underline {
width: 4rem;
height: 4px;
border-radius: 9999px;
margin-bottom: 1.5rem;
}
.about-section__summary {
font-size: 1rem;
line-height: 1.75;
margin-bottom: 1.5rem;
}
.about-section__more {
display: inline-flex;
align-items: center;
font-weight: 600;
font-size: 0.95rem;
transition: opacity 0.2s;
width: fit-content;
}
.about-section__more:hover {
opacity: 0.75;
}
.about-section__arrow {
width: 1rem;
height: 1rem;
margin-left: 0.5rem;
flex-shrink: 0;
}
.about-section__image-wrap {
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
min-height: 16rem;
max-height: 20rem;
}
.about-section__image {
width: 100%;
height: 100%;
padding: 2rem;
}
.about-section__image-placeholder {
font-size: 0.9rem;
opacity: 0.45;
user-select: none;
}
/* 翻转布局 */
.about-section--reversed .about-section__text {
order: 2;
}
.about-section--reversed .about-section__image-wrap {
order: 1;
}
</style>