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,122 @@
|
||||
<template>
|
||||
<div v-if="list.length" class="mt-10 border-t border-gray-100 pt-8">
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-5">{{ title }}</h2>
|
||||
<ul class="space-y-3">
|
||||
<li
|
||||
v-for="(file, idx) in sortedList"
|
||||
:key="(file.url || '') + idx"
|
||||
class="flex items-center gap-3 rounded-lg border border-gray-200 px-4 py-3 transition hover:border-blue-400 hover:shadow-sm"
|
||||
>
|
||||
<!-- 文件图标(按扩展名换色,统一用内联 SVG,避免引入图标库依赖) -->
|
||||
<span class="shrink-0" :style="{ color: iconColor(file.ext) }">
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<path d="M14 2v6h6" />
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<!-- 文件名(可点击下载) -->
|
||||
<a
|
||||
:href="resolveUrl(file.url)"
|
||||
:download="file.name"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="min-w-0 flex-1 truncate text-blue-600 hover:underline"
|
||||
:title="file.name"
|
||||
>
|
||||
{{ file.name }}
|
||||
</a>
|
||||
|
||||
<!-- 扩展名徽标 -->
|
||||
<span
|
||||
v-if="file.ext"
|
||||
class="shrink-0 rounded bg-gray-100 px-2 py-0.5 text-xs font-medium uppercase text-gray-500"
|
||||
>{{ file.ext }}</span>
|
||||
|
||||
<!-- 文件大小 -->
|
||||
<span v-if="file.size" class="shrink-0 text-xs text-gray-400">{{ formatSize(file.size) }}</span>
|
||||
|
||||
<!-- 下载入口 -->
|
||||
<a
|
||||
:href="resolveUrl(file.url)"
|
||||
:download="file.name"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="shrink-0 text-sm text-blue-600 hover:underline"
|
||||
>下载</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { PageAttachment } from '~/types'
|
||||
import { ensureFullUrl } from '~/utils/image'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** 附件列表(来自 pageData.attachments) */
|
||||
attachments?: PageAttachment[]
|
||||
/** 区块标题,默认「相关附件」 */
|
||||
title?: string
|
||||
}>(),
|
||||
{
|
||||
attachments: () => [],
|
||||
title: '相关附件'
|
||||
}
|
||||
)
|
||||
|
||||
const list = computed<PageAttachment[]>(() => props.attachments || [])
|
||||
|
||||
/** 按 sort 升序,sort 缺失的排后面 */
|
||||
const sortedList = computed<PageAttachment[]>(() =>
|
||||
[...list.value].sort((a, b) => (a.sort ?? 9999) - (b.sort ?? 9999))
|
||||
)
|
||||
|
||||
function resolveUrl(url?: string) {
|
||||
return url ? ensureFullUrl(url) : ''
|
||||
}
|
||||
|
||||
/** 字节转可读大小 */
|
||||
function formatSize(bytes?: number) {
|
||||
if (!bytes || bytes <= 0) return ''
|
||||
const units = ['B', 'KB', 'MB', 'GB']
|
||||
let val = bytes
|
||||
let i = 0
|
||||
while (val >= 1024 && i < units.length - 1) {
|
||||
val /= 1024
|
||||
i++
|
||||
}
|
||||
return `${val.toFixed(i === 0 ? 0 : 1)} ${units[i]}`
|
||||
}
|
||||
|
||||
/** 不同扩展名用不同图标色,便于一眼区分文件类型 */
|
||||
function iconColor(ext?: string) {
|
||||
const map: Record<string, string> = {
|
||||
pdf: '#e5484d',
|
||||
doc: '#2b6cee',
|
||||
docx: '#2b6cee',
|
||||
xls: '#1f9d55',
|
||||
xlsx: '#1f9d55',
|
||||
csv: '#1f9d55',
|
||||
ppt: '#e8833a',
|
||||
pptx: '#e8833a',
|
||||
zip: '#8a6df0',
|
||||
rar: '#8a6df0',
|
||||
'7z': '#8a6df0',
|
||||
txt: '#6b7280'
|
||||
}
|
||||
return (ext && map[ext.toLowerCase()]) || '#6b7280'
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user