feat(app): 添加多模板关于我们页面及相关路由和404页面
- 新增404页面,优化未找到页面体验,避免被搜索引擎索引 - 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理 - 实现/article、/case、/product及/page动态路由兼容列表与详情展示 - 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置 - 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持 - 模板增强支持CMS单页内容加载及SEO信息动态设置 - 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user