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