作者 xiaoqiu

完成繁体页面

不能预览此文件类型
@@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
28 </header> 28 </header>
29 </template> 29 </template>
30 30
31 -<script setup> 31 +<script setup lang="ts">
32 const webSite = useState("webSite"); 32 const webSite = useState("webSite");
33 </script> 33 </script>
34 34
@@ -77,7 +77,6 @@ const route = useRoute(); @@ -77,7 +77,6 @@ const route = useRoute();
77 const router = useRouter(); 77 const router = useRouter();
78 const config = useRuntimeConfig(); 78 const config = useRuntimeConfig();
79 const { setActiveSubCategory } = useActiveSubCategory(); 79 const { setActiveSubCategory } = useActiveSubCategory();
80 -  
81 const toggleExpand = (index: number) => { 80 const toggleExpand = (index: number) => {
82 if (activeCategory.value === index) { 81 if (activeCategory.value === index) {
83 activeCategory.value = null; 82 activeCategory.value = null;
@@ -97,11 +96,10 @@ const scrollToElement = (elementId: string) => { @@ -97,11 +96,10 @@ const scrollToElement = (elementId: string) => {
97 } 96 }
98 return false; 97 return false;
99 }; 98 };
100 -  
101 const handleCategoryClick = ( 99 const handleCategoryClick = (
102 event: Event, 100 event: Event,
103 id: number, 101 id: number,
104 - SubCategory: classifyType 102 + SubCategory: classifyType,
105 ) => { 103 ) => {
106 event.preventDefault(); 104 event.preventDefault();
107 if (SubCategory.children && SubCategory.children.length > 0) { 105 if (SubCategory.children && SubCategory.children.length > 0) {
@@ -111,9 +109,11 @@ const handleCategoryClick = ( @@ -111,9 +109,11 @@ const handleCategoryClick = (
111 setActiveSubCategory(null); 109 setActiveSubCategory(null);
112 } 110 }
113 const targetId = `term-${id}`; 111 const targetId = `term-${id}`;
114 -  
115 - if (route.path === "/") { 112 + // 如果在首页,滚动到目标元素或者以hktw首页
  113 + if (route.path === "/" || route.path === "/hktw") {
116 scrollToElement(targetId); 114 scrollToElement(targetId);
  115 + } else if (route.path.startsWith("/hktw/")) {
  116 + router.push(`/hktw#${targetId}`);
117 } else { 117 } else {
118 router.push(`/#${targetId}`); 118 router.push(`/#${targetId}`);
119 } 119 }
@@ -122,7 +122,7 @@ const handleCategoryClick = ( @@ -122,7 +122,7 @@ const handleCategoryClick = (
122 const handleSubCategoryClick = ( 122 const handleSubCategoryClick = (
123 event: Event, 123 event: Event,
124 parentId: number, 124 parentId: number,
125 - subId: number 125 + subId: number,
126 ) => { 126 ) => {
127 event.preventDefault(); 127 event.preventDefault();
128 const targetId = `term-${parentId}-${subId}`; 128 const targetId = `term-${parentId}-${subId}`;
@@ -17,8 +17,8 @@ @@ -17,8 +17,8 @@
17 @confirm="onConfirm(item.id)" 17 @confirm="onConfirm(item.id)"
18 > 18 >
19 <template #reference> 19 <template #reference>
20 - <a  
21 - :href="config.public.baseUrl + '/site-details/' + item.id" 20 + <nuxt-link
  21 + :to="routePath + '/site-details/' + item.id"
22 target="_blank" 22 target="_blank"
23 @click.stop="onNuxtLink" 23 @click.stop="onNuxtLink"
24 > 24 >
@@ -44,12 +44,12 @@ @@ -44,12 +44,12 @@
44 </div> 44 </div>
45 </div> 45 </div>
46 </div> 46 </div>
47 - </a> 47 + </nuxt-link>
48 </template> 48 </template>
49 </el-popconfirm> 49 </el-popconfirm>
50 - <a 50 + <nuxt-link
51 v-else 51 v-else
52 - :href="config.public.baseUrl + '/site-details/' + item.id" 52 + :to="routePath + '/site-details/' + item.id"
53 target="_blank" 53 target="_blank"
54 > 54 >
55 <div class="group p-3"> 55 <div class="group p-3">
@@ -74,15 +74,22 @@ @@ -74,15 +74,22 @@
74 </div> 74 </div>
75 </div> 75 </div>
76 </div> 76 </div>
77 - </a> 77 + </nuxt-link>
78 </div> 78 </div>
79 </template> 79 </template>
80 80
81 <script lang="ts" setup> 81 <script lang="ts" setup>
82 import { Promotion } from "@element-plus/icons-vue"; 82 import { Promotion } from "@element-plus/icons-vue";
83 -defineProps<{ 83 +// 设置默认值
  84 +const props = withDefaults(
  85 + defineProps<{
84 cardList: any[]; 86 cardList: any[];
85 -}>(); 87 + routePath?: string; // 必须加 ? 表示可选
  88 + }>(),
  89 + {
  90 + routePath: "", // 默认值
  91 + },
  92 +);
86 93
87 const config = useRuntimeConfig(); 94 const config = useRuntimeConfig();
88 // 阻止默认行为 95 // 阻止默认行为
@@ -91,6 +98,6 @@ function onNuxtLink(event: any) { @@ -91,6 +98,6 @@ function onNuxtLink(event: any) {
91 } 98 }
92 // 点击确认跳转 99 // 点击确认跳转
93 function onConfirm(id: number) { 100 function onConfirm(id: number) {
94 - window.open(`/site-details/${id}`); 101 + window.open(props.routePath + "/site-details/" + id);
95 } 102 }
96 </script> 103 </script>
@@ -49,15 +49,15 @@ @@ -49,15 +49,15 @@
49 </ul> 49 </ul>
50 </div> 50 </div>
51 <div class="flex-auto"></div> 51 <div class="flex-auto"></div>
52 - <a 52 + <nuxt-link
53 class="hidden md:block text-xs ml-2 text-[#282a2d] hover:text-[#5961f9]" 53 class="hidden md:block text-xs ml-2 text-[#282a2d] hover:text-[#5961f9]"
54 - :href="`${config.public.baseUrl}/category/${childAlias}`"  
55 - >查看更多 &gt;&gt;</a 54 + :to="routePath + `/category/${childAlias}`"
  55 + >查看更多 &gt;&gt;</nuxt-link
56 > 56 >
57 - <a 57 + <nuxt-link
58 class="md:hidden text-xs ml-2 text-[#282a2d] hover:text-[#5961f9]" 58 class="md:hidden text-xs ml-2 text-[#282a2d] hover:text-[#5961f9]"
59 - :href="`${config.public.baseUrl}/category/${childAlias}`"  
60 - >&gt;&gt;</a 59 + :to="routePath + `/category/${childAlias}`"
  60 + >&gt;&gt;</nuxt-link
61 > 61 >
62 </div> 62 </div>
63 </div> 63 </div>
@@ -90,8 +90,8 @@ @@ -90,8 +90,8 @@
90 @confirm="onConfirm(appItem.id)" 90 @confirm="onConfirm(appItem.id)"
91 > 91 >
92 <template #reference> 92 <template #reference>
93 - <a  
94 - :href="config.public.baseUrl + '/site-details/' + appItem.id" 93 + <nuxt-link
  94 + :to="routePath + '/site-details/' + appItem.id"
95 target="_blank" 95 target="_blank"
96 @click.stop="onNuxtLink" 96 @click.stop="onNuxtLink"
97 > 97 >
@@ -117,12 +117,12 @@ @@ -117,12 +117,12 @@
117 </div> 117 </div>
118 </div> 118 </div>
119 </div> 119 </div>
120 - </a> 120 + </nuxt-link>
121 </template> 121 </template>
122 </el-popconfirm> 122 </el-popconfirm>
123 - <a 123 + <nuxt-link
124 v-else 124 v-else
125 - :href="config.public.baseUrl + '/site-details/' + appItem.id" 125 + :to="routePath + '/site-details/' + appItem.id"
126 target="_blank" 126 target="_blank"
127 > 127 >
128 <div class="group p-3"> 128 <div class="group p-3">
@@ -147,22 +147,27 @@ @@ -147,22 +147,27 @@
147 </div> 147 </div>
148 </div> 148 </div>
149 </div> 149 </div>
150 - </a> 150 + </nuxt-link>
151 </div> 151 </div>
152 </div> 152 </div>
153 </template> 153 </template>
154 154
155 <script setup lang="ts"> 155 <script setup lang="ts">
156 import { Promotion } from "@element-plus/icons-vue"; 156 import { Promotion } from "@element-plus/icons-vue";
157 -  
158 -const props = defineProps<{ 157 +// 设置默认值
  158 +const props = withDefaults(
  159 + defineProps<{
159 childData: any; 160 childData: any;
160 -}>(); 161 + routePath?: string; // 必须加 ? 表示可选
  162 + }>(),
  163 + {
  164 + routePath: "", // 默认值
  165 + },
  166 +);
161 const route = useRoute(); 167 const route = useRoute();
162 const childAlias = ref(props.childData.children[0].alias); 168 const childAlias = ref(props.childData.children[0].alias);
163 const config = useRuntimeConfig(); 169 const config = useRuntimeConfig();
164 const { activeSubCategoryId, setActiveSubCategory } = useActiveSubCategory(); 170 const { activeSubCategoryId, setActiveSubCategory } = useActiveSubCategory();
165 -  
166 // 监听activeSubCategoryId的变化,当变化是把currentFilter更新为-1 171 // 监听activeSubCategoryId的变化,当变化是把currentFilter更新为-1
167 watch(activeSubCategoryId, (newValue, oldValue) => { 172 watch(activeSubCategoryId, (newValue, oldValue) => {
168 if (newValue !== oldValue) { 173 if (newValue !== oldValue) {
@@ -175,7 +180,7 @@ function onNuxtLink(event: any) { @@ -175,7 +180,7 @@ function onNuxtLink(event: any) {
175 } 180 }
176 181
177 function onConfirm(id: number) { 182 function onConfirm(id: number) {
178 - window.open(`/site-details/${id}`); 183 + window.open(props.routePath + "/site-details/" + id);
179 } 184 }
180 185
181 const currentFilter = ref(0); 186 const currentFilter = ref(0);
@@ -199,7 +204,7 @@ const scrollToSubCategory = () => { @@ -199,7 +204,7 @@ const scrollToSubCategory = () => {
199 204
200 if (parentId === String(props.childData.id)) { 205 if (parentId === String(props.childData.id)) {
201 const index = props.childData.children.findIndex( 206 const index = props.childData.children.findIndex(
202 - (child: any) => String(child.id) === childId 207 + (child: any) => String(child.id) === childId,
203 ); 208 );
204 if (index !== -1) { 209 if (index !== -1) {
205 currentFilter.value = index; 210 currentFilter.value = index;
@@ -11,15 +11,15 @@ @@ -11,15 +11,15 @@
11 {{ childData.label }} 11 {{ childData.label }}
12 </h4> 12 </h4>
13 <div class="flex-auto"></div> 13 <div class="flex-auto"></div>
14 - <a 14 + <nuxt-link
15 class="hidden md:block text-xs ml-2 text-[#282a2d] hover:text-[#5961f9] dark:text-[#989da1]" 15 class="hidden md:block text-xs ml-2 text-[#282a2d] hover:text-[#5961f9] dark:text-[#989da1]"
16 - :href="`${config.public.baseUrl}/category/${childData.alias}`"  
17 - >查看更多 &gt;&gt;</a 16 + :to="`${routePath}/category/${childData.alias}`"
  17 + >查看更多 &gt;&gt;</nuxt-link
18 > 18 >
19 - <a 19 + <nuxt-link
20 class="md:hidden text-xs ml-2 text-[#282a2d] hover:text-[#5961f9]" 20 class="md:hidden text-xs ml-2 text-[#282a2d] hover:text-[#5961f9]"
21 - :href="`${config.public.baseUrl}/category/${childData.alias}`"  
22 - >&gt;&gt;</a 21 + :to="`${routePath}/category/${childData.alias}`"
  22 + >&gt;&gt;</nuxt-link
23 > 23 >
24 </div> 24 </div>
25 </div> 25 </div>
@@ -45,8 +45,8 @@ @@ -45,8 +45,8 @@
45 @confirm="onConfirm(item.id)" 45 @confirm="onConfirm(item.id)"
46 > 46 >
47 <template #reference> 47 <template #reference>
48 - <a  
49 - :href="'/site-details/' + item.id" 48 + <nuxt-link
  49 + :to="routePath + '/site-details/' + item.id"
50 target="_blank" 50 target="_blank"
51 @click.stop="onNuxtLink" 51 @click.stop="onNuxtLink"
52 > 52 >
@@ -72,10 +72,14 @@ @@ -72,10 +72,14 @@
72 </div> 72 </div>
73 </div> 73 </div>
74 </div> 74 </div>
75 - </a> 75 + </nuxt-link>
76 </template> 76 </template>
77 </el-popconfirm> 77 </el-popconfirm>
78 - <a v-else :href="'/site-details/' + item.id" target="_blank"> 78 + <nuxt-link
  79 + v-else
  80 + :to="routePath + '/site-details/' + item.id"
  81 + target="_blank"
  82 + >
79 <div class="group p-3"> 83 <div class="group p-3">
80 <div class="flex items-start space-x-4"> 84 <div class="flex items-start space-x-4">
81 <img 85 <img
@@ -98,23 +102,31 @@ @@ -98,23 +102,31 @@
98 </div> 102 </div>
99 </div> 103 </div>
100 </div> 104 </div>
101 - </a> 105 + </nuxt-link>
102 </div> 106 </div>
103 </div> 107 </div>
104 </template> 108 </template>
105 109
106 <script setup lang="ts"> 110 <script setup lang="ts">
107 import { Promotion } from "@element-plus/icons-vue"; 111 import { Promotion } from "@element-plus/icons-vue";
108 -defineProps<{ 112 +// 设置默认值
  113 +const props = withDefaults(
  114 + defineProps<{
109 childData: any; 115 childData: any;
110 -}>(); 116 + routePath?: string; // 必须加 ? 表示可选
  117 + }>(),
  118 + {
  119 + routePath: "", // 默认值
  120 + },
  121 +);
111 const config = useRuntimeConfig(); 122 const config = useRuntimeConfig();
  123 +const route = useRoute();
112 // 阻止默认行为 124 // 阻止默认行为
113 function onNuxtLink(event: any) { 125 function onNuxtLink(event: any) {
114 event.preventDefault(); 126 event.preventDefault();
115 } 127 }
116 // 点击确认跳转 128 // 点击确认跳转
117 function onConfirm(id: number) { 129 function onConfirm(id: number) {
118 - window.open(`/site-details/${id}`); 130 + window.open(props.routePath + "/site-details/" + id);
119 } 131 }
120 </script> 132 </script>
1 <template> 1 <template>
2 - <LazyHomeContentHasChildren v-if="appData.children" :childData="appData" />  
3 - <LazyHomeContentNoChildren v-else :childData="appData" /> 2 + <LazyHomeContentHasChildren
  3 + v-if="appData.children"
  4 + :childData="appData"
  5 + :routePath="routePath"
  6 + />
  7 + <LazyHomeContentNoChildren
  8 + v-else
  9 + :childData="appData"
  10 + :routePath="routePath"
  11 + />
4 </template> 12 </template>
5 13
6 <script lang="ts" setup> 14 <script lang="ts" setup>
7 -defineProps<{ 15 +withDefaults(
  16 + defineProps<{
8 appData: any; 17 appData: any;
9 -}>(); 18 + routePath?: string; // 必须加 ? 表示可选
  19 + }>(),
  20 + {
  21 + routePath: "", // 默认值
  22 + },
  23 +);
10 </script> 24 </script>
@@ -51,8 +51,8 @@ @@ -51,8 +51,8 @@
51 @confirm="onConfirm(item.id)" 51 @confirm="onConfirm(item.id)"
52 > 52 >
53 <template #reference> 53 <template #reference>
54 - <a  
55 - :href="config.public.baseUrl + '/site-details/' + item.id" 54 + <nuxt-link
  55 + :to="routePath + '/site-details/' + item.id"
56 target="_blank" 56 target="_blank"
57 @click.stop="onNuxtLink" 57 @click.stop="onNuxtLink"
58 > 58 >
@@ -78,12 +78,12 @@ @@ -78,12 +78,12 @@
78 </div> 78 </div>
79 </div> 79 </div>
80 </div> 80 </div>
81 - </a> 81 + </nuxt-link>
82 </template> 82 </template>
83 </el-popconfirm> 83 </el-popconfirm>
84 - <a 84 + <nuxt-link
85 v-else 85 v-else
86 - :href="config.public.baseUrl + '/site-details/' + item.id" 86 + :to="routePath + '/site-details/' + item.id"
87 target="_blank" 87 target="_blank"
88 > 88 >
89 <div class="group p-3"> 89 <div class="group p-3">
@@ -108,7 +108,7 @@ @@ -108,7 +108,7 @@
108 </div> 108 </div>
109 </div> 109 </div>
110 </div> 110 </div>
111 - </a> 111 + </nuxt-link>
112 </div> 112 </div>
113 </div> 113 </div>
114 </div> 114 </div>
@@ -116,12 +116,20 @@ @@ -116,12 +116,20 @@
116 116
117 <script lang="ts" setup> 117 <script lang="ts" setup>
118 import { Promotion } from "@element-plus/icons-vue"; 118 import { Promotion } from "@element-plus/icons-vue";
119 -defineProps<{ 119 +// 设置默认值
  120 +withDefaults(
  121 + defineProps<{
120 recommendList: any[]; 122 recommendList: any[];
121 navTitle: string; 123 navTitle: string;
122 navIcon: string; 124 navIcon: string;
123 -}>(); 125 + routePath?: string; // 必须加 ? 表示可选
  126 + }>(),
  127 + {
  128 + routePath: "", // 默认值
  129 + },
  130 +);
124 const config = useRuntimeConfig(); 131 const config = useRuntimeConfig();
  132 +const route = useRoute();
125 // 阻止默认行为 133 // 阻止默认行为
126 function onNuxtLink(event: any) { 134 function onNuxtLink(event: any) {
127 event.preventDefault(); 135 event.preventDefault();
@@ -23,6 +23,13 @@ @@ -23,6 +23,13 @@
23 <el-dropdown-item> 23 <el-dropdown-item>
24 <a 24 <a
25 class="ignore" 25 class="ignore"
  26 + href="javascript:translate.changeLanguage('chinese_traditional');"
  27 + >繁体中文</a
  28 + >
  29 + </el-dropdown-item>
  30 + <el-dropdown-item>
  31 + <a
  32 + class="ignore"
26 href="javascript:translate.changeLanguage('vietnamese');" 33 href="javascript:translate.changeLanguage('vietnamese');"
27 >Tiếng Việt</a 34 >Tiếng Việt</a
28 > 35 >
@@ -36,11 +36,14 @@ @@ -36,11 +36,14 @@
36 </div> 36 </div>
37 </template> 37 </template>
38 38
39 -<script setup> 39 +<script setup lang="ts">
40 const keyWord = ref(""); 40 const keyWord = ref("");
41 const isExpanded = ref(false); 41 const isExpanded = ref(false);
42 const searchInput = ref(null); 42 const searchInput = ref(null);
43 - 43 +const route = useRoute();
  44 +const routePath = computed(() => {
  45 + return route.path.startsWith("/hktw") ? "/hktw" : "";
  46 +});
44 function toggleSearch() { 47 function toggleSearch() {
45 isExpanded.value = !isExpanded.value; 48 isExpanded.value = !isExpanded.value;
46 if (isExpanded.value && searchInput.value) { 49 if (isExpanded.value && searchInput.value) {
@@ -58,7 +61,7 @@ function handleBlur() { @@ -58,7 +61,7 @@ function handleBlur() {
58 61
59 function onSearch() { 62 function onSearch() {
60 if (keyWord.value) { 63 if (keyWord.value) {
61 - window.location.href = "/search?keyword=" + keyWord.value; 64 + window.location.href = routePath.value + "/search?keyword=" + keyWord.value;
62 } 65 }
63 } 66 }
64 </script> 67 </script>
@@ -15,7 +15,6 @@ @@ -15,7 +15,6 @@
15 15
16 <script lang="ts" setup> 16 <script lang="ts" setup>
17 const { initTheme } = useDarkMode(); 17 const { initTheme } = useDarkMode();
18 -  
19 onMounted(() => { 18 onMounted(() => {
20 initTheme(); 19 initTheme();
21 }); 20 });
@@ -40,8 +40,8 @@ export default defineNuxtConfig({ @@ -40,8 +40,8 @@ export default defineNuxtConfig({
40 compressPublicAssets: false, 40 compressPublicAssets: false,
41 devProxy: { 41 devProxy: {
42 '/dev-api': { 42 '/dev-api': {
43 - // target: 'http://192.168.2.15:35273/',  
44 - target: 'http://htai.aiboxgo.com/', 43 + target: 'http://192.168.2.15:35273/',
  44 + // target: 'https://htai.aiboxgo.com/',
45 changeOrigin: true, 45 changeOrigin: true,
46 46
47 } 47 }
@@ -49,8 +49,8 @@ export default defineNuxtConfig({ @@ -49,8 +49,8 @@ export default defineNuxtConfig({
49 // 该配置用于服务端请求转发 49 // 该配置用于服务端请求转发
50 routeRules: { 50 routeRules: {
51 '/dev-api/**': { 51 '/dev-api/**': {
52 - // proxy: 'http://192.168.2.15:35273/**'  
53 - proxy: 'http://htai.aiboxgo.com/**' 52 + proxy: 'http://192.168.2.15:35273/**'
  53 + // proxy: 'http://htai.aiboxgo.com/**'
54 }, 54 },
55 }, 55 },
56 prerender: { 56 prerender: {
@@ -40,7 +40,7 @@ const params = ref<any>({ @@ -40,7 +40,7 @@ const params = ref<any>({
40 }); 40 });
41 41
42 function findLabelByAlias< 42 function findLabelByAlias<
43 - T extends { alias?: string; label?: string; children?: T[] } 43 + T extends { alias?: string; label?: string; children?: T[] },
44 >(alias: string, data: T[], childrenKey: string = "children"): string { 44 >(alias: string, data: T[], childrenKey: string = "children"): string {
45 if (!data || data.length === 0) { 45 if (!data || data.length === 0) {
46 return ""; 46 return "";
@@ -98,10 +98,10 @@ useHead({ @@ -98,10 +98,10 @@ useHead({
98 content: `${categoryLabel}分类下的AI工具推荐,精选优质${categoryLabel}相关AI工具。`, 98 content: `${categoryLabel}分类下的AI工具推荐,精选优质${categoryLabel}相关AI工具。`,
99 }, 99 },
100 { property: "og:type", content: "website" }, 100 { property: "og:type", content: "website" },
101 - { property: "og:url", content: config.public.baseUrl + route.fullPath }, 101 + { property: "og:url", content: config.public.baseUrl + route.path },
102 { property: "og:site_name", content: webSite.value.webname }, 102 { property: "og:site_name", content: webSite.value.webname },
103 ], 103 ],
104 - link: [{ rel: "canonical", href: config.public.baseUrl + route.fullPath }], 104 + link: [{ rel: "canonical", href: config.public.baseUrl + route.path }],
105 script: [ 105 script: [
106 { 106 {
107 type: "application/ld+json", 107 type: "application/ld+json",
@@ -110,8 +110,8 @@ useHead({ @@ -110,8 +110,8 @@ useHead({
110 "@graph": [ 110 "@graph": [
111 { 111 {
112 "@type": "CollectionPage", 112 "@type": "CollectionPage",
113 - "@id": config.public.baseUrl + route.fullPath + "#collectionpage",  
114 - url: config.public.baseUrl + route.fullPath, 113 + "@id": config.public.baseUrl + route.path + "#collectionpage",
  114 + url: config.public.baseUrl + route.path,
115 name: `${categoryLabel} - ${webSite.value.webname}`, 115 name: `${categoryLabel} - ${webSite.value.webname}`,
116 description: `${categoryLabel}分类下的AI工具推荐`, 116 description: `${categoryLabel}分类下的AI工具推荐`,
117 isPartOf: { 117 isPartOf: {
@@ -124,7 +124,7 @@ useHead({ @@ -124,7 +124,7 @@ useHead({
124 }, 124 },
125 { 125 {
126 "@type": "BreadcrumbList", 126 "@type": "BreadcrumbList",
127 - "@id": config.public.baseUrl + route.fullPath + "#breadcrumb", 127 + "@id": config.public.baseUrl + route.path + "#breadcrumb",
128 itemListElement: [ 128 itemListElement: [
129 { 129 {
130 "@type": "ListItem", 130 "@type": "ListItem",
@@ -39,7 +39,7 @@ const params = ref<any>({ @@ -39,7 +39,7 @@ const params = ref<any>({
39 }); 39 });
40 40
41 function findLabelByAlias< 41 function findLabelByAlias<
42 - T extends { alias?: string; label?: string; children?: T[] } 42 + T extends { alias?: string; label?: string; children?: T[] },
43 >(alias: string, data: T[], childrenKey: string = "children"): string { 43 >(alias: string, data: T[], childrenKey: string = "children"): string {
44 if (!data || data.length === 0) { 44 if (!data || data.length === 0) {
45 return ""; 45 return "";
@@ -103,9 +103,9 @@ useHead({ @@ -103,9 +103,9 @@ useHead({
103 content: `${categoryLabel}分类下的AI工具推荐第${pageNum}页。`, 103 content: `${categoryLabel}分类下的AI工具推荐第${pageNum}页。`,
104 }, 104 },
105 { property: "og:type", content: "website" }, 105 { property: "og:type", content: "website" },
106 - { property: "og:url", content: config.public.baseUrl + route.fullPath }, 106 + { property: "og:url", content: config.public.baseUrl + route.path },
107 { property: "og:site_name", content: webSite.value.webname }, 107 { property: "og:site_name", content: webSite.value.webname },
108 ], 108 ],
109 - link: [{ rel: "canonical", href: config.public.baseUrl + route.fullPath }], 109 + link: [{ rel: "canonical", href: config.public.baseUrl + route.path }],
110 }); 110 });
111 </script> 111 </script>
  1 +<template>
  2 + <div class="md:p-10 p-4 pt-0" style="min-height: calc(100vh - 320px)">
  3 + <a
  4 + id="toogle-chinese-traditional"
  5 + href="javascript:translate.changeLanguage('chinese_traditional');"
  6 + class="ignore"
  7 + ></a>
  8 + <h1 style="font-size: 0; margin: 0">
  9 + {{ findLabelByAlias(name as string, sortList) }}
  10 + </h1>
  11 + <HomeRecommend
  12 + :recommendList="list"
  13 + :navTitle="findLabelByAlias(name as string, sortList)"
  14 + navIcon="tag"
  15 + routePath="/hktw"
  16 + />
  17 + <el-pagination
  18 + background
  19 + layout="prev, pager, next"
  20 + :hide-on-single-page="true"
  21 + v-model:page-size="params.pageSize"
  22 + v-model:current-page="params.pageNum"
  23 + :total="total"
  24 + @current-change="onPageChange"
  25 + />
  26 + </div>
  27 +</template>
  28 +
  29 +<script lang="ts" setup>
  30 +import type { appType } from "~/api/types/app";
  31 +import { getAppList } from "~/api/app";
  32 +import type { classifyType } from "~/api/types/classify";
  33 +import type { webSiteType } from "~/api/types/webSite";
  34 +const sortList = useState<classifyType[]>("sortTree");
  35 +const webSite = useState<webSiteType>("webSite");
  36 +const route = useRoute();
  37 +const router = useRouter();
  38 +const config = useRuntimeConfig();
  39 +const { name } = route.params;
  40 +const list = ref<appType[]>([]);
  41 +const total = ref<number>(0);
  42 +const params = ref<any>({
  43 + pageNum: 1,
  44 + pageSize: 30,
  45 + typeAlias: name as string,
  46 +});
  47 +
  48 +function findLabelByAlias<
  49 + T extends { alias?: string; label?: string; children?: T[] },
  50 +>(alias: string, data: T[], childrenKey: string = "children"): string {
  51 + if (!data || data.length === 0) {
  52 + return "";
  53 + }
  54 +
  55 + for (const item of data) {
  56 + if (item.alias === alias) {
  57 + return item.label || "";
  58 + }
  59 + }
  60 +
  61 + for (const item of data) {
  62 + const children = (item as any)[childrenKey] as T[];
  63 + if (children && children.length > 0) {
  64 + const foundLabel = findLabelByAlias(alias, children, childrenKey);
  65 + if (foundLabel !== "") {
  66 + return foundLabel;
  67 + }
  68 + }
  69 + }
  70 +
  71 + return "";
  72 +}
  73 +
  74 +const categoryLabel = findLabelByAlias(name as string, sortList.value);
  75 +
  76 +function onPageChange(pageNum: number) {
  77 + router.push({
  78 + path: route.path + "/page/" + pageNum,
  79 + });
  80 +}
  81 +
  82 +const res = await getAppList(params.value);
  83 +list.value = res.rows;
  84 +total.value = res.total;
  85 +onMounted(() => {
  86 + let toggleChineseTraditional = document.getElementById(
  87 + "toogle-chinese-traditional",
  88 + );
  89 + if (toggleChineseTraditional) {
  90 + // 自动点击
  91 + toggleChineseTraditional.click();
  92 + }
  93 +});
  94 +useHead({
  95 + title: `${categoryLabel} - ${webSite.value.webname}`,
  96 + meta: [
  97 + {
  98 + name: "description",
  99 + content: `${categoryLabel}分类下的AI工具推荐,精选优质${categoryLabel}相关AI工具,助您高效完成工作。`,
  100 + },
  101 + {
  102 + name: "keywords",
  103 + content: `${categoryLabel},AI工具,${categoryLabel}AI,人工智能,${webSite.value.webkeywords}`,
  104 + },
  105 + { name: "robots", content: "index, follow" },
  106 + {
  107 + property: "og:title",
  108 + content: `${categoryLabel} - ${webSite.value.webname}`,
  109 + },
  110 + {
  111 + property: "og:description",
  112 + content: `${categoryLabel}分类下的AI工具推荐,精选优质${categoryLabel}相关AI工具。`,
  113 + },
  114 + { property: "og:type", content: "website" },
  115 + { property: "og:url", content: config.public.baseUrl + route.path },
  116 + { property: "og:site_name", content: webSite.value.webname },
  117 + ],
  118 + link: [{ rel: "canonical", href: config.public.baseUrl + route.path }],
  119 + script: [
  120 + {
  121 + type: "application/ld+json",
  122 + children: JSON.stringify({
  123 + "@context": "https://schema.org",
  124 + "@graph": [
  125 + {
  126 + "@type": "CollectionPage",
  127 + "@id": config.public.baseUrl + route.path + "#collectionpage",
  128 + url: config.public.baseUrl + route.path,
  129 + name: `${categoryLabel} - ${webSite.value.webname}`,
  130 + description: `${categoryLabel}分类下的AI工具推荐`,
  131 + isPartOf: {
  132 + "@id": "https://aiboxgo.com/#website",
  133 + },
  134 + about: {
  135 + "@type": "Thing",
  136 + name: categoryLabel,
  137 + },
  138 + },
  139 + {
  140 + "@type": "BreadcrumbList",
  141 + "@id": config.public.baseUrl + route.path + "#breadcrumb",
  142 + itemListElement: [
  143 + {
  144 + "@type": "ListItem",
  145 + position: 1,
  146 + name: "首页",
  147 + item: "https://aiboxgo.com/",
  148 + },
  149 + {
  150 + "@type": "ListItem",
  151 + position: 2,
  152 + name: categoryLabel,
  153 + },
  154 + ],
  155 + },
  156 + ],
  157 + }),
  158 + },
  159 + ],
  160 +});
  161 +</script>
  1 +<template>
  2 + <div class="p-10">
  3 + <a
  4 + id="toogle-chinese-traditional"
  5 + href="javascript:translate.changeLanguage('chinese_traditional');"
  6 + class="ignore"
  7 + ></a>
  8 + <!-- 推荐工具区 -->
  9 + <HomeRecommend
  10 + :recommendList="list"
  11 + :navTitle="findLabelByAlias(name as string, sortList)"
  12 + navIcon="tag"
  13 + :navTitleWidth="120.5"
  14 + routePath="/hktw"
  15 + />
  16 + <el-pagination
  17 + background
  18 + layout="prev, pager, next"
  19 + :hide-on-single-page="true"
  20 + v-model:page-size="params.pageSize"
  21 + v-model:current-page="params.pageNum"
  22 + :total="total"
  23 + @current-change="onPageChange"
  24 + />
  25 + </div>
  26 +</template>
  27 +
  28 +<script lang="ts" setup>
  29 +import type { appType } from "~/api/types/app";
  30 +import { getAppList } from "~/api/app";
  31 +import type { classifyType } from "~/api/types/classify";
  32 +import type { webSiteType } from "~/api/types/webSite";
  33 +const sortList = useState<classifyType[]>("sortTree");
  34 +const webSite = useState<webSiteType>("webSite");
  35 +const route = useRoute();
  36 +const router = useRouter();
  37 +const config = useRuntimeConfig();
  38 +const { pageNum, name } = route.params;
  39 +const list = ref<appType[]>([]);
  40 +const total = ref<number>(0);
  41 +const params = ref<any>({
  42 + pageNum: Number(pageNum),
  43 + pageSize: 30,
  44 + typeAlias: name as string,
  45 +});
  46 +
  47 +function findLabelByAlias<
  48 + T extends { alias?: string; label?: string; children?: T[] },
  49 +>(alias: string, data: T[], childrenKey: string = "children"): string {
  50 + if (!data || data.length === 0) {
  51 + return "";
  52 + }
  53 +
  54 + for (const item of data) {
  55 + if (item.alias === alias) {
  56 + return item.label || "";
  57 + }
  58 + }
  59 +
  60 + for (const item of data) {
  61 + const children = (item as any)[childrenKey] as T[];
  62 + if (children && children.length > 0) {
  63 + const foundLabel = findLabelByAlias(alias, children, childrenKey);
  64 + if (foundLabel !== "") {
  65 + return foundLabel;
  66 + }
  67 + }
  68 + }
  69 +
  70 + return "";
  71 +}
  72 +
  73 +const categoryLabel = findLabelByAlias(name as string, sortList.value);
  74 +
  75 +function onPageChange(pageNum: number) {
  76 + if (pageNum === 1) {
  77 + router.push({
  78 + path: "/hktw/category/" + name,
  79 + });
  80 + } else if (pageNum > 1) {
  81 + router.push({
  82 + path: `/hktw/category/${name}/page/${pageNum}`,
  83 + });
  84 + }
  85 +}
  86 +
  87 +const res = await getAppList(params.value);
  88 +list.value = res.rows;
  89 +total.value = res.total;
  90 +onMounted(() => {
  91 + let toggleChineseTraditional = document.getElementById(
  92 + "toogle-chinese-traditional",
  93 + );
  94 + if (toggleChineseTraditional) {
  95 + // 自动点击
  96 + toggleChineseTraditional.click();
  97 + }
  98 +});
  99 +useHead({
  100 + title: `${categoryLabel} - 第${pageNum}页 - ${webSite.value.webname}`,
  101 + meta: [
  102 + {
  103 + name: "description",
  104 + content: `${categoryLabel}分类下的AI工具推荐第${pageNum}页,精选优质${categoryLabel}相关AI工具。`,
  105 + },
  106 + {
  107 + name: "keywords",
  108 + content: `${categoryLabel},AI工具,${categoryLabel}AI,人工智能,第${pageNum}页`,
  109 + },
  110 + { name: "robots", content: "index, follow" },
  111 + {
  112 + property: "og:title",
  113 + content: `${categoryLabel} - 第${pageNum}页 - ${webSite.value.webname}`,
  114 + },
  115 + {
  116 + property: "og:description",
  117 + content: `${categoryLabel}分类下的AI工具推荐第${pageNum}页。`,
  118 + },
  119 + { property: "og:type", content: "website" },
  120 + { property: "og:url", content: config.public.baseUrl + route.path },
  121 + { property: "og:site_name", content: webSite.value.webname },
  122 + ],
  123 + link: [{ rel: "canonical", href: config.public.baseUrl + route.path }],
  124 +});
  125 +</script>
  1 +<script lang="ts" setup>
  2 +import { getAppList, getAllApp } from "~/api/app";
  3 +import type { webSiteType } from "~/api/types/webSite";
  4 +import { getAdList } from "~/api/ad";
  5 +
  6 +const webSite = useState<webSiteType>("webSite");
  7 +const route = useRoute();
  8 +console.log(route);
  9 +const { data: adList } = await useAsyncData(
  10 + "adList",
  11 + async () => {
  12 + const res = await getAdList();
  13 + return res[0] || null;
  14 + },
  15 + {
  16 + server: true,
  17 + lazy: false,
  18 + },
  19 +);
  20 +
  21 +const { data: recommendList } = await useAsyncData(
  22 + "recommendList",
  23 + async () => {
  24 + const res = await getAppList({
  25 + pageSize: 10,
  26 + pageNum: 1,
  27 + isRecommend: "1",
  28 + });
  29 + return res.rows || [];
  30 + },
  31 + {
  32 + server: true,
  33 + lazy: false,
  34 + },
  35 +);
  36 +
  37 +const { data: appList } = await useAsyncData(
  38 + "appList",
  39 + async () => {
  40 + const res = await getAllApp();
  41 + return res.data || [];
  42 + },
  43 + {
  44 + server: true,
  45 + lazy: false,
  46 + },
  47 +);
  48 +onMounted(() => {
  49 + let toggleChineseTraditional = document.getElementById(
  50 + "toogle-chinese-traditional",
  51 + );
  52 + if (toggleChineseTraditional) {
  53 + // 自动点击
  54 + toggleChineseTraditional.click();
  55 + }
  56 +});
  57 +
  58 +useHead({
  59 + title: webSite.value.webname,
  60 + meta: [
  61 + { name: "description", content: webSite.value.webdescription },
  62 + { name: "keywords", content: webSite.value.webkeywords },
  63 + { name: "format-detection", content: "telephone=no" },
  64 + { name: "referrer", content: "origin-when-cross-origin" },
  65 + {
  66 + name: "robots",
  67 + content:
  68 + "follow, index, max-snippet:-1, max-video-preview:-1, max-image-preview:large",
  69 + },
  70 + { property: "og:locale", content: "zh_CN" },
  71 + {
  72 + property: "og:title",
  73 + content: "AI工具盒 - 優質 AI 工具集合、AI 資源網站、AI 導航平台",
  74 + },
  75 + {
  76 + property: "og:description",
  77 + content:
  78 + "專業 AI 工具導全网優質全网優質 AI 工具,包含 AI 写作、AI 绘画、AI 视频、AI 对话、AI 代码、AI 设计、AI 办公等各类 AI 資源,快速找到好用的 AI 工具。",
  79 + },
  80 + { property: "og:url", content: "https://aiboxgo.com/" },
  81 + { property: "og:site_name", content: "AI工具盒" },
  82 + { property: "og:type", content: "website" },
  83 + { property: "twitter:card", content: "summary_large_image" },
  84 + {
  85 + property: "twitter:title",
  86 + content: "AI工具盒 - 優質 AI 工具集合、AI 資源網站、AI 導航平台",
  87 + },
  88 + {
  89 + property: "twitter:description",
  90 + content:
  91 + "專業 AI 工具導全网優質全网優質 AI 工具,包含 AI 写作、AI 绘画、AI 视频、AI 对话、AI 代码、AI 设计、AI 办公等各类 AI 資源,快速找到好用的 AI 工具。",
  92 + },
  93 + ],
  94 + script: [
  95 + {
  96 + type: "application/ld+json",
  97 + children: JSON.stringify({
  98 + "@context": "https://schema.org",
  99 + "@graph": [
  100 + {
  101 + "@type": "WebSite",
  102 + "@id": "https://aiboxgo.com/#website",
  103 + url: "https://aiboxgo.com/",
  104 + name: webSite.value.webname,
  105 + description: webSite.value.webdescription,
  106 + inLanguage: "zh-CN",
  107 + potentialAction: {
  108 + "@type": "SearchAction",
  109 + target: {
  110 + "@type": "EntryPoint",
  111 + urlTemplate:
  112 + "https://aiboxgo.com/search?keyword={search_term_string}",
  113 + },
  114 + "query-input": "required name=search_term_string",
  115 + },
  116 + },
  117 + {
  118 + "@type": "Organization",
  119 + "@id": "https://aiboxgo.com/#organization",
  120 + name: webSite.value.webname,
  121 + url: "https://aiboxgo.com/",
  122 + logo: {
  123 + "@type": "ImageObject",
  124 + url: "https://aiboxgo.com/favicon.ico",
  125 + },
  126 + sameAs: [],
  127 + },
  128 + {
  129 + "@type": "CollectionPage",
  130 + "@id": "https://aiboxgo.com/",
  131 + url: "https://aiboxgo.com/",
  132 + name: webSite.value.webname,
  133 + description: webSite.value.webdescription,
  134 + isPartOf: {
  135 + "@id": "https://aiboxgo.com/#website",
  136 + },
  137 + about: {
  138 + "@id": "https://aiboxgo.com/#organization",
  139 + },
  140 + },
  141 + ],
  142 + }),
  143 + },
  144 + ],
  145 +});
  146 +</script>
  147 +
  148 +<template>
  149 + <div
  150 + class="flex flex-col min-h-screen bg-white dark:bg-[#1a1b1d] transition-colors duration-300"
  151 + >
  152 + <a
  153 + id="toogle-chinese-traditional"
  154 + href="javascript:translate.changeLanguage('chinese_traditional');"
  155 + class="ignore"
  156 + ></a>
  157 + <main
  158 + class="flex-grow md:p-6 p-2 bg-white dark:bg-[#1a1b1d] transition-colors duration-300"
  159 + >
  160 + <ADSwiperCarousel v-if="adList" :adSwiperList="adList" />
  161 + <HomeBanner />
  162 + <section class="md:mb-12 mb-6">
  163 + <HomeRecommend
  164 + v-if="recommendList && recommendList.length > 0"
  165 + :recommendList="recommendList"
  166 + navTitle="推薦工具"
  167 + navIcon="star"
  168 + routePath="/hktw"
  169 + />
  170 +
  171 + <div
  172 + v-for="appItem in appList"
  173 + :key="appItem?.id"
  174 + class="md:mb-12 mb-6"
  175 + >
  176 + <HomeContent :appData="appItem" routePath="/hktw" />
  177 + </div>
  178 + </section>
  179 + </main>
  180 + </div>
  181 +</template>
  1 +<template>
  2 + <div class="p-10" style="min-height: calc(100vh - 320px)">
  3 + <a
  4 + id="toogle-chinese-traditional"
  5 + href="javascript:translate.changeLanguage('chinese_traditional');"
  6 + class="ignore"
  7 + ></a>
  8 + <HomeRecommend
  9 + :recommendList="list"
  10 + :navTitle="keyword"
  11 + navIcon="tag"
  12 + routePath="/hktw"
  13 + />
  14 +
  15 + <el-empty v-show="!list.length" description="未搜索到相关内容" />
  16 +
  17 + <el-pagination
  18 + background
  19 + layout="prev, pager, next"
  20 + :hide-on-single-page="true"
  21 + v-model:page-size="params.pageSize"
  22 + v-model:current-page="params.pageNum"
  23 + :total="total"
  24 + @current-change="onPageChange"
  25 + />
  26 + </div>
  27 +</template>
  28 +
  29 +<script lang="ts" setup>
  30 +import type { appType } from "~/api/types/app";
  31 +import { getAppList } from "~/api/app";
  32 +import type { webSiteType } from "~/api/types/webSite";
  33 +
  34 +const route = useRoute();
  35 +const router = useRouter();
  36 +const config = useRuntimeConfig();
  37 +const webSite = useState<webSiteType>("webSite");
  38 +
  39 +const { keyword } = route.query as { keyword: string };
  40 +const list = ref<appType[]>([]);
  41 +const total = ref<number>(0);
  42 +const params = ref<any>({
  43 + pageNum: 1,
  44 + pageSize: 10,
  45 + title: keyword,
  46 +});
  47 +
  48 +function onPageChange(pageNum: number) {
  49 + router.push({
  50 + path: route.path + "/page/" + pageNum,
  51 + query: {
  52 + keyword: keyword,
  53 + },
  54 + });
  55 +}
  56 +
  57 +const res = await getAppList(params.value);
  58 +list.value = res.rows;
  59 +total.value = res.total;
  60 +onMounted(() => {
  61 + let toggleChineseTraditional = document.getElementById(
  62 + "toogle-chinese-traditional",
  63 + );
  64 + if (toggleChineseTraditional) {
  65 + // 自动点击
  66 + toggleChineseTraditional.click();
  67 + }
  68 +});
  69 +useHead({
  70 + title: `"${keyword}" 搜索结果 - ${webSite.value.webname}`,
  71 + meta: [
  72 + {
  73 + name: "description",
  74 + content: `搜索"${keyword}"相关的AI工具,找到${total.value}个结果。${webSite.value.webdescription}`,
  75 + },
  76 + {
  77 + name: "keywords",
  78 + content: `${keyword},AI工具搜索,${webSite.value.webkeywords}`,
  79 + },
  80 + { name: "robots", content: "noindex, follow" },
  81 + {
  82 + property: "og:title",
  83 + content: `"${keyword}" 搜索结果 - ${webSite.value.webname}`,
  84 + },
  85 + {
  86 + property: "og:description",
  87 + content: `搜索"${keyword}"相关的AI工具结果页面。`,
  88 + },
  89 + { property: "og:type", content: "website" },
  90 + { property: "og:url", content: config.public.baseUrl + route.path },
  91 + { property: "og:site_name", content: webSite.value.webname },
  92 + ],
  93 + link: [{ rel: "canonical", href: config.public.baseUrl + route.path }],
  94 +});
  95 +</script>
  1 +<template>
  2 + <div class="p-10">
  3 + <a
  4 + id="toogle-chinese-traditional"
  5 + href="javascript:translate.changeLanguage('chinese_traditional');"
  6 + class="ignore"
  7 + ></a>
  8 + <!-- 推荐工具区 -->
  9 + <HomeRecommend
  10 + :recommendList="list"
  11 + :navTitle="keyword"
  12 + navIcon="tag"
  13 + :navTitleWidth="120.5"
  14 + routePath="/hktw"
  15 + />
  16 + <el-pagination
  17 + background
  18 + layout="prev, pager, next"
  19 + :hide-on-single-page="true"
  20 + v-model:page-size="params.pageSize"
  21 + v-model:current-page="params.pageNum"
  22 + :total="total"
  23 + @current-change="onPageChange"
  24 + />
  25 + </div>
  26 +</template>
  27 +
  28 +<script lang="ts" setup>
  29 +import type { appType } from "~/api/types/app";
  30 +import { getAppList } from "~/api/app";
  31 +import type { webSiteType } from "~/api/types/webSite";
  32 +const route = useRoute();
  33 +const router = useRouter();
  34 +const config = useRuntimeConfig();
  35 +const webSite = useState<webSiteType>("webSite");
  36 +const { pageNum } = route.params;
  37 +const { keyword } = route.query as { keyword: string };
  38 +const list = ref<appType[]>([]);
  39 +const total = ref<number>(0);
  40 +const params = ref<any>({
  41 + pageNum: Number(pageNum),
  42 + pageSize: 10,
  43 + title: keyword,
  44 +});
  45 +
  46 +function onPageChange(pageNum: number) {
  47 + if (pageNum === 1) {
  48 + router.push({
  49 + path: "/hktw/search",
  50 + query: {
  51 + keyword: keyword,
  52 + },
  53 + });
  54 + } else if (pageNum > 1) {
  55 + router.push({
  56 + path: `/hktw/search/page/${pageNum}`,
  57 + query: {
  58 + keyword: keyword,
  59 + },
  60 + });
  61 + }
  62 +}
  63 +const res = await getAppList(params.value);
  64 +list.value = res.rows;
  65 +total.value = res.total;
  66 +onMounted(() => {
  67 + let toggleChineseTraditional = document.getElementById(
  68 + "toogle-chinese-traditional",
  69 + );
  70 + if (toggleChineseTraditional) {
  71 + // 自动点击
  72 + toggleChineseTraditional.click();
  73 + }
  74 +});
  75 +useHead({
  76 + title: `"${keyword}" 搜索结果 - 第${pageNum}页 - ${webSite.value.webname}`,
  77 + meta: [
  78 + {
  79 + name: "description",
  80 + content: `搜索"${keyword}"相关的AI工具第${pageNum}页结果。`,
  81 + },
  82 + { name: "keywords", content: `${keyword},AI工具搜索,第${pageNum}页` },
  83 + { name: "robots", content: "noindex, follow" },
  84 + {
  85 + property: "og:title",
  86 + content: `"${keyword}" 搜索结果 - 第${pageNum}页 - ${webSite.value.webname}`,
  87 + },
  88 + {
  89 + property: "og:description",
  90 + content: `搜索"${keyword}"相关的AI工具第${pageNum}页结果。`,
  91 + },
  92 + { property: "og:type", content: "website" },
  93 + { property: "og:url", content: config.public.baseUrl + route.path },
  94 + { property: "og:site_name", content: webSite.value.webname },
  95 + ],
  96 + link: [{ rel: "canonical", href: config.public.baseUrl + route.path }],
  97 +});
  98 +</script>
  1 +<script lang="ts" setup>
  2 +import { getAppDetail, getAppList } from "~/api/app";
  3 +import type { appDetail, Types, appType } from "~/api/types/app";
  4 +import type { webSiteType } from "~/api/types/webSite";
  5 +const route = useRoute();
  6 +const config = useRuntimeConfig();
  7 +const DetailData = ref<appDetail>({
  8 + id: 0,
  9 + title: "",
  10 + content: "",
  11 + image: "",
  12 + description: "",
  13 + link: "",
  14 + types: [],
  15 +});
  16 +const webSite = useState<webSiteType>("webSite");
  17 +const relatedApps = ref<appType[]>([]);
  18 +
  19 +function mergeDuplicates(data: Types[]) {
  20 + const map = new Map();
  21 + data.forEach((item) => {
  22 + if (!map.has(item.id)) {
  23 + map.set(item.id, {
  24 + id: item.id,
  25 + label: item.label,
  26 + alias: item.alias,
  27 + children: [...(item.children || [])],
  28 + });
  29 + } else {
  30 + const existing = map.get(item.id);
  31 + const existingChildIds = new Set(
  32 + existing.children.map((child: any) => child.id),
  33 + );
  34 + item.children.forEach((child) => {
  35 + if (!existingChildIds.has(child.id)) {
  36 + existing.children.push(child);
  37 + }
  38 + });
  39 + }
  40 + });
  41 + return Array.from(map.values());
  42 +}
  43 +
  44 +const detailRes = await getAppDetail(Number(route.params.id));
  45 +DetailData.value = detailRes.data;
  46 +DetailData.value.types = mergeDuplicates(detailRes.data.types);
  47 +if (DetailData.value.types?.length > 0) {
  48 + const firstType = DetailData.value.types[0];
  49 + const typeAlias = firstType.alias || firstType.children?.[0]?.alias;
  50 + if (typeAlias) {
  51 + const relatedRes = await getAppList({
  52 + pageNum: 1,
  53 + pageSize: 8,
  54 + typeAlias: typeAlias,
  55 + });
  56 + relatedApps.value = relatedRes.rows
  57 + .filter((app: appType) => app.id !== DetailData.value.id)
  58 + .slice(0, 6);
  59 + }
  60 +}
  61 +onMounted(() => {
  62 + let toggleChineseTraditional = document.getElementById(
  63 + "toogle-chinese-traditional",
  64 + );
  65 + if (toggleChineseTraditional) {
  66 + // 自动点击
  67 + toggleChineseTraditional.click();
  68 + }
  69 +});
  70 +useHead({
  71 + title: DetailData.value.description
  72 + ? `${DetailData.value.title} - ${DetailData.value.description}`
  73 + : DetailData.value.title,
  74 + meta: [
  75 + { name: "description", content: DetailData.value.description },
  76 + {
  77 + name: "keywords",
  78 + content: `${DetailData.value.title},AI工具,${
  79 + DetailData.value.types?.map((t: any) => t.label).join(",") || ""
  80 + }`,
  81 + },
  82 + { name: "robots", content: "index, follow" },
  83 + {
  84 + property: "og:title",
  85 + content: `${DetailData.value.title}${
  86 + DetailData.value.popupContent != null
  87 + ? ` - ${DetailData.value.popupContent}`
  88 + : `- ${DetailData.value.description} | ${webSite.value.webname.slice(
  89 + 0,
  90 + 7,
  91 + )}`
  92 + }`,
  93 + },
  94 + { property: "og:description", content: DetailData.value.description },
  95 + { property: "og:type", content: "article" },
  96 + {
  97 + property: "og:image",
  98 + content: config.public.baseUrl + DetailData.value.image,
  99 + },
  100 + { property: "og:url", content: config.public.baseUrl + route.path },
  101 + { property: "og:site_name", content: webSite.value.webname.slice(0, 7) },
  102 + { property: "og:updated_time", content: DetailData.value.updateTime },
  103 + {
  104 + property: "article:published_time",
  105 + content: DetailData.value.updateTime,
  106 + },
  107 + { property: "twitter:card", content: "summary_large_image" },
  108 + { property: "twitter:title", content: DetailData.value.title },
  109 + { property: "twitter:description", content: DetailData.value.description },
  110 + {
  111 + property: "twitter:image",
  112 + content: config.public.baseUrl + DetailData.value.image,
  113 + },
  114 + ],
  115 + link: [
  116 + {
  117 + rel: "canonical",
  118 + href: config.public.baseUrl + route.path,
  119 + },
  120 + ],
  121 + script: [
  122 + {
  123 + type: "application/ld+json",
  124 + children: JSON.stringify({
  125 + "@context": "https://schema.org",
  126 + "@graph": [
  127 + {
  128 + "@type": "SoftwareApplication",
  129 + "@id": config.public.baseUrl + route.path + "#software",
  130 + name: DetailData.value.title,
  131 + description: DetailData.value.description,
  132 + url: DetailData.value.link,
  133 + image: config.public.baseUrl + DetailData.value.image,
  134 + applicationCategory: "UtilitiesApplication",
  135 + operatingSystem: "Web Browser",
  136 + offers: {
  137 + "@type": "Offer",
  138 + price: "0",
  139 + priceCurrency: "CNY",
  140 + },
  141 + },
  142 + {
  143 + "@type": "Article",
  144 + "@id": config.public.baseUrl + route.path + "#article",
  145 + headline: DetailData.value.title,
  146 + description: DetailData.value.description,
  147 + image: config.public.baseUrl + DetailData.value.image,
  148 + datePublished: DetailData.value.updateTime,
  149 + dateModified: DetailData.value.updateTime,
  150 + author: {
  151 + "@id": "https://aiboxgo.com/#organization",
  152 + },
  153 + publisher: {
  154 + "@id": "https://aiboxgo.com/#organization",
  155 + },
  156 + mainEntityOfPage: {
  157 + "@type": "WebPage",
  158 + "@id": config.public.baseUrl + route.path,
  159 + },
  160 + },
  161 + {
  162 + "@type": "BreadcrumbList",
  163 + "@id": config.public.baseUrl + route.path + "#breadcrumb",
  164 + itemListElement: [
  165 + {
  166 + "@type": "ListItem",
  167 + position: 1,
  168 + name: "首页",
  169 + item: "https://aiboxgo.com/",
  170 + },
  171 + ...(DetailData.value.types?.[0]
  172 + ? [
  173 + {
  174 + "@type": "ListItem",
  175 + position: 2,
  176 + name: DetailData.value.types[0].label,
  177 + item: `https://aiboxgo.com/category/${DetailData.value.types[0].alias}`,
  178 + },
  179 + ]
  180 + : []),
  181 + {
  182 + "@type": "ListItem",
  183 + position: DetailData.value.types?.length ? 3 : 2,
  184 + name: DetailData.value.title,
  185 + },
  186 + ],
  187 + },
  188 + ],
  189 + }),
  190 + },
  191 + ],
  192 +});
  193 +</script>
  194 +
  195 +<template>
  196 + <div
  197 + class="flex flex-col min-h-screen bg-white dark:bg-[#1a1b1d] transition-colors duration-300"
  198 + >
  199 + <a
  200 + id="toogle-chinese-traditional"
  201 + href="javascript:translate.changeLanguage('chinese_traditional');"
  202 + class="ignore"
  203 + ></a>
  204 + <main class="flex-grow bg-white dark:bg-[#1a1b1d]">
  205 + <div class="relative overflow-hidden">
  206 + <CommonParticleBackground
  207 + :particleCount="60"
  208 + particleColor="#5961f9"
  209 + lineColor="#7c3aed"
  210 + :particleSize="2"
  211 + :lineDistance="100"
  212 + :speed="0.3"
  213 + />
  214 + <div
  215 + 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"
  216 + ></div>
  217 +
  218 + <div class="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
  219 + <div class="grid grid-cols-1 lg:grid-cols-12 gap-6 items-start">
  220 + <div class="lg:col-span-2 flex justify-center lg:justify-end">
  221 + <div class="relative group">
  222 + <div
  223 + class="absolute -inset-1 bg-gradient-to-r from-[#5961f9] to-[#a855f7] rounded-2xl blur opacity-30 group-hover:opacity-50 transition duration-300"
  224 + ></div>
  225 + <img
  226 + :src="config.public.apiUrl + DetailData.image"
  227 + :alt="DetailData.title"
  228 + 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"
  229 + loading="lazy"
  230 + />
  231 + </div>
  232 + </div>
  233 +
  234 + <div class="lg:col-span-7 space-y-4">
  235 + <div class="flex items-center gap-3 flex-wrap">
  236 + <h1
  237 + class="text-2xl md:text-3xl font-bold text-[#282a2d] dark:text-[#c6c9cf]"
  238 + >
  239 + {{ DetailData.title }}
  240 + </h1>
  241 + <span
  242 + v-if="DetailData.popupContent"
  243 + class="px-3 py-1 bg-gradient-to-r from-[#5961f9] to-[#a855f7] text-white text-xs rounded-full"
  244 + >
  245 + {{ DetailData.popupContent }}
  246 + </span>
  247 + </div>
  248 +
  249 + <div class="flex flex-wrap gap-2">
  250 + <template v-for="tag in DetailData.types" :key="tag.id">
  251 + <template v-if="tag.children && tag.children.length > 0">
  252 + <NuxtLink
  253 + v-for="child in tag.children"
  254 + :key="child.id"
  255 + :to="'/category/' + child.alias"
  256 + 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"
  257 + >
  258 + {{ child.label }}
  259 + </NuxtLink>
  260 + </template>
  261 + <template v-else>
  262 + <NuxtLink
  263 + :to="'/category/' + tag.alias"
  264 + 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"
  265 + >
  266 + {{ tag.label }}
  267 + </NuxtLink>
  268 + </template>
  269 + </template>
  270 + </div>
  271 +
  272 + <p
  273 + class="text-gray-600 dark:text-gray-300 text-base leading-relaxed line-clamp-3"
  274 + >
  275 + {{ DetailData.description }}
  276 + </p>
  277 +
  278 + <div class="flex flex-wrap gap-3 pt-2">
  279 + <a
  280 + :href="DetailData.link"
  281 + target="_blank"
  282 + rel="noopener noreferrer"
  283 + 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"
  284 + >
  285 + <i class="iconfont icon-guide text-lg"></i>
  286 + <span>立即访问</span>
  287 + </a>
  288 + </div>
  289 + </div>
  290 +
  291 + <div class="lg:col-span-3">
  292 + <div
  293 + 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"
  294 + >
  295 + <div
  296 + class="text-center text-gray-400 dark:text-gray-500 text-sm"
  297 + >
  298 + <a
  299 + no_cache=""
  300 + href="https://www.coze.cn/?utm_medium=daohang&amp;utm_source=aikit&amp;utm_content=&amp;utm_id=&amp;utm_campaign=&amp;utm_term=hw_coze_aikit&amp;utm_source_platform="
  301 + rel="external nofollow"
  302 + target="_blank"
  303 + ><img
  304 + src="https://ai-kit.cn/wp-content/uploads/2026/01/kouzi_gd.jpg"
  305 + alt="扣子"
  306 + /></a>
  307 + </div>
  308 + </div>
  309 + </div>
  310 + </div>
  311 + </div>
  312 + </div>
  313 +
  314 + <div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
  315 + <article class="prose prose-lg dark:prose-invert max-w-none p-6 md:p-8">
  316 + <div v-html="DetailData.content" class="detail-content"></div>
  317 + </article>
  318 + </div>
  319 +
  320 + <div
  321 + v-if="relatedApps.length > 0"
  322 + class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"
  323 + >
  324 + <div class="rounded-2xl p-6">
  325 + <h2
  326 + class="text-xl font-bold text-[#555] dark:text-[#888] mb-6 flex items-center gap-2"
  327 + >
  328 + <i class="iconfont icon-tag" style="font-size: 1.2rem"></i>
  329 + 相关推荐
  330 + </h2>
  331 + <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-3 gap-4">
  332 + <CommonCard :cardList="relatedApps" routePath="/hktw" />
  333 + </div>
  334 + </div>
  335 + </div>
  336 + </main>
  337 + </div>
  338 +</template>
  339 +
  340 +<style scoped lang="less">
  341 +.line-clamp-3 {
  342 + display: -webkit-box;
  343 + -webkit-line-clamp: 3;
  344 + -webkit-box-orient: vertical;
  345 + overflow: hidden;
  346 +}
  347 +
  348 +.detail-content {
  349 + @apply text-[#282a2d] dark:text-[#c6c9cf];
  350 +}
  351 +
  352 +.detail-content :deep(h1),
  353 +.detail-content :deep(h2),
  354 +.detail-content :deep(h3),
  355 +.detail-content :deep(h4) {
  356 + @apply text-[#282a2d] my-5 py-1 pl-5 border-l-4 border-[#5961f9] dark:text-[#c6c9cf];
  357 +}
  358 +
  359 +.detail-content :deep(h1) {
  360 + @apply text-2xl;
  361 +}
  362 +
  363 +.detail-content :deep(h2) {
  364 + @apply text-xl;
  365 +}
  366 +
  367 +.detail-content :deep(h3) {
  368 + @apply text-2xl;
  369 +}
  370 +
  371 +.detail-content :deep(p) {
  372 + @apply mb-4 leading-relaxed;
  373 +}
  374 +
  375 +.detail-content :deep(img) {
  376 + @apply rounded-lg max-w-full h-auto my-4;
  377 +}
  378 +
  379 +.detail-content :deep(a) {
  380 + @apply text-[#5961f9] hover:underline;
  381 +}
  382 +
  383 +.detail-content :deep(ul),
  384 +.detail-content :deep(ol) {
  385 + @apply pl-6 mb-4 text-sm list-disc;
  386 +}
  387 +
  388 +.detail-content :deep(li) {
  389 + @apply mb-2;
  390 +}
  391 +
  392 +.detail-content :deep(blockquote) {
  393 + @apply border-l-4 border-[#5961f9] pl-4 italic my-4;
  394 +}
  395 +
  396 +.detail-content :deep(code) {
  397 + @apply bg-gray-100 dark:bg-gray-700 px-1 py-0.5 rounded text-sm;
  398 +}
  399 +
  400 +.detail-content :deep(pre) {
  401 + @apply bg-gray-100 dark:bg-gray-700 p-4 rounded-lg overflow-x-auto my-4;
  402 +}
  403 +
  404 +.detail-content :deep(table) {
  405 + @apply w-full border-collapse my-4;
  406 +}
  407 +
  408 +.detail-content :deep(th),
  409 +.detail-content :deep(td) {
  410 + @apply border border-gray-200 dark:border-gray-600 px-4 py-2;
  411 +}
  412 +
  413 +.detail-content :deep(th) {
  414 + @apply bg-gray-100 dark:bg-gray-700 font-semibold;
  415 +}
  416 +</style>
@@ -5,7 +5,6 @@ import { getAdList } from "~/api/ad"; @@ -5,7 +5,6 @@ import { getAdList } from "~/api/ad";
5 5
6 const webSite = useState<webSiteType>("webSite"); 6 const webSite = useState<webSiteType>("webSite");
7 const route = useRoute(); 7 const route = useRoute();
8 -console.log(route);  
9 const { data: adList } = await useAsyncData( 8 const { data: adList } = await useAsyncData(
10 "adList", 9 "adList",
11 async () => { 10 async () => {
@@ -15,7 +14,7 @@ const { data: adList } = await useAsyncData( @@ -15,7 +14,7 @@ const { data: adList } = await useAsyncData(
15 { 14 {
16 server: true, 15 server: true,
17 lazy: false, 16 lazy: false,
18 - } 17 + },
19 ); 18 );
20 19
21 const { data: recommendList } = await useAsyncData( 20 const { data: recommendList } = await useAsyncData(
@@ -31,7 +30,7 @@ const { data: recommendList } = await useAsyncData( @@ -31,7 +30,7 @@ const { data: recommendList } = await useAsyncData(
31 { 30 {
32 server: true, 31 server: true,
33 lazy: false, 32 lazy: false,
34 - } 33 + },
35 ); 34 );
36 35
37 const { data: appList } = await useAsyncData( 36 const { data: appList } = await useAsyncData(
@@ -43,7 +42,7 @@ const { data: appList } = await useAsyncData( @@ -43,7 +42,7 @@ const { data: appList } = await useAsyncData(
43 { 42 {
44 server: true, 43 server: true,
45 lazy: false, 44 lazy: false,
46 - } 45 + },
47 ); 46 );
48 47
49 useHead({ 48 useHead({
@@ -51,17 +51,27 @@ total.value = res.total; @@ -51,17 +51,27 @@ total.value = res.total;
51 useHead({ 51 useHead({
52 title: `"${keyword}" 搜索结果 - ${webSite.value.webname}`, 52 title: `"${keyword}" 搜索结果 - ${webSite.value.webname}`,
53 meta: [ 53 meta: [
54 - { name: "description", content: `搜索"${keyword}"相关的AI工具,找到${total.value}个结果。${webSite.value.webdescription}` },  
55 - { name: "keywords", content: `${keyword},AI工具搜索,${webSite.value.webkeywords}` }, 54 + {
  55 + name: "description",
  56 + content: `搜索"${keyword}"相关的AI工具,找到${total.value}个结果。${webSite.value.webdescription}`,
  57 + },
  58 + {
  59 + name: "keywords",
  60 + content: `${keyword},AI工具搜索,${webSite.value.webkeywords}`,
  61 + },
56 { name: "robots", content: "noindex, follow" }, 62 { name: "robots", content: "noindex, follow" },
57 - { property: "og:title", content: `"${keyword}" 搜索结果 - ${webSite.value.webname}` },  
58 - { property: "og:description", content: `搜索"${keyword}"相关的AI工具结果页面。` }, 63 + {
  64 + property: "og:title",
  65 + content: `"${keyword}" 搜索结果 - ${webSite.value.webname}`,
  66 + },
  67 + {
  68 + property: "og:description",
  69 + content: `搜索"${keyword}"相关的AI工具结果页面。`,
  70 + },
59 { property: "og:type", content: "website" }, 71 { property: "og:type", content: "website" },
60 - { property: "og:url", content: config.public.baseUrl + route.fullPath }, 72 + { property: "og:url", content: config.public.baseUrl + route.path },
61 { property: "og:site_name", content: webSite.value.webname }, 73 { property: "og:site_name", content: webSite.value.webname },
62 ], 74 ],
63 - link: [  
64 - { rel: "canonical", href: config.public.baseUrl + route.fullPath }  
65 - ] 75 + link: [{ rel: "canonical", href: config.public.baseUrl + route.path }],
66 }); 76 });
67 </script> 77 </script>
@@ -61,17 +61,24 @@ total.value = res.total; @@ -61,17 +61,24 @@ total.value = res.total;
61 useHead({ 61 useHead({
62 title: `"${keyword}" 搜索结果 - 第${pageNum}页 - ${webSite.value.webname}`, 62 title: `"${keyword}" 搜索结果 - 第${pageNum}页 - ${webSite.value.webname}`,
63 meta: [ 63 meta: [
64 - { name: "description", content: `搜索"${keyword}"相关的AI工具第${pageNum}页结果。` }, 64 + {
  65 + name: "description",
  66 + content: `搜索"${keyword}"相关的AI工具第${pageNum}页结果。`,
  67 + },
65 { name: "keywords", content: `${keyword},AI工具搜索,第${pageNum}页` }, 68 { name: "keywords", content: `${keyword},AI工具搜索,第${pageNum}页` },
66 { name: "robots", content: "noindex, follow" }, 69 { name: "robots", content: "noindex, follow" },
67 - { property: "og:title", content: `"${keyword}" 搜索结果 - 第${pageNum}页 - ${webSite.value.webname}` },  
68 - { property: "og:description", content: `搜索"${keyword}"相关的AI工具第${pageNum}页结果。` }, 70 + {
  71 + property: "og:title",
  72 + content: `"${keyword}" 搜索结果 - 第${pageNum}页 - ${webSite.value.webname}`,
  73 + },
  74 + {
  75 + property: "og:description",
  76 + content: `搜索"${keyword}"相关的AI工具第${pageNum}页结果。`,
  77 + },
69 { property: "og:type", content: "website" }, 78 { property: "og:type", content: "website" },
70 - { property: "og:url", content: config.public.baseUrl + route.fullPath }, 79 + { property: "og:url", content: config.public.baseUrl + route.path },
71 { property: "og:site_name", content: webSite.value.webname }, 80 { property: "og:site_name", content: webSite.value.webname },
72 ], 81 ],
73 - link: [  
74 - { rel: "canonical", href: config.public.baseUrl + route.fullPath }  
75 - ] 82 + link: [{ rel: "canonical", href: config.public.baseUrl + route.path }],
76 }); 83 });
77 </script> 84 </script>
@@ -44,7 +44,6 @@ function mergeDuplicates(data: Types[]) { @@ -44,7 +44,6 @@ function mergeDuplicates(data: Types[]) {
44 const detailRes = await getAppDetail(Number(route.params.id)); 44 const detailRes = await getAppDetail(Number(route.params.id));
45 DetailData.value = detailRes.data; 45 DetailData.value = detailRes.data;
46 DetailData.value.types = mergeDuplicates(detailRes.data.types); 46 DetailData.value.types = mergeDuplicates(detailRes.data.types);
47 -console.log(DetailData.value);  
48 if (DetailData.value.types?.length > 0) { 47 if (DetailData.value.types?.length > 0) {
49 const firstType = DetailData.value.types[0]; 48 const firstType = DetailData.value.types[0];
50 const typeAlias = firstType.alias || firstType.children?.[0]?.alias; 49 const typeAlias = firstType.alias || firstType.children?.[0]?.alias;
@@ -90,7 +89,7 @@ useHead({ @@ -90,7 +89,7 @@ useHead({
90 property: "og:image", 89 property: "og:image",
91 content: config.public.baseUrl + DetailData.value.image, 90 content: config.public.baseUrl + DetailData.value.image,
92 }, 91 },
93 - { property: "og:url", content: config.public.baseUrl + route.fullPath }, 92 + { property: "og:url", content: config.public.baseUrl + route.path },
94 { property: "og:site_name", content: webSite.value.webname.slice(0, 7) }, 93 { property: "og:site_name", content: webSite.value.webname.slice(0, 7) },
95 { property: "og:updated_time", content: DetailData.value.updateTime }, 94 { property: "og:updated_time", content: DetailData.value.updateTime },
96 { 95 {
@@ -108,7 +107,7 @@ useHead({ @@ -108,7 +107,7 @@ useHead({
108 link: [ 107 link: [
109 { 108 {
110 rel: "canonical", 109 rel: "canonical",
111 - href: config.public.baseUrl + route.fullPath, 110 + href: config.public.baseUrl + route.path,
112 }, 111 },
113 ], 112 ],
114 script: [ 113 script: [
@@ -119,7 +118,7 @@ useHead({ @@ -119,7 +118,7 @@ useHead({
119 "@graph": [ 118 "@graph": [
120 { 119 {
121 "@type": "SoftwareApplication", 120 "@type": "SoftwareApplication",
122 - "@id": config.public.baseUrl + route.fullPath + "#software", 121 + "@id": config.public.baseUrl + route.path + "#software",
123 name: DetailData.value.title, 122 name: DetailData.value.title,
124 description: DetailData.value.description, 123 description: DetailData.value.description,
125 url: DetailData.value.link, 124 url: DetailData.value.link,
@@ -134,7 +133,7 @@ useHead({ @@ -134,7 +133,7 @@ useHead({
134 }, 133 },
135 { 134 {
136 "@type": "Article", 135 "@type": "Article",
137 - "@id": config.public.baseUrl + route.fullPath + "#article", 136 + "@id": config.public.baseUrl + route.path + "#article",
138 headline: DetailData.value.title, 137 headline: DetailData.value.title,
139 description: DetailData.value.description, 138 description: DetailData.value.description,
140 image: config.public.baseUrl + DetailData.value.image, 139 image: config.public.baseUrl + DetailData.value.image,
@@ -148,12 +147,12 @@ useHead({ @@ -148,12 +147,12 @@ useHead({
148 }, 147 },
149 mainEntityOfPage: { 148 mainEntityOfPage: {
150 "@type": "WebPage", 149 "@type": "WebPage",
151 - "@id": config.public.baseUrl + route.fullPath, 150 + "@id": config.public.baseUrl + route.path,
152 }, 151 },
153 }, 152 },
154 { 153 {
155 "@type": "BreadcrumbList", 154 "@type": "BreadcrumbList",
156 - "@id": config.public.baseUrl + route.fullPath + "#breadcrumb", 155 + "@id": config.public.baseUrl + route.path + "#breadcrumb",
157 itemListElement: [ 156 itemListElement: [
158 { 157 {
159 "@type": "ListItem", 158 "@type": "ListItem",
@@ -317,7 +316,7 @@ useHead({ @@ -317,7 +316,7 @@ useHead({
317 相关推荐 316 相关推荐
318 </h2> 317 </h2>
319 <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-3 gap-4"> 318 <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-3 gap-4">
320 - <CommonCard :cardList="relatedApps" /> 319 + <CommonCard :cardList="relatedApps" :routePath="''" />
321 </div> 320 </div>
322 </div> 321 </div>
323 </div> 322 </div>
此 diff 太大无法显示。
1 -export default defineEventHandler(async (event) => {  
2 -  
3 - const res = await $fetch('http://htai.toolsai.cc/sitemap.xml', {  
4 - method: 'get'  
5 - })  
6 -  
7 - console.log("后台数据", res)  
8 - event.node.res.setHeader('content-type', 'text/xml')  
9 - return res  
10 -})