|
|
|
<script lang="ts" setup>
|
|
|
|
import { getAppDetail, getAppList } from "~/api/app";
|
|
|
|
import type { appDetail, Types, appType } from "~/api/types/app";
|
|
|
|
import type { webSiteType } from "~/api/types/webSite";
|
|
|
|
const route = useRoute();
|
|
|
|
const config = useRuntimeConfig();
|
|
|
|
const DetailData = ref<appDetail>({
|
|
|
|
id: 0,
|
|
|
|
title: "",
|
|
|
|
content: "",
|
|
|
|
image: "",
|
|
|
|
description: "",
|
|
|
|
link: "",
|
|
|
|
types: [],
|
|
|
|
});
|
|
|
|
const webSite = useState<webSiteType>("webSite");
|
|
|
|
const relatedApps = ref<appType[]>([]);
|
|
|
|
|
|
|
|
function mergeDuplicates(data: Types[]) {
|
|
|
|
const map = new Map();
|
|
|
|
data.forEach((item) => {
|
|
|
|
if (!map.has(item.id)) {
|
|
|
|
map.set(item.id, {
|
|
|
|
id: item.id,
|
|
|
|
label: item.label,
|
|
|
|
alias: item.alias,
|
|
|
|
children: [...(item.children || [])],
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
const existing = map.get(item.id);
|
|
|
|
const existingChildIds = new Set(
|
|
|
|
existing.children.map((child: any) => child.id),
|
|
|
|
);
|
|
|
|
item.children.forEach((child) => {
|
|
|
|
if (!existingChildIds.has(child.id)) {
|
|
|
|
existing.children.push(child);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return Array.from(map.values());
|
|
|
|
}
|
|
|
|
|
|
|
|
const detailRes = await getAppDetail(Number(route.params.id));
|
|
|
|
DetailData.value = detailRes.data;
|
|
|
|
DetailData.value.types = mergeDuplicates(detailRes.data.types);
|
|
|
|
if (DetailData.value.types?.length > 0) {
|
|
|
|
const firstType = DetailData.value.types[0];
|
|
|
|
const typeAlias = firstType.alias || firstType.children?.[0]?.alias;
|
|
|
|
if (typeAlias) {
|
|
|
|
const relatedRes = await getAppList({
|
|
|
|
pageNum: 1,
|
|
|
|
pageSize: 8,
|
|
|
|
typeAlias: typeAlias,
|
|
|
|
});
|
|
|
|
relatedApps.value = relatedRes.rows
|
|
|
|
.filter((app: appType) => app.id !== DetailData.value.id)
|
|
|
|
.slice(0, 6);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
onMounted(() => {
|
|
|
|
let toggleChineseTraditional = document.getElementById(
|
|
|
|
"toogle-chinese-traditional",
|
|
|
|
);
|
|
|
|
if (toggleChineseTraditional) {
|
|
|
|
// 自动点击
|
|
|
|
toggleChineseTraditional.click();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
useHead({
|
|
|
|
title: DetailData.value.description
|
|
|
|
? `${DetailData.value.title} - ${DetailData.value.description}`
|
|
|
|
: DetailData.value.title,
|
|
|
|
meta: [
|
|
|
|
{ name: "description", content: DetailData.value.description },
|
|
|
|
{
|
|
|
|
name: "keywords",
|
|
|
|
content: `${DetailData.value.title},AI工具,${
|
|
|
|
DetailData.value.types?.map((t: any) => t.label).join(",") || ""
|
|
|
|
}`,
|
|
|
|
},
|
|
|
|
{ name: "robots", content: "index, follow" },
|
|
|
|
{
|
|
|
|
property: "og:title",
|
|
|
|
content: `${DetailData.value.title}${
|
|
|
|
DetailData.value.popupContent != null
|
|
|
|
? ` - ${DetailData.value.popupContent}`
|
|
|
|
: `- ${DetailData.value.description} | ${webSite.value.webname.slice(
|
|
|
|
0,
|
|
|
|
7,
|
|
|
|
)}`
|
|
|
|
}`,
|
|
|
|
},
|
|
|
|
{ property: "og:description", content: DetailData.value.description },
|
|
|
|
{ property: "og:type", content: "article" },
|
|
|
|
{
|
|
|
|
property: "og:image",
|
|
|
|
content: config.public.baseUrl + DetailData.value.image,
|
|
|
|
},
|
|
|
|
{ property: "og:url", content: config.public.baseUrl + route.path },
|
|
|
|
{ property: "og:site_name", content: webSite.value.webname.slice(0, 7) },
|
|
|
|
{ property: "og:updated_time", content: DetailData.value.updateTime },
|
|
|
|
{
|
|
|
|
property: "article:published_time",
|
|
|
|
content: DetailData.value.updateTime,
|
|
|
|
},
|
|
|
|
{ property: "twitter:card", content: "summary_large_image" },
|
|
|
|
{ property: "twitter:title", content: DetailData.value.title },
|
|
|
|
{ property: "twitter:description", content: DetailData.value.description },
|
|
|
|
{
|
|
|
|
property: "twitter:image",
|
|
|
|
content: config.public.baseUrl + DetailData.value.image,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
link: [
|
|
|
|
{
|
|
|
|
rel: "canonical",
|
|
|
|
href: config.public.baseUrl + route.path,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
script: [
|
|
|
|
{
|
|
|
|
type: "application/ld+json",
|
|
|
|
children: JSON.stringify({
|
|
|
|
"@context": "https://schema.org",
|
|
|
|
"@graph": [
|
|
|
|
{
|
|
|
|
"@type": "SoftwareApplication",
|
|
|
|
"@id": config.public.baseUrl + route.path + "#software",
|
|
|
|
name: DetailData.value.title,
|
|
|
|
description: DetailData.value.description,
|
|
|
|
url: DetailData.value.link,
|
|
|
|
image: config.public.baseUrl + DetailData.value.image,
|
|
|
|
applicationCategory: "UtilitiesApplication",
|
|
|
|
operatingSystem: "Web Browser",
|
|
|
|
offers: {
|
|
|
|
"@type": "Offer",
|
|
|
|
price: "0",
|
|
|
|
priceCurrency: "CNY",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"@type": "Article",
|
|
|
|
"@id": config.public.baseUrl + route.path + "#article",
|
|
|
|
headline: DetailData.value.title,
|
|
|
|
description: DetailData.value.description,
|
|
|
|
image: config.public.baseUrl + DetailData.value.image,
|
|
|
|
datePublished: DetailData.value.updateTime,
|
|
|
|
dateModified: DetailData.value.updateTime,
|
|
|
|
author: {
|
|
|
|
"@id": "https://aiboxgo.com/#organization",
|
|
|
|
},
|
|
|
|
publisher: {
|
|
|
|
"@id": "https://aiboxgo.com/#organization",
|
|
|
|
},
|
|
|
|
mainEntityOfPage: {
|
|
|
|
"@type": "WebPage",
|
|
|
|
"@id": config.public.baseUrl + route.path,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"@type": "BreadcrumbList",
|
|
|
|
"@id": config.public.baseUrl + route.path + "#breadcrumb",
|
|
|
|
itemListElement: [
|
|
|
|
{
|
|
|
|
"@type": "ListItem",
|
|
|
|
position: 1,
|
|
|
|
name: "首页",
|
|
|
|
item: "https://aiboxgo.com/",
|
|
|
|
},
|
|
|
|
...(DetailData.value.types?.[0]
|
|
|
|
? [
|
|
|
|
{
|
|
|
|
"@type": "ListItem",
|
|
|
|
position: 2,
|
|
|
|
name: DetailData.value.types[0].label,
|
|
|
|
item: `https://aiboxgo.com/category/${DetailData.value.types[0].alias}`,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
: []),
|
|
|
|
{
|
|
|
|
"@type": "ListItem",
|
|
|
|
position: DetailData.value.types?.length ? 3 : 2,
|
|
|
|
name: DetailData.value.title,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<div
|
|
|
|
class="flex flex-col min-h-screen bg-white dark:bg-[#1a1b1d] transition-colors duration-300"
|
|
|
|
>
|
|
|
|
<a
|
|
|
|
id="toogle-chinese-traditional"
|
|
|
|
href="javascript:translate.changeLanguage('chinese_traditional');"
|
|
|
|
class="ignore"
|
|
|
|
></a>
|
|
|
|
<main class="flex-grow bg-white dark:bg-[#1a1b1d]">
|
|
|
|
<div class="relative overflow-hidden">
|
|
|
|
<CommonParticleBackground
|
|
|
|
:particleCount="60"
|
|
|
|
particleColor="#5961f9"
|
|
|
|
lineColor="#7c3aed"
|
|
|
|
:particleSize="2"
|
|
|
|
:lineDistance="100"
|
|
|
|
:speed="0.3"
|
|
|
|
/>
|
|
|
|
<div
|
|
|
|
class="absolute inset-0 bg-gradient-to-r from-[#5961f9]/10 via-[#7c3aed]/10 to-[#a855f7]/10 dark:from-[#5961f9]/5 dark:via-[#7c3aed]/5 dark:to-[#a855f7]/5"
|
|
|
|
></div>
|
|
|
|
|
|
|
|
<div class="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6 items-start">
|
|
|
|
<div class="lg:col-span-2 flex justify-center lg:justify-end">
|
|
|
|
<div class="relative group">
|
|
|
|
<div
|
|
|
|
class="absolute -inset-1 bg-gradient-to-r from-[#5961f9] to-[#a855f7] rounded-2xl blur opacity-30 group-hover:opacity-50 transition duration-300"
|
|
|
|
></div>
|
|
|
|
<img
|
|
|
|
:src="config.public.apiUrl + DetailData.image"
|
|
|
|
:alt="DetailData.title"
|
|
|
|
class="relative w-24 h-24 md:w-32 md:h-32 object-contain bg-white dark:bg-gray-800 rounded-2xl shadow-xl p-3"
|
|
|
|
loading="lazy"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="lg:col-span-7 space-y-4">
|
|
|
|
<div class="flex items-center gap-3 flex-wrap">
|
|
|
|
<h1
|
|
|
|
class="text-2xl md:text-3xl font-bold text-[#282a2d] dark:text-[#c6c9cf]"
|
|
|
|
>
|
|
|
|
{{ DetailData.title }}
|
|
|
|
</h1>
|
|
|
|
<span
|
|
|
|
v-if="DetailData.popupContent"
|
|
|
|
class="px-3 py-1 bg-gradient-to-r from-[#5961f9] to-[#a855f7] text-white text-xs rounded-full"
|
|
|
|
>
|
|
|
|
{{ DetailData.popupContent }}
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex flex-wrap gap-2">
|
|
|
|
<template v-for="tag in DetailData.types" :key="tag.id">
|
|
|
|
<template v-if="tag.children && tag.children.length > 0">
|
|
|
|
<NuxtLink
|
|
|
|
v-for="child in tag.children"
|
|
|
|
:key="child.id"
|
|
|
|
:to="'/category/' + child.alias"
|
|
|
|
class="px-3 py-1.5 bg-white/80 dark:bg-gray-800/80 text-[#5961f9] dark:text-[#8b92f9] rounded-full text-sm hover:bg-[#5961f9] hover:text-white dark:hover:bg-[#5961f9] transition-all duration-300 shadow-sm"
|
|
|
|
>
|
|
|
|
{{ child.label }}
|
|
|
|
</NuxtLink>
|
|
|
|
</template>
|
|
|
|
<template v-else>
|
|
|
|
<NuxtLink
|
|
|
|
:to="'/category/' + tag.alias"
|
|
|
|
class="px-3 py-1.5 bg-white/80 dark:bg-gray-800/80 text-[#5961f9] dark:text-[#8b92f9] rounded-full text-sm hover:bg-[#5961f9] hover:text-white dark:hover:bg-[#5961f9] transition-all duration-300 shadow-sm"
|
|
|
|
>
|
|
|
|
{{ tag.label }}
|
|
|
|
</NuxtLink>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<p
|
|
|
|
class="text-gray-600 dark:text-gray-300 text-base leading-relaxed line-clamp-3"
|
|
|
|
>
|
|
|
|
{{ DetailData.description }}
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<div class="flex flex-wrap gap-3 pt-2">
|
|
|
|
<a
|
|
|
|
:href="DetailData.link"
|
|
|
|
target="_blank"
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
class="inline-flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-[#5961f9] to-[#7c3aed] hover:from-[#4751e8] hover:to-[#6d28d9] text-white font-medium rounded-xl shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 transition-all duration-300"
|
|
|
|
>
|
|
|
|
<i class="iconfont icon-guide text-lg"></i>
|
|
|
|
<span>立即访问</span>
|
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="lg:col-span-3">
|
|
|
|
<div
|
|
|
|
class="bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm rounded-2xl shadow-lg p-4 border border-gray-100 dark:border-gray-700"
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
class="text-center text-gray-400 dark:text-gray-500 text-sm"
|
|
|
|
>
|
|
|
|
<a
|
|
|
|
no_cache=""
|
|
|
|
href="https://www.coze.cn/?utm_medium=daohang&utm_source=aikit&utm_content=&utm_id=&utm_campaign=&utm_term=hw_coze_aikit&utm_source_platform="
|
|
|
|
rel="external nofollow"
|
|
|
|
target="_blank"
|
|
|
|
><img
|
|
|
|
src="https://ai-kit.cn/wp-content/uploads/2026/01/kouzi_gd.jpg"
|
|
|
|
alt="扣子"
|
|
|
|
/></a>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
|
|
<article class="prose prose-lg dark:prose-invert max-w-none p-6 md:p-8">
|
|
|
|
<div v-html="DetailData.content" class="detail-content"></div>
|
|
|
|
</article>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div
|
|
|
|
v-if="relatedApps.length > 0"
|
|
|
|
class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"
|
|
|
|
>
|
|
|
|
<div class="rounded-2xl p-6">
|
|
|
|
<h2
|
|
|
|
class="text-xl font-bold text-[#555] dark:text-[#888] mb-6 flex items-center gap-2"
|
|
|
|
>
|
|
|
|
<i class="iconfont icon-tag" style="font-size: 1.2rem"></i>
|
|
|
|
相关推荐
|
|
|
|
</h2>
|
|
|
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-3 gap-4">
|
|
|
|
<CommonCard :cardList="relatedApps" routePath="/hktw" />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</main>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
|
|
|
.line-clamp-3 {
|
|
|
|
display: -webkit-box;
|
|
|
|
-webkit-line-clamp: 3;
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content {
|
|
|
|
@apply text-[#282a2d] dark:text-[#c6c9cf];
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content :deep(h1),
|
|
|
|
.detail-content :deep(h2),
|
|
|
|
.detail-content :deep(h3),
|
|
|
|
.detail-content :deep(h4) {
|
|
|
|
@apply text-[#282a2d] my-5 py-1 pl-5 border-l-4 border-[#5961f9] dark:text-[#c6c9cf];
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content :deep(h1) {
|
|
|
|
@apply text-2xl;
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content :deep(h2) {
|
|
|
|
@apply text-xl;
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content :deep(h3) {
|
|
|
|
@apply text-2xl;
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content :deep(p) {
|
|
|
|
@apply mb-4 leading-relaxed;
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content :deep(img) {
|
|
|
|
@apply rounded-lg max-w-full h-auto my-4;
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content :deep(a) {
|
|
|
|
@apply text-[#5961f9] hover:underline;
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content :deep(ul),
|
|
|
|
.detail-content :deep(ol) {
|
|
|
|
@apply pl-6 mb-4 text-sm list-disc;
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content :deep(li) {
|
|
|
|
@apply mb-2;
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content :deep(blockquote) {
|
|
|
|
@apply border-l-4 border-[#5961f9] pl-4 italic my-4;
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content :deep(code) {
|
|
|
|
@apply bg-gray-100 dark:bg-gray-700 px-1 py-0.5 rounded text-sm;
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content :deep(pre) {
|
|
|
|
@apply bg-gray-100 dark:bg-gray-700 p-4 rounded-lg overflow-x-auto my-4;
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content :deep(table) {
|
|
|
|
@apply w-full border-collapse my-4;
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content :deep(th),
|
|
|
|
.detail-content :deep(td) {
|
|
|
|
@apply border border-gray-200 dark:border-gray-600 px-4 py-2;
|
|
|
|
}
|
|
|
|
|
|
|
|
.detail-content :deep(th) {
|
|
|
|
@apply bg-gray-100 dark:bg-gray-700 font-semibold;
|
|
|
|
}
|
|
|
|
</style> |
...
|
...
|
|