feat(app): 添加多模板关于我们页面及相关路由和404页面

- 新增404页面,优化未找到页面体验,避免被搜索引擎索引
- 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理
- 实现/article、/case、/product及/page动态路由兼容列表与详情展示
- 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置
- 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持
- 模板增强支持CMS单页内容加载及SEO信息动态设置
- 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
This commit is contained in:
2026-09-08 12:13:44 +08:00
commit 2b69686795
381 changed files with 59891 additions and 0 deletions
+193
View File
@@ -0,0 +1,193 @@
<template>
<div ref="contentRef" v-html="enhancedHtml" class="prose prose-slate max-w-none" />
</template>
<script setup lang="ts">
/**
* 富文本内容渲染组件
* 用于渲染 CMS 返回的 HTML 内容
*
* 设计要点:
* 1. 未安装 @tailwindcss/typography,因此 .prose 仅作为命名空间,
* 实际排版样式由本组件自身提供。
* 2. 后台编辑器(Quill / WangEditor / Tinymce 等)常输出 ql-align-center、
* w-e-text-align-center 等 class,或内联 style="text-align:center"
* 本组件会识别这些标记并让图片真正居中,实现「所见即所得」。
*/
interface Props {
content?: string | null
}
const props = defineProps<Props>()
const contentRef = ref<HTMLElement | null>(null)
const CENTER_CLASSES = ['ql-align-center', 'w-e-text-align-center']
const RIGHT_CLASSES = ['ql-align-right', 'w-e-text-align-right']
function hasCenterMarker(el: Element): boolean {
const className = el.className || ''
const style = (el.getAttribute('style') || '').toLowerCase()
return CENTER_CLASSES.some(c => className.includes(c)) || style.includes('text-align:center') || style.includes('text-align: center')
}
/** 把新的 style 声明追加到已有 style 字符串,避免双分号 */
function appendStyle(existing: string, addition: string): string {
const trimmed = existing.trim()
return trimmed ? `${trimmed};${addition}` : addition
}
/**
* 服务端/无 DOM 环境下的简单正则增强:
* - 移除 script 标签
* - 给 align="center" 的图片追加居中样式
* - 确保图片默认 display:inline-block,使父级 text-align:center 可生效
*/
function enhanceHtmlString(html: string): string {
if (!html) return ''
let result = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
// 给 align="center" 的图片追加 block + auto margin
result = result.replace(/<img\b([^>]*?)align=["']center["']([^>]*)>/gi, (match, before, after) => {
const styleMatch = /style=["']([^"']*)["']/i.exec(match)
let style = styleMatch ? styleMatch[1] : ''
if (!/display\s*:/i.test(style)) style = appendStyle(style, 'display:block')
if (!/margin\s*:/i.test(style) && !/margin-left\s*:/i.test(style)) {
style = appendStyle(style, 'margin-left:auto;margin-right:auto')
}
if (styleMatch) {
return match.replace(/style=["'][^"']*["']/i, `style="${style}"`)
}
return `<img${before}align="center"${after} style="${style}">`
})
// 默认所有图片 inline-block,方便响应父级 text-align
result = result.replace(/<img\b([^>]*)>/gi, (match, attrs) => {
let a = attrs
// 懒加载 + 异步解码(性能/CLS 优化),避免重复加载阻塞渲染
if (!/\bloading\s*=/i.test(a)) a = ` loading="lazy"${a}`
if (!/\bdecoding\s*=/i.test(a)) a = ` decoding="async"${a}`
if (/display\s*:/i.test(a)) return `<img${a}>`
const styleMatch = /style=["']([^"']*)["']/i.exec(a)
if (styleMatch) {
const style = appendStyle(styleMatch[1], 'display:inline-block')
return `<img${a}`.replace(/style=["'][^"']*["']/i, `style="${style}"`)
}
return `<img${a} style="display:inline-block">`
})
return result
}
/**
* 客户端增强:基于真实 DOM 做更精确的处理
*/
function enhanceDom(root: HTMLElement) {
// 1. 给常见编辑器居中/右对齐 class 追加内联 style(确保 class 样式生效)
CENTER_CLASSES.forEach(cls => {
root.querySelectorAll(`.${cls}`).forEach(el => {
(el as HTMLElement).style.textAlign = 'center'
})
})
RIGHT_CLASSES.forEach(cls => {
root.querySelectorAll(`.${cls}`).forEach(el => {
(el as HTMLElement).style.textAlign = 'right'
})
})
// 2. 处理图片:让被父级标记为居中的图片真正居中
root.querySelectorAll('img').forEach(img => {
const parent = img.parentElement
const isCentered =
img.getAttribute('align') === 'center' ||
(parent ? hasCenterMarker(parent) || hasCenterMarker(img) : hasCenterMarker(img))
if (isCentered) {
img.style.display = 'block'
img.style.marginLeft = 'auto'
img.style.marginRight = 'auto'
} else if (!img.style.display) {
// 默认 inline-block:父级 text-align:center 时仍可居中,
// 同时保留 vertical-align 避免行高异常
img.style.display = 'inline-block'
}
if (!img.style.maxWidth) img.style.maxWidth = '100%'
if (!img.style.height) img.style.height = 'auto'
})
}
const enhancedHtml = computed(() => enhanceHtmlString(props.content || ''))
onMounted(() => {
if (contentRef.value) {
enhanceDom(contentRef.value)
}
})
// 内容变化时重新增强(例如路由切换复用组件)
watch(() => props.content, () => {
nextTick(() => {
if (contentRef.value) {
enhanceDom(contentRef.value)
}
})
})
</script>
<style>
.prose img {
max-width: 100%;
height: auto;
display: inline-block;
vertical-align: middle;
}
.prose h2, .prose h3, .prose h4 {
margin-top: 1.5em;
margin-bottom: 0.75em;
}
.prose p {
margin-bottom: 1em;
line-height: 1.75;
}
.prose ul, .prose ol {
margin-bottom: 1em;
padding-left: 1.5em;
}
.prose ul {
list-style-type: disc;
}
.prose ol {
list-style-type: decimal;
}
/* 常见富文本编辑器居中/右对齐 class */
.prose .ql-align-center,
.prose .w-e-text-align-center,
.prose [style*="text-align: center"],
.prose [style*="text-align:center"] {
text-align: center;
}
.prose .ql-align-right,
.prose .w-e-text-align-right,
.prose [style*="text-align: right"],
.prose [style*="text-align:right"] {
text-align: right;
}
/* 父级被标记居中时,内部图片也居中 */
.prose .ql-align-center img,
.prose .w-e-text-align-center img,
.prose [style*="text-align: center"] img,
.prose [style*="text-align:center"] img,
.prose img[align="center"] {
display: block;
margin-left: auto;
margin-right: auto;
}
</style>