正在显示
26 个修改的文件
包含
1257 行增加
和
121 行删除
不能预览此文件类型
| @@ -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<{ | ||
| 84 | - cardList: any[]; | ||
| 85 | -}>(); | 83 | +// 设置默认值 |
| 84 | +const props = withDefaults( | ||
| 85 | + defineProps<{ | ||
| 86 | + cardList: any[]; | ||
| 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 | - >查看更多 >></a | 54 | + :to="routePath + `/category/${childAlias}`" |
| 55 | + >查看更多 >></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 | - >>></a | 59 | + :to="routePath + `/category/${childAlias}`" |
| 60 | + >>></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<{ | ||
| 159 | - childData: any; | ||
| 160 | -}>(); | 157 | +// 设置默认值 |
| 158 | +const props = withDefaults( | ||
| 159 | + defineProps<{ | ||
| 160 | + childData: any; | ||
| 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 | - >查看更多 >></a | 16 | + :to="`${routePath}/category/${childData.alias}`" |
| 17 | + >查看更多 >></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 | - >>></a | 21 | + :to="`${routePath}/category/${childData.alias}`" |
| 22 | + >>></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<{ | ||
| 109 | - childData: any; | ||
| 110 | -}>(); | 112 | +// 设置默认值 |
| 113 | +const props = withDefaults( | ||
| 114 | + defineProps<{ | ||
| 115 | + childData: any; | ||
| 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<{ | ||
| 8 | - appData: any; | ||
| 9 | -}>(); | 15 | +withDefaults( |
| 16 | + defineProps<{ | ||
| 17 | + appData: any; | ||
| 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<{ | ||
| 120 | - recommendList: any[]; | ||
| 121 | - navTitle: string; | ||
| 122 | - navIcon: string; | ||
| 123 | -}>(); | 119 | +// 设置默认值 |
| 120 | +withDefaults( | ||
| 121 | + defineProps<{ | ||
| 122 | + recommendList: any[]; | ||
| 123 | + navTitle: string; | ||
| 124 | + navIcon: string; | ||
| 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> |
| @@ -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> |
pages/hktw/category/[name]/index.vue
0 → 100644
| 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> |
pages/hktw/index.vue
0 → 100644
| 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> |
pages/hktw/search/index.vue
0 → 100644
| 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> |
pages/hktw/search/page/[pageNum].vue
0 → 100644
| 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> |
pages/hktw/site-details/[id].vue
0 → 100644
| 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&utm_source=aikit&utm_content=&utm_id=&utm_campaign=&utm_term=hw_coze_aikit&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 太大无法显示。
server/routes/sitemap.xml.ts
已删除
100644 → 0
-
请 注册 或 登录 后发表评论