|
|
|
<script setup>
|
|
|
|
const route = useRoute();
|
|
|
|
const config = useRuntimeConfig();
|
|
|
|
const showAd = ref(true);
|
|
|
|
const appDetail = ref({
|
|
|
|
types: [],
|
|
|
|
});
|
|
|
|
const webSite = useState("webSite");
|
|
|
|
const detailAd = ref({
|
|
|
|
width: 300,
|
|
|
|
height: 177,
|
|
|
|
frontAdVos: [],
|
|
|
|
});
|
|
|
|
function mergeDuplicates(data) {
|
|
|
|
const map = new Map();
|
|
|
|
|
|
|
|
data.forEach((item) => {
|
|
|
|
if (!map.has(item.id)) {
|
|
|
|
// 如果是第一次遇到这个id,创建新对象
|
|
|
|
map.set(item.id, {
|
|
|
|
id: item.id,
|
|
|
|
label: item.label,
|
|
|
|
children: [...(item.children || [])],
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// 如果已经存在,合并children
|
|
|
|
const existing = map.get(item.id);
|
|
|
|
// 避免重复的子项(基于子项id)
|
|
|
|
const existingChildIds = new Set(
|
|
|
|
existing.children.map((child) => child.id)
|
|
|
|
);
|
|
|
|
item.children.forEach((child) => {
|
|
|
|
if (!existingChildIds.has(child.id)) {
|
|
|
|
existing.children.push(child);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return Array.from(map.values());
|
|
|
|
}
|
|
|
|
const { data: detailData } = await useFetch(
|
|
|
|
`http://aitoolht.crgx.net/dh/app/${route.params.id}`
|
|
|
|
);
|
|
|
|
const { data: adData } = await useFetch(
|
|
|
|
"http://aitoolht.crgx.net/dh/ad/listFrontAd",
|
|
|
|
{
|
|
|
|
method: "get",
|
|
|
|
params: { pageSize: 10, pageNum: 1, code: "top" },
|
|
|
|
}
|
|
|
|
);
|
|
|
|
detailAd.value = adData.value.rows[0];
|
|
|
|
appDetail.value = detailData.value.data;
|
|
|
|
appDetail.value.types = mergeDuplicates(detailData.value.data.types);
|
|
|
|
|
|
|
|
useHead({
|
|
|
|
title: appDetail.value.popupContent
|
|
|
|
? `${appDetail.value.title} - ${appDetail.value.popupContent}`
|
|
|
|
: appDetail.value.title,
|
|
|
|
meta: [
|
|
|
|
{ name: "description", content: appDetail.value.description },
|
|
|
|
{
|
|
|
|
name: "og:title",
|
|
|
|
content: `${appDetail.value.title}-${appDetail.value.popupContent}`,
|
|
|
|
},
|
|
|
|
{ name: "og:description", content: appDetail.value.description },
|
|
|
|
{
|
|
|
|
name: "og:image",
|
|
|
|
content: config.public.baseUrl + appDetail.value.image,
|
|
|
|
},
|
|
|
|
{ name: "og:url", content: route.fullPath },
|
|
|
|
{ name: "og:site_name", content: webSite.value.webname },
|
|
|
|
],
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<div class="flex flex-col min-h-screen bg-white">
|
|
|
|
<main class="flex-grow md:p-6 bg-white p-1">
|
|
|
|
<!-- Top Application Info Bar -->
|
|
|
|
<header
|
|
|
|
v-show="appDetail.types.length > 0"
|
|
|
|
class="bg-white shadow-sm md:py-4 md:px-8 py-2 px-4 flex md:items-center md:justify-between flex-col md:flex-row"
|
|
|
|
>
|
|
|
|
<div class="flex items-center space-x-4">
|
|
|
|
<img
|
|
|
|
:src="config.public.baseUrl + appDetail.image"
|
|
|
|
alt="App Icon"
|
|
|
|
class="w-16 h-16 object-contain"
|
|
|
|
/>
|
|
|
|
<div>
|
|
|
|
<h1 class="text-2xl font-bold text-[#5961f9]">
|
|
|
|
{{ appDetail.title }}
|
|
|
|
</h1>
|
|
|
|
<p class="text-sm text-gray-600 mt-1">
|
|
|
|
{{ appDetail.description }}
|
|
|
|
</p>
|
|
|
|
<div class="mt-2 flex items-center space-x-2">
|
|
|
|
<div
|
|
|
|
v-for="tag in appDetail.types"
|
|
|
|
class="flex items-center space-x-2"
|
|
|
|
>
|
|
|
|
<template v-if="tag.children.length > 0">
|
|
|
|
<NuxtLink
|
|
|
|
v-for="child in tag.children"
|
|
|
|
:to="'/category/' + child.alias"
|
|
|
|
class="px-2 py-1 bg-blue-100 text-[#5961f9] rounded-full text-xs"
|
|
|
|
>
|
|
|
|
{{ child.label }}
|
|
|
|
</NuxtLink>
|
|
|
|
</template>
|
|
|
|
<template v-else>
|
|
|
|
<NuxtLink
|
|
|
|
:to="'/category/' + tag.alias"
|
|
|
|
class="px-2 py-1 bg-blue-100 text-[#5961f9] rounded-full text-xs"
|
|
|
|
>
|
|
|
|
{{ tag.label }}
|
|
|
|
</NuxtLink>
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="flex md:space-x-3 md:mt-0 mt-4">
|
|
|
|
<a
|
|
|
|
:href="appDetail.link"
|
|
|
|
target="_blank"
|
|
|
|
class="!rounded-button whitespace-nowrap px-4 py-2 bg-[#5961f9] max-[768px]:text-xs text-white hover:bg-blue-600 transition-colors"
|
|
|
|
>
|
|
|
|
<i class="iconfont icon-guide"></i>访问官网
|
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
</header>
|
|
|
|
|
|
|
|
<main class="relative w-full">
|
|
|
|
<!-- 悬浮广告弹窗 -->
|
|
|
|
<div
|
|
|
|
class="md:absolute top-0 right-0 md:m-4 z-50 relative max-[768px]:m-auto"
|
|
|
|
v-show="showAd"
|
|
|
|
:style="{
|
|
|
|
width: `${detailAd.width}px`,
|
|
|
|
height: `${detailAd.height}px`,
|
|
|
|
}"
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
class="w-full h-full relative"
|
|
|
|
v-for="item in detailAd.frontAdVos"
|
|
|
|
>
|
|
|
|
<img
|
|
|
|
class="w-full h-full object-contain"
|
|
|
|
:src="config.public.baseUrl + item.image"
|
|
|
|
:alt="item.title"
|
|
|
|
/>
|
|
|
|
<div
|
|
|
|
class="absolute top-1 right-1 cursor-pointer bg-white w-4 h-4 text-center rounded-[50%] text-xs"
|
|
|
|
@click="showAd = false"
|
|
|
|
>
|
|
|
|
X
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="md:max-w-5xl mx-auto md:p-8 p-2 w-full">
|
|
|
|
<div v-html="appDetail.content"></div>
|
|
|
|
</div>
|
|
|
|
</main>
|
|
|
|
</main>
|
|
|
|
</div>
|
|
|
|
</template> |
...
|
...
|
|