|
- <template>
- <div>
- <!-- Hero -->
- <section class="border-b border-[var(--c-e8eef7)] bg-gradient-to-br from-[var(--c-ffffff)] via-[var(--c-eef6ff)] to-[var(--c-f5faff)]">
- <div class="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8 lg:py-14">
- <!-- 面包屑(SEO) -->
- <nav aria-label="面包屑" class="flex items-center gap-1.5 text-xs text-[var(--c-8a97a8)]">
- <NuxtLink to="/" class="hover:text-[var(--c-0b8cff)]">首页</NuxtLink>
- <span>/</span>
- <NuxtLink v-if="colId || tagName" to="/info" class="hover:text-[var(--c-0b8cff)]">AI 资讯</NuxtLink>
- <span v-if="colId || tagName">/</span>
- <span class="text-[var(--c-4b5b70)]">{{ heading }}</span>
- </nav>
-
- <h1 class="mt-4 text-3xl font-extrabold text-[var(--c-172033)] sm:text-4xl">{{ heading }}</h1>
- <p class="mt-3 max-w-2xl text-sm leading-relaxed text-[var(--c-66758a)] sm:text-base">{{ subtitle }}</p>
-
- <!-- 栏目切换 -->
- <div class="mt-6 flex flex-wrap gap-2">
- <NuxtLink
- to="/info"
- class="rounded-full px-4 py-2 text-sm font-bold transition-colors"
- :class="!colId && !tagName ? 'bg-[var(--c-0b8cff)] text-white shadow-[0_8px_18px_var(--sh-11-140-255-220)]' : 'bg-[var(--c-ffffff)] text-[var(--c-5b6b80)] hover:text-[var(--c-0b8cff)]'"
- >
- 全部
- </NuxtLink>
- <NuxtLink
- v-for="col in COLUMNS"
- :key="col.colId"
- :to="col.path"
- class="inline-flex items-center gap-1.5 rounded-full px-4 py-2 text-sm font-bold transition-colors"
- :class="colId === col.colId ? 'bg-[var(--c-0b8cff)] text-white shadow-[0_8px_18px_var(--sh-11-140-255-220)]' : 'bg-[var(--c-ffffff)] text-[var(--c-5b6b80)] hover:text-[var(--c-0b8cff)]'"
- >
- <UIcon :name="col.icon" class="size-4" />
- {{ col.name }}
- </NuxtLink>
- </div>
- </div>
- </section>
-
- <!-- 内容 -->
- <div class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8 lg:py-12">
- <div class="lg:grid lg:grid-cols-12 lg:gap-8">
- <!-- 列表 -->
- <div class="lg:col-span-8">
- <div v-if="pending" class="space-y-4">
- <div v-for="i in 5" :key="i" class="h-32 animate-pulse rounded-2xl bg-[var(--c-ffffff)]" />
- </div>
-
- <div v-else-if="list.length" class="space-y-4">
- <article
- v-for="item in list"
- :key="item.id"
- class="group overflow-hidden rounded-2xl bg-[var(--c-ffffff)] shadow-[0_8px_28px_var(--sh-34-68-112-50)] transition-all hover:shadow-[0_14px_34px_var(--sh-34-68-112-120)]"
- >
- <NuxtLink :to="item.url" class="flex flex-col gap-4 p-4 sm:flex-row sm:p-5">
- <!-- 封面 -->
- <div v-if="item.cover" class="shrink-0 overflow-hidden rounded-xl bg-[var(--c-eef3f8)] sm:w-52">
- <img
- :src="item.cover"
- :alt="item.title"
- class="h-40 w-full object-cover transition-transform duration-500 group-hover:scale-105 sm:h-32"
- loading="lazy"
- >
- </div>
-
- <div class="flex min-w-0 flex-1 flex-col">
- <h2 class="line-clamp-2 text-base font-extrabold leading-snug text-[var(--c-172033)] transition-colors group-hover:text-[var(--c-0b8cff)] sm:text-lg">
- {{ item.title }}
- </h2>
- <p v-if="item.info" class="mt-2 line-clamp-2 flex-1 text-sm leading-relaxed text-[var(--c-8a97a8)]">
- {{ item.info }}
- </p>
- <div class="mt-3 flex flex-wrap items-center gap-3 text-xs text-[var(--c-a0aabb)]">
- <span class="rounded-full bg-[var(--c-eef7ff)] px-2 py-0.5 font-bold text-[var(--c-0b8cff)]">
- {{ colName(item.col_id) }}
- </span>
- <span class="inline-flex items-center gap-1">
- <UIcon name="i-lucide-clock" class="size-3.5" />
- {{ item.time }}
- </span>
- <span class="inline-flex items-center gap-1">
- <UIcon name="i-lucide-eye" class="size-3.5" />
- {{ item.view_num || 0 }}
- </span>
- </div>
- </div>
- </NuxtLink>
- </article>
- </div>
-
- <!-- 空态 -->
- <div v-else class="rounded-2xl bg-[var(--c-ffffff)] py-20 text-center shadow-[0_8px_28px_var(--sh-34-68-112-50)]">
- <UIcon name="i-lucide-newspaper" class="mx-auto mb-4 size-12 text-[var(--c-a0aabb)]" />
- <div class="text-base font-extrabold text-[var(--c-172033)]">暂无内容</div>
- <div class="mt-2 text-sm text-[var(--c-8a95a6)]">内容正在整理中,敬请期待</div>
- </div>
-
- <!-- 分页(保留原站 URL 形式,利于 SEO 收录) -->
- <nav v-if="totalPages > 1" aria-label="分页" class="mt-8 flex flex-wrap items-center justify-center gap-2">
- <NuxtLink
- v-if="page > 1"
- :to="pageUrl(basePath, page - 1, tagKey)"
- class="rounded-lg bg-[var(--c-ffffff)] px-3.5 py-2 text-sm font-bold text-[var(--c-5b6b80)] shadow-sm transition-colors hover:text-[var(--c-0b8cff)]"
- rel="prev"
- >
- 上一页
- </NuxtLink>
- <NuxtLink
- v-for="p in pageNumbers"
- :key="p"
- :to="pageUrl(basePath, p, tagKey)"
- class="min-w-9 rounded-lg px-3 py-2 text-center text-sm font-bold transition-colors"
- :class="p === page ? 'bg-[var(--c-0b8cff)] text-white' : 'bg-[var(--c-ffffff)] text-[var(--c-5b6b80)] shadow-sm hover:text-[var(--c-0b8cff)]'"
- >
- {{ p }}
- </NuxtLink>
- <NuxtLink
- v-if="page < totalPages"
- :to="pageUrl(basePath, page + 1, tagKey)"
- class="rounded-lg bg-[var(--c-ffffff)] px-3.5 py-2 text-sm font-bold text-[var(--c-5b6b80)] shadow-sm transition-colors hover:text-[var(--c-0b8cff)]"
- rel="next"
- >
- 下一页
- </NuxtLink>
- </nav>
- </div>
-
- <!-- 侧栏 -->
- <div class="mt-8 lg:col-span-4 lg:mt-0">
- <div class="lg:sticky lg:top-20">
- <NewsSidebar :hot-list="hotList" :tag-list="tagList" :active-tag="tagKey" />
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
-
- <script setup>
- import { COLUMNS, colName, pageUrl } from '~/composables/useArticle'
-
- const props = defineProps({
- /** 栏目 id,不传表示「全部」 */
- colId: { type: Number, default: 0 },
- /** 标签 key(标签页使用) */
- tagKey: { type: String, default: '' },
- /** 列表基础路径,用于生成分页链接 */
- basePath: { type: String, default: '/info' },
- heading: { type: String, required: true },
- subtitle: { type: String, default: '' },
- page: { type: Number, default: 1 },
- pageSize: { type: Number, default: 10 },
- list: { type: Array, default: () => [] },
- total: { type: Number, default: 0 },
- tagName: { type: String, default: '' },
- hotList: { type: Array, default: () => [] },
- tagList: { type: Array, default: () => [] },
- pending: { type: Boolean, default: false },
- })
-
- const totalPages = computed(() => Math.ceil(props.total / props.pageSize) || 0)
-
- // 分页只展示当前页附近的页码,避免页码过多
- const pageNumbers = computed(() => {
- const total = totalPages.value
- const cur = props.page
- const arr = []
- const start = Math.max(1, cur - 2)
- const end = Math.min(total, start + 4)
- for (let i = start; i <= end; i++) arr.push(i)
- return arr
- })
- </script>
|