20260831223203

This commit is contained in:
oneao committed 2026-08-31 22:32:03 +08:00
1 parent 4d46ce6c53
commit 3bbcafc5be
91 files changed
+20921 -584

No files matched your search

@@ -0,0 +1,43 @@
<script setup lang="ts">
import { getCompany } from '~/data/mock'
// 企业空间使用独立的 company 布局:顶部只有一根纯企业导航(到顶、不含公司信息),
// 公司身份信息下放到导航下方的内容 hero(CompanyIdentityHeader),满足“导航上方无特殊信息”。
definePageMeta({
layout: 'company'
})
const route = useRoute()
const company = getCompany(route.params.slug as string)
if (!company) {
throw createError({
statusCode: 404,
statusMessage: '企业不存在',
fatal: true
})
}
useSeoMeta({
title: `${company.name} - 链通物流`,
description: `${company.description}`,
ogTitle: company.name,
ogDescription: company.description
})
// 传给子页面(/company/[slug]/index、about、articles、services、contact)
provide('company', company)
</script>
<template>
<div v-if="company">
<!-- 顶部:纯企业导航(唯一一根栏,不含公司信息) -->
<CompanyPageTabs :company="company" />
<!-- 导航下方:企业 hero(公司信息放在内容区,不在导航栏) -->
<CompanyIdentityHeader :company="company" />
<NuxtPage />
</div>
</template>
@@ -0,0 +1,76 @@
<script setup lang="ts">
import type { Company } from '~/data/mock'
const company = inject<Company>('company')!
const qualifications = [
{ name: '营业执照', icon: 'i-lucide-file-check' },
{ name: 'NVOCC 无船承运人', icon: 'i-lucide-ship' },
{ name: '国际货运代理备案', icon: 'i-lucide-globe' },
{ name: '道路运输经营许可', icon: 'i-lucide-truck' }
]
useSeoMeta({
title: `关于我们 - ${company.name}`,
description: company.about[0]
})
</script>
<template>
<div>
<CompanyPageBanner :company="company" title="关于我们" />
<UContainer class="max-w-6xl py-10">
<!-- 公司介绍 -->
<div class="max-w-4xl">
<h2 class="text-xl font-bold text-highlighted mb-4">
公司介绍
</h2>
<div class="space-y-4 text-toned leading-loose">
<p v-for="(paragraph, i) in company.about" :key="i">
{{ paragraph }}
</p>
</div>
</div>
<!-- 基本信息 -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-6 mt-12 py-8 border-y border-muted">
<div class="text-center">
<p class="text-2xl font-bold text-primary">{{ company.established }}</p>
<p class="text-sm text-muted mt-1">成立年份</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold text-primary mt-1">{{ company.employees.split(' ')[0] }}</p>
<p class="text-sm text-muted mt-1">企业规模</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold text-primary">{{ company.routes.length }}+</p>
<p class="text-sm text-muted mt-1">主营航线</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold text-primary">{{ company.services.length }}+</p>
<p class="text-sm text-muted mt-1">服务类型</p>
</div>
</div>
<!-- 资质证书 -->
<div class="mt-12">
<h2 class="text-xl font-bold text-highlighted mb-6">
资质证书
</h2>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div
v-for="qualification in qualifications"
:key="qualification.name"
class="flex items-center gap-3 rounded-lg bg-elevated ring ring-default p-4"
>
<div class="flex items-center justify-center size-10 rounded-lg bg-primary/10 shrink-0">
<UIcon :name="qualification.icon" class="size-5 text-primary" />
</div>
<span class="text-sm text-toned">{{ qualification.name }}</span>
</div>
</div>
</div>
</UContainer>
</div>
</template>
@@ -0,0 +1,41 @@
<script setup lang="ts">
import type { Company } from '~/data/mock'
import { getCompanyArticles } from '~/data/mock'
const company = inject<Company>('company')!
const articles = getCompanyArticles(company.slug)
useSeoMeta({
title: `${company.name} - 文章 - 链通物流`,
description: `${company.name} 发布的行业资讯、公司动态与案例分享。`
})
</script>
<template>
<UContainer class="max-w-6xl py-8">
<div
v-if="articles.length"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
>
<ArticleCard
v-for="article in articles"
:key="article.id"
:article="article"
/>
</div>
<div
v-else
class="flex flex-col items-center gap-2 py-20 text-center"
>
<UIcon
name="i-lucide-file-x"
class="size-10 text-dimmed"
/>
<p class="text-toned">
该企业暂未发布文章
</p>
</div>
</UContainer>
</template>
@@ -0,0 +1,85 @@
<script setup lang="ts">
import type { Company } from '~/data/mock'
const company = inject<Company>('company')!
const contacts = computed(() => [
{ icon: 'i-lucide-phone', label: '联系电话', value: company.contact.phone },
{ icon: 'i-lucide-mail', label: '电子邮箱', value: company.contact.email },
{ icon: 'i-lucide-map-pin', label: '办公地址', value: company.contact.address },
{ icon: 'i-lucide-clock', label: '工作时间', value: company.contact.hours }
])
useSeoMeta({
title: `${company.name} - 联系方式 - 链通物流`,
description: `联系${company.name}:电话 ${company.contact.phone},地址 ${company.contact.address}。`
})
</script>
<template>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- 联系信息 -->
<div class="lg:col-span-2 rounded-lg bg-elevated ring ring-default p-6">
<h2 class="font-semibold text-highlighted mb-5">
联系方式
</h2>
<div class="space-y-5">
<div
v-for="item in contacts"
:key="item.label"
class="flex items-start gap-3"
>
<div class="flex items-center justify-center size-9 rounded-lg bg-primary/10 shrink-0">
<UIcon
:name="item.icon"
class="size-4.5 text-primary"
/>
</div>
<div>
<p class="text-sm text-dimmed">
{{ item.label }}
</p>
<p class="text-default font-medium">
{{ item.value }}
</p>
</div>
</div>
</div>
</div>
<!-- 在线沟通(示例) -->
<div class="rounded-lg bg-elevated ring ring-default p-6 flex flex-col">
<h2 class="font-semibold text-highlighted mb-3">
在线沟通
</h2>
<p class="text-sm text-muted leading-relaxed">
登录后可通过站内私信与该企业直接沟通,咨询航线报价、服务方案与合作事宜。
</p>
<UBadge
color="warning"
variant="soft"
icon="i-lucide-clock"
class="mt-4 self-start"
>
示例阶段未开放
</UBadge>
<div class="flex flex-col gap-2 mt-auto pt-6">
<UButton
label="发私信"
icon="i-lucide-message-circle"
disabled
block
/>
<UButton
label="加入行业群聊"
icon="i-lucide-messages-square"
color="neutral"
variant="outline"
disabled
block
/>
</div>
</div>
</div>
</template>
@@ -0,0 +1,157 @@
<script setup lang="ts">
import type { Company } from '~/data/mock'
import { getCompanyArticles } from '~/data/mock'
const company = inject<Company>('company')!
const articles = getCompanyArticles(company.slug).slice(0, 4)
useSeoMeta({
title: `${company.name} - ${company.slogan} - 链通物流`,
description: company.description
})
</script>
<template>
<div>
<!-- 公司简介 -->
<UContainer class="max-w-6xl pt-10 pb-14">
<div class="grid grid-cols-1 lg:grid-cols-5 gap-8 items-center">
<div class="lg:col-span-3">
<p class="text-primary font-semibold tracking-wider mb-2">关于我们 / ABOUT US</p>
<h2 class="text-2xl font-bold text-highlighted mb-4">
{{ company.name }}
</h2>
<p class="text-toned leading-relaxed mb-6">
{{ company.about[0] }}
</p>
<div class="flex flex-wrap gap-8 text-sm">
<div class="flex items-center gap-2 text-muted">
<UIcon name="i-lucide-building-2" class="size-4 text-primary" />
成立于 {{ company.established }} 年
</div>
<div class="flex items-center gap-2 text-muted">
<UIcon name="i-lucide-users" class="size-4 text-primary" />
{{ company.employees }}
</div>
<div class="flex items-center gap-2 text-muted">
<UIcon name="i-lucide-map-pin" class="size-4 text-primary" />
{{ company.city }}
</div>
</div>
<UButton
:to="`/company/${company.slug}/about`"
label="了解更多"
trailing-icon="i-lucide-arrow-right"
variant="link"
class="px-0 mt-2"
/>
</div>
<!-- 服务类型统计卡 -->
<div class="lg:col-span-2 grid grid-cols-2 gap-4">
<div
v-for="service in company.services.slice(0, 4)"
:key="service"
class="rounded-lg bg-elevated ring ring-default p-5 text-center"
>
<p class="text-sm text-muted">{{ service }}</p>
<p class="text-xs text-dimmed mt-1">主营服务</p>
</div>
</div>
</div>
</UContainer>
<!-- 服务项目 -->
<div class="bg-muted/40 py-14">
<UContainer class="max-w-6xl">
<div class="text-center mb-10">
<p class="text-primary font-semibold tracking-wider mb-2">主营业务 / SERVICES</p>
<h2 class="text-2xl font-bold text-highlighted">
服务项目
</h2>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<NuxtLink
v-for="service in company.servicesDetail"
:key="service.name"
:to="`/company/${company.slug}/services`"
class="group rounded-lg bg-elevated ring ring-default hover:ring-primary/50 p-6 transition-colors"
>
<div class="flex items-center justify-center size-11 rounded-lg bg-primary/10 mb-4">
<UIcon :name="company.strengths[0]?.icon ?? 'i-lucide-package'" class="size-5 text-primary" />
</div>
<h3 class="font-semibold text-default mb-2 group-hover:text-primary transition-colors">
{{ service.name }}
</h3>
<p class="text-sm text-muted line-clamp-3 leading-relaxed">
{{ service.description }}
</p>
</NuxtLink>
</div>
</UContainer>
</div>
<!-- 核心优势 -->
<UContainer class="max-w-6xl py-14">
<div class="text-center mb-10">
<p class="text-primary font-semibold tracking-wider mb-2">为什么选择我们 / WHY US</p>
<h2 class="text-2xl font-bold text-highlighted">
核心优势
</h2>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div
v-for="strength in company.strengths"
:key="strength.title"
class="text-center"
>
<div class="flex items-center justify-center size-14 rounded-full bg-primary/10 mx-auto mb-4">
<UIcon :name="strength.icon" class="size-6 text-primary" />
</div>
<h3 class="font-semibold text-default mb-2">
{{ strength.title }}
</h3>
<p class="text-sm text-muted leading-relaxed">
{{ strength.description }}
</p>
</div>
</div>
</UContainer>
<!-- 新闻资讯 -->
<div class="bg-muted/40 py-14">
<UContainer class="max-w-6xl">
<div class="flex items-center justify-between mb-8">
<div>
<p class="text-primary font-semibold tracking-wider mb-2">公司动态 / NEWS</p>
<h2 class="text-2xl font-bold text-highlighted">
新闻资讯
</h2>
</div>
<UButton
:to="`/company/${company.slug}/articles`"
label="更多文章"
trailing-icon="i-lucide-arrow-right"
color="neutral"
variant="outline"
size="sm"
/>
</div>
<div v-if="articles.length" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<ArticleCard
v-for="article in articles"
:key="article.id"
:article="article"
/>
</div>
<p v-else class="text-muted text-center py-8">
暂无新闻动态
</p>
</UContainer>
</div>
</div>
</template>
@@ -0,0 +1,65 @@
<script setup lang="ts">
import type { Company } from '~/data/mock'
const company = inject<Company>('company')!
useSeoMeta({
title: `${company.name} - 服务 - 链通物流`,
description: `${company.name} 的主营服务:${company.services.join('、')}。`
})
</script>
<template>
<UContainer class="max-w-6xl py-8">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div
v-for="service in company.servicesDetail"
:key="service.name"
class="rounded-lg bg-elevated ring ring-default p-6"
>
<div class="flex items-center justify-between mb-2">
<h3 class="font-semibold text-default">
{{ service.name }}
</h3>
<UBadge
color="primary"
variant="soft"
size="sm"
>
在营
</UBadge>
</div>
<p class="text-sm text-muted leading-relaxed">
{{ service.description }}
</p>
</div>
</div>
<!-- 覆盖航线 -->
<div class="rounded-lg bg-elevated ring ring-default p-6 mt-6">
<h2 class="font-semibold text-highlighted mb-4">
覆盖航线
</h2>
<div class="flex flex-wrap gap-2">
<UBadge
v-for="route in company.routes"
:key="route"
color="primary"
variant="soft"
size="lg"
>
{{ route }}
</UBadge>
</div>
<p class="text-sm text-muted mt-4">
需要该企业的航线报价或服务方案?
<NuxtLink
:to="`/company/${company.slug}/contact`"
class="text-primary hover:underline"
>
联系他们 →
</NuxtLink>
</p>
</div>
</UContainer>
</template>
@@ -0,0 +1,142 @@
<script setup lang="ts">
import { companies, regionOptions, routeOptions, serviceOptions } from '~/data/mock'
// USelect 的候选项不允许 value 为空字符串(Reka UI 会直接抛错导致页面水合失败),
// 因此「全部」使用 'all' 作为哨兵值,筛选时再转换为「不限制」。
const ANY = 'all'
const route = useRoute()
const keyword = ref(typeof route.query.q === 'string' ? route.query.q : '')
const region = ref<string>(ANY)
const routeFilter = ref<string>(ANY)
const serviceFilter = ref<string>(ANY)
const verifiedOnly = ref(false)
// 平台主导航的搜索框会带 q 跳转;目录页内再次搜索时组件复用,需同步 query
watch(() => route.query.q, (q) => {
if (typeof q === 'string') {
keyword.value = q
}
})
const toItems = (options: string[], label: string) => [
{ label: `全部${label}`, value: ANY },
...options.map(o => ({ label: o, value: o }))
]
const filtered = computed(() => {
const kw = keyword.value.trim().toLowerCase()
return companies.filter((c) => {
if (verifiedOnly.value && !c.verified) return false
if (region.value !== ANY && c.region !== region.value) return false
if (routeFilter.value !== ANY && !c.routes.includes(routeFilter.value)) return false
if (serviceFilter.value !== ANY && !c.services.includes(serviceFilter.value)) return false
if (kw && !(`${c.name}${c.slogan}${c.description}${c.city}`.toLowerCase().includes(kw))) return false
return true
})
})
function resetFilters() {
keyword.value = ''
region.value = ANY
routeFilter.value = ANY
serviceFilter.value = ANY
verifiedOnly.value = false
}
useSeoMeta({
title: '企业目录 - 链通物流',
description: '按航线、服务类型与地区筛选物流企业:海运整箱、拼箱、空运、铁路联运、报关报检、仓储与跨境电商物流服务商目录。'
})
</script>
<template>
<UPage>
<UPageHeader
headline="企业目录"
title="查找物流服务商"
description="按航线、服务类型、地区筛选,或直接搜索企业名称与主营关键词。"
/>
<UPageBody>
<UContainer>
<!-- 筛选器 -->
<div class="flex flex-col lg:flex-row gap-3 lg:items-center mb-6">
<UInput
v-model="keyword"
icon="i-lucide-search"
placeholder="搜索企业名称、主营关键词…"
class="flex-1"
size="lg"
/>
<div class="flex flex-wrap gap-3 items-center">
<USelect
v-model="region"
:items="toItems(regionOptions, '地区')"
icon="i-lucide-map-pin"
class="w-32"
/>
<USelect
v-model="routeFilter"
:items="toItems(routeOptions, '航线')"
icon="i-lucide-globe"
class="w-36"
/>
<USelect
v-model="serviceFilter"
:items="toItems(serviceOptions, '服务')"
icon="i-lucide-ship"
class="w-40"
/>
<UCheckbox
v-model="verifiedOnly"
label="仅认证企业"
icon="i-lucide-badge-check"
/>
</div>
</div>
<!-- 结果统计 -->
<p class="text-sm text-muted mb-4">
共找到 <span class="font-semibold text-default">{{ filtered.length }}</span> 家企业
</p>
<!-- 企业列表 -->
<div
v-if="filtered.length"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
>
<CompanyCard
v-for="company in filtered"
:key="company.slug"
:company="company"
/>
</div>
<div
v-else
class="flex flex-col items-center gap-3 py-20 text-center"
>
<UIcon
name="i-lucide-search-x"
class="size-10 text-dimmed"
/>
<p class="text-toned">
没有符合条件的企业,试试放宽筛选条件
</p>
<UButton
label="重置筛选"
color="neutral"
variant="outline"
icon="i-lucide-rotate-ccw"
@click="resetFilters"
/>
</div>
<div class="h-12" />
</UContainer>
</UPageBody>
</UPage>
</template>
@@ -0,0 +1,149 @@
<script setup lang="ts">
import { companies, getLatestArticles, getCompany } from '~/data/mock'
const latestCompanies = companies.slice(0, 3)
const latestArticles = getLatestArticles(3)
const stats = [
{ label: '入驻物流企业', value: `${companies.length * 37}+` },
{ label: '覆盖航线', value: '8 大航线' },
{ label: '行业资讯文章', value: `${companies.length * 26}+` },
{ label: '认证企业占比', value: '85%' }
]
useSeoMeta({
title: '链通物流 - 物流企业连接平台',
description: '按航线与服务类型查找物流服务商,浏览企业动态与行业资讯,与物流企业直接建立联系。'
})
</script>
<template>
<div>
<UPageHero
title="找到你的下一位物流合作伙伴"
description="链通物流是物流企业的目录、内容与沟通网络。按航线、服务类型与地区查找认证物流服务商,浏览企业动态与行业资讯,在线建立联系。"
:links="[{
label: '浏览企业目录',
to: '/directory',
icon: 'i-lucide-search',
size: 'xl'
}, {
label: '入驻平台',
to: '/directory',
color: 'neutral',
variant: 'subtle',
icon: 'i-lucide-building-2',
size: 'xl'
}]"
/>
<!-- 平台数据 -->
<UPageSection
:ui="{
container: 'py-8 sm:py-10',
title: 'sr-only',
description: 'sr-only'
}"
title="平台数据"
>
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
<div
v-for="stat in stats"
:key="stat.label"
class="flex flex-col items-center gap-1 rounded-lg bg-elevated ring ring-default py-6"
>
<span class="text-2xl sm:text-3xl font-bold text-primary">{{ stat.value }}</span>
<span class="text-sm text-muted">{{ stat.label }}</span>
</div>
</div>
</UPageSection>
<!-- 平台价值 -->
<UPageSection
headline="为什么选择链通物流"
title="一个平台,连接整个物流行业"
description="从发现到建立联系,物流企业间的合作在这里完成。"
:features="[{
icon: 'i-lucide-search',
title: '精准发现',
description: '按航线、服务类型、地区与认证状态筛选,快速定位匹配的物流服务商。'
}, {
icon: 'i-lucide-file-text',
title: '内容与实力背书',
description: '企业空间 + 行业资讯与案例分享,让合作决策有据可依。'
}, {
icon: 'i-lucide-messages-square',
title: '直接建立联系',
description: '站内私信与行业群聊,从「找到对方」到「谈成合作」一站完成。'
}, {
icon: 'i-lucide-badge-check',
title: '平台认证',
description: '营业执照审核认证体系,降低企业间合作的信任成本。'
}]"
/>
<!-- 最新入驻企业 -->
<UPageSection
headline="企业目录"
title="最新入驻企业"
description="来自全国各地的海运、空运、铁路与跨境物流服务商。"
:ui="{ container: 'py-8 sm:py-12' }"
>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<CompanyCard
v-for="company in latestCompanies"
:key="company.slug"
:company="company"
/>
</div>
<div class="flex justify-center mt-8">
<UButton
to="/directory"
label="查看全部企业"
trailing-icon="i-lucide-arrow-right"
color="neutral"
variant="outline"
/>
</div>
</UPageSection>
<!-- 最新资讯 -->
<UPageSection
headline="资讯"
title="行业动态与企业分享"
description="运价走势、航线动态、实操案例,来自平台企业的真实分享。"
:ui="{ container: 'py-8 sm:py-12' }"
>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<ArticleCard
v-for="article in latestArticles"
:key="article.id"
:article="article"
:company="getCompany(article.companySlug)"
show-company
/>
</div>
<div class="flex justify-center mt-8">
<UButton
to="/news"
label="浏览全部资讯"
trailing-icon="i-lucide-arrow-right"
color="neutral"
variant="outline"
/>
</div>
</UPageSection>
<UPageCTA
title="让物流同行找到你"
description="创建企业空间,发布服务与文章,被需要你的客户与同行看见。"
:links="[{
label: '入驻平台',
to: '/directory',
icon: 'i-lucide-building-2'
}]"
/>
</div>
</template>
@@ -0,0 +1,134 @@
<script setup lang="ts">
import { getArticle, getCompany, getLatestArticles } from '~/data/mock'
const route = useRoute()
const article = getArticle(route.params.id as string)
if (!article) {
throw createError({
statusCode: 404,
statusMessage: '文章不存在',
fatal: true
})
}
const company = getCompany(article.companySlug)
const related = getLatestArticles(4).filter(a => a.id !== article.id).slice(0, 3)
useSeoMeta({
title: `${article.title} - 链通物流`,
description: article.summary,
ogTitle: article.title,
ogDescription: article.summary,
articlePublishedTime: article.date
})
</script>
<template>
<UPage>
<UPageBody>
<UContainer class="max-w-3xl">
<UBreadcrumb
:items="[{
label: '资讯',
to: '/news'
}, {
label: article.category
}]"
class="mb-6"
/>
<article v-if="article">
<!-- 文章头部 -->
<div class="flex items-center gap-2 mb-3 text-sm text-dimmed">
<UBadge
:color="article.category === '行业资讯' ? 'primary' : article.category === '案例分享' ? 'info' : 'neutral'"
variant="soft"
>
{{ article.category }}
</UBadge>
<span class="flex items-center gap-1">
<UIcon
name="i-lucide-calendar"
class="size-4"
/>
{{ article.date }}
</span>
<span class="flex items-center gap-1">
<UIcon
name="i-lucide-clock"
class="size-4"
/>
{{ article.readMinutes }} 分钟阅读
</span>
</div>
<h1 class="text-2xl sm:text-3xl font-bold text-highlighted leading-tight mb-8">
{{ article.title }}
</h1>
<!-- 内容 -->
<div class="space-y-5 text-default leading-relaxed">
<p
v-for="(paragraph, i) in article.content"
:key="i"
>
{{ paragraph }}
</p>
</div>
<USeparator class="my-10" />
<!-- 来源企业 -->
<NuxtLink
v-if="company"
:to="`/company/${company.slug}`"
class="flex items-center gap-3 rounded-lg bg-elevated ring ring-default hover:ring-primary/50 p-4 transition-colors"
>
<CompanyLogo
:name="company.name"
:color="company.color"
/>
<div class="flex-1 min-w-0">
<p class="text-sm text-dimmed">发布自</p>
<p class="font-semibold text-default truncate">{{ company.name }}</p>
</div>
<UBadge
v-if="company.verified"
icon="i-lucide-badge-check"
color="primary"
variant="soft"
>
认证企业
</UBadge>
<UIcon
name="i-lucide-arrow-right"
class="size-5 text-muted"
/>
</NuxtLink>
<!-- 相关阅读 -->
<div
v-if="related.length"
class="mt-10"
>
<h2 class="font-semibold text-highlighted mb-4">
相关阅读
</h2>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<ArticleCard
v-for="rel in related"
:key="rel.id"
:article="rel"
:company="getCompany(rel.companySlug)"
/>
</div>
</div>
</article>
<div class="h-12" />
</UContainer>
</UPageBody>
</UPage>
</template>
@@ -0,0 +1,66 @@
<script setup lang="ts">
import { articles, articleCategories, getCompany } from '~/data/mock'
const activeCategory = ref('')
const sorted = computed(() =>
[...articles].sort((a, b) => b.date.localeCompare(a.date))
)
const filtered = computed(() =>
activeCategory.value
? sorted.value.filter(a => a.category === activeCategory.value)
: sorted.value
)
useSeoMeta({
title: '资讯 - 链通物流',
description: '物流行业运价走势、航线动态与企业实操案例分享,来自平台企业的真实内容。'
})
</script>
<template>
<UPage>
<UPageHeader
headline="资讯"
title="行业动态与企业分享"
description="运价走势、航线动态、实操案例,来自平台物流企业的真实分享。"
/>
<UPageBody>
<UContainer>
<!-- 分类筛选 -->
<div class="flex flex-wrap gap-2 mb-6">
<UButton
label="全部"
:color="activeCategory === '' ? 'primary' : 'neutral'"
:variant="activeCategory === '' ? 'soft' : 'ghost'"
size="sm"
@click="activeCategory = ''"
/>
<UButton
v-for="category in articleCategories"
:key="category"
:label="category"
:color="activeCategory === category ? 'primary' : 'neutral'"
:variant="activeCategory === category ? 'soft' : 'ghost'"
size="sm"
@click="activeCategory = category"
/>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<ArticleCard
v-for="article in filtered"
:key="article.id"
:article="article"
:company="getCompany(article.companySlug)"
show-company
/>
</div>
<div class="h-12" />
</UContainer>
</UPageBody>
</UPage>
</template>