作者 xiaoqiu

首次提交

  1 +# Nuxt dev/build outputs
  2 +.output
  3 +.nuxt
  4 +.nitro
  5 +.cache
  6 +dist
  7 +
  8 +# Node dependencies
  9 +node_modules
  10 +
  11 +# Logs
  12 +logs
  13 +*.log
  14 +
  15 +# Misc
  16 +.DS_Store
  17 +.fleet
  18 +.idea
  19 +
  20 +# Local env files
  21 +.env
  22 +.env.*
  23 +!.env.example
  1 +shamefully-hoist=true
  2 +strict-peer-dependencies=false
  1 +# nuxt3-template
  2 +
  3 +#### Description
  4 +nuxt3的模版代码,因为nuxt3项目初始化失败所以保存此模版
  5 +
  6 +#### Software Architecture
  7 +Software architecture description
  8 +
  9 +#### Installation
  10 +
  11 +1. xxxx
  12 +2. xxxx
  13 +3. xxxx
  14 +
  15 +#### Instructions
  16 +
  17 +1. xxxx
  18 +2. xxxx
  19 +3. xxxx
  20 +
  21 +#### Contribution
  22 +
  23 +1. Fork the repository
  24 +2. Create Feat_xxx branch
  25 +3. Commit your code
  26 +4. Create Pull Request
  27 +
  28 +
  29 +#### Gitee Feature
  30 +
  31 +1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
  32 +2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
  33 +3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
  34 +4. The most valuable open source project [GVP](https://gitee.com/gvp)
  35 +5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
  36 +6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
  1 +# nuxt3-template
  2 +
  3 +#### 介绍
  4 +nuxt3的模版代码,因为nuxt3项目初始化失败所以保存此模版
  5 +
  6 +#### 软件架构
  7 +软件架构说明
  8 +
  9 +
  10 +#### 安装教程
  11 +
  12 +1. xxxx
  13 +2. xxxx
  14 +3. xxxx
  15 +
  16 +#### 使用说明
  17 +
  18 +1. xxxx
  19 +2. xxxx
  20 +3. xxxx
  21 +
  22 +#### 参与贡献
  23 +
  24 +1. Fork 本仓库
  25 +2. 新建 Feat_xxx 分支
  26 +3. 提交代码
  27 +4. 新建 Pull Request
  28 +
  29 +
  30 +#### 特技
  31 +
  32 +1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
  33 +2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
  34 +3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
  35 +4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
  36 +5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
  37 +6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
  1 +<template>
  2 + <NuxtLayout>
  3 + <NuxtPage></NuxtPage>
  4 + </NuxtLayout>
  5 +</template>
  6 +
  7 +<script setup>
  8 +const webSite = useState("webSite", () => {});
  9 +const sortList = useState("sortTree", () => []);
  10 +const { data: webData } = await useFetch(
  11 + "http://aitoolht.crgx.net/dh/config/get"
  12 +);
  13 +const { data: treeData } = await useFetch(
  14 + "http://aitoolht.crgx.net/dh/type/typeTree"
  15 +);
  16 +webSite.value = webData.value.data;
  17 +sortList.value = treeData.value.data;
  18 +
  19 +useHead({
  20 + title: webSite.value.webname,
  21 + meta: [
  22 + { name: "description", content: webSite.value.webdescription },
  23 + { name: "keywords", content: webSite.value.webkeywords },
  24 + ],
  25 +});
  26 +</script>
  27 +
  28 +<style>
  29 +.scroll-container {
  30 + /* 隐藏滚动条 */
  31 + scrollbar-width: none; /* Firefox */
  32 + -ms-overflow-style: none; /* IE/Edge */
  33 +}
  34 +
  35 +.scroll-container::-webkit-scrollbar {
  36 + display: none; /* Chrome/Safari/Opera */
  37 +}
  38 +</style>
  1 +@font-face {
  2 + font-family: "iconfont"; /* Project id 5094593 */
  3 + src: url('iconfont.woff2?t=1766651481158') format('woff2'),
  4 + url('iconfont.woff?t=1766651481158') format('woff'),
  5 + url('iconfont.ttf?t=1766651481158') format('truetype');
  6 +}
  7 +
  8 +.iconfont {
  9 + font-family: "iconfont" !important;
  10 + font-size: 16px;
  11 + font-style: normal;
  12 + -webkit-font-smoothing: antialiased;
  13 + -moz-osx-font-smoothing: grayscale;
  14 +}
  15 +
  16 +.icon-tag:before {
  17 + content: "\e6af";
  18 +}
  19 +
  20 +.icon-folder:before {
  21 + content: "\e631";
  22 +}
  23 +
  24 +.icon-video:before {
  25 + content: "\e632";
  26 +}
  27 +
  28 +.icon-image:before {
  29 + content: "\e704";
  30 +}
  31 +
  32 +.icon-music:before {
  33 + content: "\e645";
  34 +}
  35 +
  36 +.icon-study:before {
  37 + content: "\e634";
  38 +}
  39 +
  40 +.icon-dev:before {
  41 + content: "\e635";
  42 +}
  43 +
  44 +.icon-download:before {
  45 + content: "\e600";
  46 +}
  47 +
  48 +.icon-upload:before {
  49 + content: "\e656";
  50 +}
  51 +
  52 +.icon-fontsize:before {
  53 + content: "\e7a1";
  54 +}
  55 +
  56 +.icon-money:before {
  57 + content: "\e663";
  58 +}
  59 +
  60 +.icon-clipboard:before {
  61 + content: "\ea4c";
  62 +}
  63 +
  64 +.icon-tool:before {
  65 + content: "\e678";
  66 +}
  67 +
  68 +.icon-listnested:before {
  69 + content: "\e78c";
  70 +}
  71 +
  72 +.icon-qq:before {
  73 + content: "\e65d";
  74 +}
  75 +
  76 +.icon-select:before {
  77 + content: "\e60d";
  78 +}
  79 +
  80 +.icon-fullscreen:before {
  81 + content: "\e620";
  82 +}
  83 +
  84 +.icon-message:before {
  85 + content: "\e643";
  86 +}
  87 +
  88 +.icon-riqi2:before {
  89 + content: "\e697";
  90 +}
  91 +
  92 +.icon-user:before {
  93 + content: "\e752";
  94 +}
  95 +
  96 +.icon-switch:before {
  97 + content: "\e601";
  98 +}
  99 +
  100 +.icon-star:before {
  101 + content: "\e61d";
  102 +}
  103 +
  104 +.icon-color:before {
  105 + content: "\e760";
  106 +}
  107 +
  108 +.icon-password:before {
  109 + content: "\e82b";
  110 +}
  111 +
  112 +.icon-system:before {
  113 + content: "\e60c";
  114 +}
  115 +
  116 +.icon-GitHub:before {
  117 + content: "\ea0a";
  118 +}
  119 +
  120 +.icon-redis:before {
  121 + content: "\e619";
  122 +}
  123 +
  124 +.icon-build:before {
  125 + content: "\e7d5";
  126 +}
  127 +
  128 +.icon-post:before {
  129 + content: "\e62d";
  130 +}
  131 +
  132 +.icon-language:before {
  133 + content: "\e602";
  134 +}
  135 +
  136 +.icon-people:before {
  137 + content: "\e603";
  138 +}
  139 +
  140 +.icon-lock:before {
  141 + content: "\e604";
  142 +}
  143 +
  144 +.icon-documentation:before {
  145 + content: "\e605";
  146 +}
  147 +
  148 +.icon-email:before {
  149 + content: "\e606";
  150 +}
  151 +
  152 +.icon-dashboard:before {
  153 + content: "\e607";
  154 +}
  155 +
  156 +.icon-peoples:before {
  157 + content: "\e60a";
  158 +}
  159 +
  160 +.icon-theme:before {
  161 + content: "\e60e";
  162 +}
  163 +
  164 +.icon-eye-open:before {
  165 + content: "\e7ec";
  166 +}
  167 +
  168 +.icon-list:before {
  169 + content: "\e62e";
  170 +}
  171 +
  172 +.icon-question:before {
  173 + content: "\e81f";
  174 +}
  175 +
  176 +.icon-shopping:before {
  177 + content: "\e865";
  178 +}
  179 +
  180 +.icon-online:before {
  181 + content: "\e694";
  182 +}
  183 +
  184 +.icon-bug:before {
  185 + content: "\e8e8";
  186 +}
  187 +
  188 +.icon-chart:before {
  189 + content: "\e608";
  190 +}
  191 +
  192 +.icon-drag:before {
  193 + content: "\e60f";
  194 +}
  195 +
  196 +.icon-input:before {
  197 + content: "\e61a";
  198 +}
  199 +
  200 +.icon-radio:before {
  201 + content: "\e627";
  202 +}
  203 +
  204 +.icon-textarea:before {
  205 + content: "\e62f";
  206 +}
  207 +
  208 +.icon-Excel:before {
  209 + content: "\edde";
  210 +}
  211 +
  212 +.icon-Phone:before {
  213 + content: "\e660";
  214 +}
  215 +
  216 +.icon-component:before {
  217 + content: "\e71a";
  218 +}
  219 +
  220 +.icon-moon:before {
  221 + content: "\e6c3";
  222 +}
  223 +
  224 +.icon-rows:before {
  225 + content: "\e940";
  226 +}
  227 +
  228 +.icon-monitoring:before {
  229 + content: "\e88e";
  230 +}
  231 +
  232 +.icon-wechat:before {
  233 + content: "\e610";
  234 +}
  235 +
  236 +.icon-skill:before {
  237 + content: "\e61c";
  238 +}
  239 +
  240 +.icon-time:before {
  241 + content: "\e621";
  242 +}
  243 +
  244 +.icon-guide:before {
  245 + content: "\e611";
  246 +}
  247 +
  248 +.icon-form:before {
  249 + content: "\e612";
  250 +}
  251 +
  252 +.icon-international:before {
  253 + content: "\e613";
  254 +}
  255 +
  256 +.icon-link:before {
  257 + content: "\e614";
  258 +}
  259 +
  260 +.icon-table:before {
  261 + content: "\e618";
  262 +}
  263 +
  264 +.icon-tab:before {
  265 + content: "\e61b";
  266 +}
  267 +
  268 +.icon-zip:before {
  269 + content: "\e61e";
  270 +}
  271 +
  272 +.icon-bg-pdf:before {
  273 + content: "\e639";
  274 +}
  275 +
  276 +.icon-server:before {
  277 + content: "\e644";
  278 +}
  279 +
  280 +.icon-number:before {
  281 + content: "\e63a";
  282 +}
  283 +
  284 +.icon-enter:before {
  285 + content: "\e609";
  286 +}
  287 +
  288 +.icon-example:before {
  289 + content: "\e60b";
  290 +}
  291 +
  292 +.icon-education:before {
  293 + content: "\e615";
  294 +}
  295 +
  296 +.icon-exit-fullscreen:before {
  297 + content: "\e616";
  298 +}
  299 +
  300 +.icon-tree:before {
  301 + content: "\e61f";
  302 +}
  303 +
  304 +.icon-slider:before {
  305 + content: "\e622";
  306 +}
  307 +
  308 +.icon-search:before {
  309 + content: "\e747";
  310 +}
  311 +
  312 +.icon-cascader:before {
  313 + content: "\e664";
  314 +}
  315 +
  316 +.icon-tree-table:before {
  317 + content: "\e64e";
  318 +}
  319 +
  320 +.icon-sunny:before {
  321 + content: "\e67d";
  322 +}
  323 +
  324 +.icon-edit:before {
  325 + content: "\e653";
  326 +}
  327 +
  328 +.icon-swagger:before {
  329 + content: "\e623";
  330 +}
  331 +
  332 +.icon-more-up:before {
  333 + content: "\e667";
  334 +}
  335 +
  336 +.icon-druid:before {
  337 + content: "\e657";
  338 +}
  339 +
  340 +.icon-logininfor:before {
  341 + content: "\e74f";
  342 +}
  343 +
  344 +.icon-time-range:before {
  345 + content: "\e762";
  346 +}
  347 +
  348 +.icon-log:before {
  349 + content: "\e624";
  350 +}
  351 +
  352 +.icon-job:before {
  353 + content: "\e633";
  354 +}
  355 +
  356 +.icon-checkbox:before {
  357 + content: "\e625";
  358 +}
  359 +
  360 +.icon-redis-list:before {
  361 + content: "\e626";
  362 +}
  363 +
  364 +.icon-code:before {
  365 + content: "\e84f";
  366 +}
  367 +
  368 +.icon-date-range:before {
  369 + content: "\e628";
  370 +}
  371 +
  372 +.icon-eye:before {
  373 + content: "\e629";
  374 +}
  375 +
  376 +.icon-a-404:before {
  377 + content: "\e62a";
  378 +}
  379 +
  380 +.icon-icon:before {
  381 + content: "\e62b";
  382 +}
  383 +
  384 +.icon-validCode:before {
  385 + content: "\e738";
  386 +}
  387 +
  388 +.icon-rate:before {
  389 + content: "\e62c";
  390 +}
  391 +
  392 +.icon-dict:before {
  393 + content: "\e630";
  394 +}
  395 +
  396 +.icon-write:before {
  397 + content: "\e617";
  398 +}
  399 +
此 diff 太大无法显示。
不能预览此文件类型
不能预览此文件类型
不能预览此文件类型
  1 +<template>
  2 + <!-- Footer -->
  3 + <footer class="bg-gray-800 text-white py-12 px-8 mt-auto">
  4 + <div class="max-w-6xl mx-auto grid grid-cols-1 md:grid-cols-4 gap-8">
  5 + <div>
  6 + <h3 class="text-xl font-bold mb-4">
  7 + {{ webSite.webname }}
  8 + </h3>
  9 + <p class="text-gray-400">
  10 + 提供安全、快速的网址跳转服务,保护您的隐私安全。
  11 + </p>
  12 + </div>
  13 +
  14 + <div>
  15 + <h4 class="font-bold mb-4">联系我们</h4>
  16 + <ul class="space-y-2 text-gray-400">
  17 + <li>邮箱: contact@linkhub.com</li>
  18 + <li>电话: +86 400 123 4567</li>
  19 + </ul>
  20 + </div>
  21 + <div>
  22 + <h4 class="font-bold mb-4">关注我们</h4>
  23 + <div class="flex space-x-4">
  24 + <a
  25 + href="#"
  26 + class="w-10 h-10 rounded-full bg-gray-700 flex items-center justify-center hover:bg-blue-500 transition-colors"
  27 + >
  28 + <el-icon :size="20"><Star /></el-icon>
  29 + </a>
  30 + <a
  31 + href="#"
  32 + class="w-10 h-10 rounded-full bg-gray-700 flex items-center justify-center hover:bg-blue-400 transition-colors"
  33 + >
  34 + <el-icon :size="20"><Link /></el-icon>
  35 + </a>
  36 + <a
  37 + href="#"
  38 + class="w-10 h-10 rounded-full bg-gray-700 flex items-center justify-center hover:bg-pink-500 transition-colors"
  39 + >
  40 + <el-icon :size="20"><Star /></el-icon>
  41 + </a>
  42 + <a
  43 + href="#"
  44 + class="w-10 h-10 rounded-full bg-gray-700 flex items-center justify-center hover:bg-red-500 transition-colors"
  45 + >
  46 + <el-icon :size="20"><Search /></el-icon>
  47 + </a>
  48 + </div>
  49 + </div>
  50 + </div>
  51 + <div
  52 + class="max-w-6xl mx-auto mt-8 pt-8 border-t border-gray-700 text-center text-gray-400"
  53 + >
  54 + <p>&copy; {{ webSite.bottomAnnouncement }}</p>
  55 + </div>
  56 + </footer>
  57 +</template>
  58 +
  59 +<script setup>
  60 +import { Link, Search, Star } from "@element-plus/icons-vue";
  61 +const webSite = useState("webSite");
  62 +</script>
  63 +
  64 +<style scoped lang="less"></style>
  1 +<template>
  2 + <!-- 顶部导航栏 -->
  3 + <header
  4 + class="fixed top-0 left-0 right-0 z-50 bg-gray-900 text-white shadow-md"
  5 + >
  6 + <div class="mx-auto md:px-6 px-3 py-3 flex items-center justify-between">
  7 + <NuxtLink to="/" class="flex items-center space-x-2">
  8 + <el-icon :size="24"><Promotion /></el-icon>
  9 + <h1 class="md:text-xl text-base font-bold">
  10 + {{ webSite.webname }}
  11 + </h1>
  12 + </NuxtLink>
  13 +
  14 + <div class="flex items-center gap-2">
  15 + <MySearch />
  16 + <MyMenu />
  17 + </div>
  18 + </div>
  19 + </header>
  20 +</template>
  21 +
  22 +<script setup>
  23 +import { Promotion } from "@element-plus/icons-vue";
  24 +const webSite = useState("webSite");
  25 +</script>
  26 +
  27 +<style scoped lang="less"></style>
  1 +<template>
  2 + <nav
  3 + class="max-[768px]:flex-[0] scroll-container w-56 bg-white shadow-lg h-[calc(100vh-4rem)] sticky top-16 overflow-y-auto"
  4 + >
  5 + <div class="md:p-4 p-2">
  6 + <h2 class="text-lg font-semibold mb-4 text-gray-700">工具分类</h2>
  7 + <ul class="space-y-1">
  8 + <li v-for="(category, index) in sortList" :key="index">
  9 + <a
  10 + :href="`#term-${category.id}`"
  11 + @click.stop="toggleCategory($event, category.id, index)"
  12 + class="w-full flex items-center justify-between p-3 text-[#515c6b] hover:text-[#5961f9] rounded-lg hover:bg-gray-100 transition-colors"
  13 + >
  14 + <div class="flex items-center space-x-2">
  15 + <i
  16 + class="iconfont text-sm"
  17 + :class="[`icon-${category.icon}`]"
  18 + ></i>
  19 + <span class="text-sm">{{ category.label }}</span>
  20 + </div>
  21 + <div v-if="category.children">
  22 + <el-icon
  23 + size="14px"
  24 + color="#515c6b"
  25 + v-show="activeCategory !== index"
  26 + >
  27 + <ArrowRightBold />
  28 + </el-icon>
  29 + <el-icon
  30 + size="14px"
  31 + color="#515c6b"
  32 + v-show="activeCategory === index"
  33 + >
  34 + <ArrowDownBold />
  35 + </el-icon>
  36 + </div>
  37 + </a>
  38 +
  39 + <transition name="slide">
  40 + <ul v-show="activeCategory === index" class="ml-4 space-y-0.5">
  41 + <li
  42 + v-for="(subItem, subIndex) in category.children"
  43 + :key="subItem.id"
  44 + >
  45 + <a
  46 + :href="`#term-${category.id}-${subItem.id}`"
  47 + class="block text-sm py-2 px-3 rounded hover:bg-gray-100 text-[#515c6b] hover:text-[#5961f9] transition-colors"
  48 + @click.stop=""
  49 + >
  50 + {{ subItem.label }}
  51 + </a>
  52 + </li>
  53 + </ul>
  54 + </transition>
  55 + </li>
  56 + </ul>
  57 + </div>
  58 + </nav>
  59 +</template>
  60 +
  61 +<script setup lang="ts">
  62 +import { ArrowRightBold, ArrowDownBold } from "@element-plus/icons-vue";
  63 +const sortList = useState("sortTree");
  64 +// 激活的分类索引
  65 +const activeCategory = ref<number | null>(0);
  66 +const route = useRoute();
  67 +const router = useRouter();
  68 +
  69 +// 切换分类展开状态
  70 +const toggleCategory = (event: any, id: number, index: number) => {
  71 + if (activeCategory.value === index) {
  72 + activeCategory.value = null;
  73 + } else {
  74 + activeCategory.value = index;
  75 + }
  76 + event?.preventDefault();
  77 + if (route.path === "/") {
  78 + document.getElementById(`term-${id}`)?.scrollIntoView({
  79 + behavior: "smooth",
  80 + block: "center",
  81 + });
  82 + } else {
  83 + router.push("/");
  84 + let timer = setTimeout(() => {
  85 + document.getElementById(`term-${id}`)?.scrollIntoView({
  86 + behavior: "smooth",
  87 + block: "center",
  88 + });
  89 + clearTimeout(timer);
  90 + }, 500);
  91 + }
  92 +};
  93 +</script>
  1 +<template>
  2 + <section
  3 + class="mb-12 rounded-2xl overflow-hidden relative h-80 bg-gradient-to-r from-blue-500 to-purple-600 text-white"
  4 + >
  5 + <div class="absolute inset-0 bg-black bg-opacity-30"></div>
  6 + <div class="relative z-10 h-full flex flex-col justify-center px-12">
  7 + <h2 class="text-4xl font-bold mb-4">发现强大的 AI 工具</h2>
  8 + <p class="text-xl max-w-2xl mb-6">
  9 + 一站式获取各类 AI 解决方案,提升工作效率与创造力
  10 + </p>
  11 + <button
  12 + class="!rounded-button whitespace-nowrap self-start px-6 py-3 bg-white text-blue-600 font-medium hover:bg-gray-100 transition-colors"
  13 + >
  14 + 开始探索
  15 + </button>
  16 + </div>
  17 + </section>
  18 +</template>
  1 +<template>
  2 + <div class="mb-6">
  3 + <div class="flex items-center mb-2">
  4 + <i
  5 + :id="`term-${childData.id}`"
  6 + class="iconfont text-lg mr-2"
  7 + :class="[`icon-${childData.icon}`]"
  8 + ></i>
  9 + <h4 class="text-xl text-[#555]">
  10 + {{ childData.label }}
  11 + </h4>
  12 + </div>
  13 + <div class="flex items-center flex-auto">
  14 + <div
  15 + class="scroll-container relative bg-black/10 rounded-[50px] md:overflow-hidden p-[3px] overflow-y-auto"
  16 + slidertab="sliderTab"
  17 + >
  18 + <ul
  19 + class="relative whitespace-nowrap flex"
  20 + style="flex-wrap: inherit"
  21 + role="tablist"
  22 + >
  23 + <li
  24 + class="anchor md:w-[120.891px] w-[107.3px] cursor-pointer rounded-[100px] bg-[#5961f9]"
  25 + style="
  26 + position: absolute;
  27 + height: 28px;
  28 + opacity: 1;
  29 + transition: 0.35s;
  30 + "
  31 + :style="{ left: `${left}px` }"
  32 + ></li>
  33 + <li
  34 + v-for="(child, index) in childData.children"
  35 + class="h-auto w-auto cursor-pointer"
  36 + >
  37 + <a
  38 + :id="`#term-${childData.id}-${child.id}`"
  39 + class="h-7 leading-7 px-3 block relative text-[#888] text-center md:text-sm text-xs md:leading-7"
  40 + :class="[index === currentFilter ? 'text-white' : '']"
  41 + style="transition: 0.25s"
  42 + :href="`#tab-${childData.id}-${child.id}`"
  43 + @click.stop="onClick($event, child.alias, index)"
  44 + >{{ child.label }}</a
  45 + >
  46 + </li>
  47 + </ul>
  48 + </div>
  49 + <div class="flex-auto"></div>
  50 + <a
  51 + class="hidden md:block text-xs ml-2 text-[#282a2d] hover:text-[#5961f9]"
  52 + :href="`/category/${childAlias}`"
  53 + >查看更多 &gt;&gt;</a
  54 + >
  55 + <a
  56 + class="md:hidden text-xs ml-2 text-[#282a2d] hover:text-[#5961f9]"
  57 + :href="`/category/${childAlias}`"
  58 + >&gt;&gt;</a
  59 + >
  60 + </div>
  61 + </div>
  62 +
  63 + <!-- 内容区域 -->
  64 + <div
  65 + v-for="(childContentItem, childContentIndex) in childData.children"
  66 + :key="childContentItem.id"
  67 + v-show="currentFilter === childContentIndex"
  68 + class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 md:gap-6 gap-4"
  69 + >
  70 + <div
  71 + v-for="appItem in childContentItem.appVos"
  72 + :key="appItem.id"
  73 + class="bg-white rounded-xl shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300"
  74 + >
  75 + <el-popconfirm
  76 + v-if="appItem.isPopup == '1'"
  77 + class="box-item"
  78 + :title="appItem.popupContent"
  79 + placement="top-start"
  80 + width="280"
  81 + :icon="Promotion"
  82 + confirm-button-text="确认前往"
  83 + cancel-button-text="取消"
  84 + @confirm="onConfirm(appItem.id)"
  85 + >
  86 + <template #reference>
  87 + <a
  88 + :href="'/details/' + appItem.id"
  89 + target="_blank"
  90 + @click.stop="onNuxtLink"
  91 + >
  92 + <div class="group p-3">
  93 + <div class="flex items-start space-x-4">
  94 + <img
  95 + :src="'http://aitoolht.crgx.net' + appItem.image"
  96 + :alt="appItem.title"
  97 + class="w-10 h-10 md:w-14 md:h-14 object-cover rounded-lg"
  98 + />
  99 + <div>
  100 + <h3
  101 + class="font-bold md:text-base text-sm line-clamp-1 text-gray-800 transition group-hover:text-[#5961f9]"
  102 + >
  103 + {{ appItem.title }}
  104 + </h3>
  105 + <p
  106 + class="text-gray-600 text-xs mt-1 md:line-clamp-2 line-clamp-1"
  107 + >
  108 + {{ appItem.description }}
  109 + </p>
  110 + </div>
  111 + </div>
  112 + </div>
  113 + </a>
  114 + </template>
  115 + </el-popconfirm>
  116 + <a v-else :href="'/details/' + appItem.id" target="_blank">
  117 + <div class="group p-3">
  118 + <div class="flex items-start space-x-4">
  119 + <img
  120 + :src="'http://aitoolht.crgx.net' + appItem.image"
  121 + :alt="appItem.title"
  122 + class="w-10 h-10 md:w-14 md:h-14 object-cover rounded-lg"
  123 + />
  124 + <div>
  125 + <h3
  126 + class="font-bold md:text-base text-sm line-clamp-1 text-gray-800 transition group-hover:text-[#5961f9]"
  127 + >
  128 + {{ appItem.title }}
  129 + </h3>
  130 + <p
  131 + class="text-gray-600 text-xs mt-1 md:line-clamp-2 line-clamp-1"
  132 + >
  133 + {{ appItem.description }}
  134 + </p>
  135 + </div>
  136 + </div>
  137 + </div>
  138 + </a>
  139 + </div>
  140 + </div>
  141 +</template>
  142 +
  143 +<script setup lang="ts">
  144 +import { Promotion } from "@element-plus/icons-vue";
  145 +
  146 +const props = defineProps<{
  147 + childData: any;
  148 +}>();
  149 +
  150 +const childAlias = ref(props.childData.children[0].alias);
  151 +// 阻止默认行为
  152 +function onNuxtLink(event: any) {
  153 + event.preventDefault();
  154 +}
  155 +// 点击确认跳转
  156 +function onConfirm(id: number) {
  157 + window.open(`/details/${id}`);
  158 +}
  159 +// 导航样式内容
  160 +const currentFilter = ref(0);
  161 +const left = ref(0);
  162 +
  163 +// 切换分类内容
  164 +function onClick(event: any, alias: string, index: number) {
  165 + let moveWidth = window.innerWidth > 768 ? 120.891 : 107.3;
  166 + event?.preventDefault();
  167 + childAlias.value = alias;
  168 + currentFilter.value = index;
  169 + left.value = index * moveWidth;
  170 +}
  171 +</script>
  1 +<template>
  2 + <!-- 分类导航 -->
  3 + <div class="mb-6">
  4 + <div class="flex items-center mb-2">
  5 + <h4 class="text-gray text-lg m-0">
  6 + <i
  7 + :id="`term-${childData.id}`"
  8 + class="iconfont text-lg mr-2"
  9 + :class="[`icon-${childData.icon}`]"
  10 + ></i>
  11 + {{ childData.label }}
  12 + </h4>
  13 + <div class="flex-auto"></div>
  14 + <a
  15 + class="hidden md:block text-xs ml-2 text-[#282a2d] hover:text-[#5961f9]"
  16 + :href="`/category/${childData.alias}`"
  17 + >查看更多 &gt;&gt;</a
  18 + >
  19 + <a
  20 + class="md:hidden text-xs ml-2 text-[#282a2d] hover:text-[#5961f9]"
  21 + :href="`/category/${childData.alias}`"
  22 + >&gt;&gt;</a
  23 + >
  24 + </div>
  25 + </div>
  26 + <!-- 分类内容 -->
  27 + <div
  28 + class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 md:gap-6 gap-4"
  29 + >
  30 + <div
  31 + v-for="(item, index) in childData.appVos"
  32 + :key="index"
  33 + class="bg-white rounded-xl shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300"
  34 + >
  35 + <el-popconfirm
  36 + v-if="item.isPopup == '1'"
  37 + class="box-item"
  38 + :title="item.popupContent"
  39 + placement="top-start"
  40 + icon-color="#5961f9"
  41 + width="280"
  42 + :icon="Promotion"
  43 + confirm-button-text="确认前往"
  44 + cancel-button-text="取消"
  45 + @confirm="onConfirm(item.id)"
  46 + >
  47 + <template #reference>
  48 + <a
  49 + :href="'/details/' + item.id"
  50 + target="_blank"
  51 + @click.stop="onNuxtLink"
  52 + >
  53 + <div class="group p-3">
  54 + <div class="flex items-start space-x-4">
  55 + <img
  56 + :src="'http://aitoolht.crgx.net' + item.image"
  57 + :alt="item.title"
  58 + class="w-10 h-10 md:w-14 md:h-14 object-cover rounded-lg"
  59 + />
  60 + <div>
  61 + <h3
  62 + class="font-bold md:text-base text-sm line-clamp-1 text-gray-800 transition group-hover:text-[#5961f9]"
  63 + >
  64 + {{ item.title }}
  65 + </h3>
  66 + <p
  67 + class="text-gray-600 text-xs mt-1 md:line-clamp-2 line-clamp-1"
  68 + >
  69 + {{ item.description }}
  70 + </p>
  71 + </div>
  72 + </div>
  73 + </div>
  74 + </a>
  75 + </template>
  76 + </el-popconfirm>
  77 + <a v-else :href="'/details/' + item.id" target="_blank">
  78 + <div class="group p-3">
  79 + <div class="flex items-start space-x-4">
  80 + <img
  81 + :src="'http://aitoolht.crgx.net' + item.image"
  82 + :alt="item.title"
  83 + class="w-10 h-10 md:w-14 md:h-14 object-cover rounded-lg"
  84 + />
  85 + <div>
  86 + <h3
  87 + class="font-bold md:text-base text-sm line-clamp-1 text-gray-800 transition group-hover:text-[#5961f9]"
  88 + >
  89 + {{ item.title }}
  90 + </h3>
  91 + <p
  92 + class="text-gray-600 text-xs mt-1 md:line-clamp-2 line-clamp-1"
  93 + >
  94 + {{ item.description }}
  95 + </p>
  96 + </div>
  97 + </div>
  98 + </div>
  99 + </a>
  100 + </div>
  101 + </div>
  102 +</template>
  103 +
  104 +<script setup lang="ts">
  105 +import { Promotion } from "@element-plus/icons-vue";
  106 +defineProps<{
  107 + childData: any;
  108 +}>();
  109 +
  110 +// 阻止默认行为
  111 +function onNuxtLink(event: any) {
  112 + event.preventDefault();
  113 +}
  114 +// 点击确认跳转
  115 +function onConfirm(id: number) {
  116 + window.open(`/details/${id}`);
  117 +}
  118 +</script>
  1 +<template>
  2 + <LazyHomeContentHasChildren v-if="appData.children" :childData="appData" />
  3 + <LazyHomeContentNoChildren v-else :childData="appData" />
  4 +</template>
  5 +
  6 +<script lang="ts" setup>
  7 +defineProps<{
  8 + appData: any;
  9 +}>();
  10 +</script>
  1 +<template>
  2 + <div class="md:mb-12 mb-6">
  3 + <div class="flex mb-6">
  4 + <div
  5 + class="relative bg-black/10 rounded-[50px] p-[3px] overflow-hidden"
  6 + slidertab="sliderTab"
  7 + >
  8 + <ul
  9 + class="flex relative whitespace-nowrap overflow-x-auto rounded-[100px] bg-[#5961f9]"
  10 + style="flex-wrap: inherit; overflow-y: unset"
  11 + role="tablist"
  12 + >
  13 + <li
  14 + class="w-auto h-auto cursor-pointer list-item whitespace-nowrap line-clamp-1"
  15 + >
  16 + <a
  17 + class="h-7 leading-7 px-3 block relative text-white text-center text-sm"
  18 + data-toggle="pill"
  19 + data-action="load_hot_post"
  20 + data-datas='{"data":{"title":"热门工具","type":"sites","order":"views","num":"12","mini":""}}'
  21 + >
  22 + <i
  23 + class="iconfont mr-2 font-bold text-xs"
  24 + :class="[`icon-${navIcon}`]"
  25 + ></i>
  26 + {{ navTitle }}
  27 + </a>
  28 + </li>
  29 + </ul>
  30 + </div>
  31 + </div>
  32 +
  33 + <div
  34 + class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 md:gap-6 gap-4"
  35 + >
  36 + <div
  37 + v-for="(item, index) in recommendList"
  38 + :key="index"
  39 + class="bg-white rounded-xl shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300"
  40 + >
  41 + <el-popconfirm
  42 + v-if="item.isPopup == '1'"
  43 + class="box-item"
  44 + :title="item.popupContent"
  45 + placement="top-start"
  46 + icon-color="#5961f9"
  47 + width="280"
  48 + :icon="Promotion"
  49 + confirm-button-text="确认前往"
  50 + cancel-button-text="取消"
  51 + @confirm="onConfirm(item.id)"
  52 + >
  53 + <template #reference>
  54 + <a
  55 + :href="'/details/' + item.id"
  56 + target="_blank"
  57 + @click.stop="onNuxtLink"
  58 + >
  59 + <div class="group p-3">
  60 + <div class="flex items-start space-x-4">
  61 + <img
  62 + :src="'http://aitoolht.crgx.net' + item.image"
  63 + :alt="item.title"
  64 + class="w-10 h-10 md:w-14 md:h-14 object-cover rounded-lg"
  65 + />
  66 + <div>
  67 + <h3
  68 + class="font-bold md:text-base text-sm line-clamp-1 text-gray-800 transition group-hover:text-[#5961f9]"
  69 + >
  70 + {{ item.title }}
  71 + </h3>
  72 + <p
  73 + class="text-gray-600 text-xs mt-1 md:line-clamp-2 line-clamp-1"
  74 + >
  75 + {{ item.description }}
  76 + </p>
  77 + </div>
  78 + </div>
  79 + </div>
  80 + </a>
  81 + </template>
  82 + </el-popconfirm>
  83 + <a v-else :href="'/details/' + item.id" target="_blank">
  84 + <div class="group p-3">
  85 + <div class="flex items-start space-x-4">
  86 + <img
  87 + :src="'http://aitoolht.crgx.net' + item.image"
  88 + :alt="item.title"
  89 + class="w-10 h-10 md:w-14 md:h-14 object-cover rounded-lg"
  90 + />
  91 + <div>
  92 + <h3
  93 + class="font-bold md:text-base text-sm line-clamp-1 text-gray-800 transition group-hover:text-[#5961f9]"
  94 + >
  95 + {{ item.title }}
  96 + </h3>
  97 + <p
  98 + class="text-gray-600 text-xs mt-1 md:line-clamp-2 line-clamp-1"
  99 + >
  100 + {{ item.description }}
  101 + </p>
  102 + </div>
  103 + </div>
  104 + </div>
  105 + </a>
  106 + </div>
  107 + </div>
  108 + </div>
  109 +</template>
  110 +
  111 +<script lang="ts" setup>
  112 +import { Promotion } from "@element-plus/icons-vue";
  113 +defineProps<{
  114 + recommendList: any[];
  115 + navTitle: string;
  116 + navIcon: string;
  117 +}>();
  118 +
  119 +// 阻止默认行为
  120 +function onNuxtLink(event: any) {
  121 + event.preventDefault();
  122 +}
  123 +// 点击确认跳转
  124 +function onConfirm(id: number) {
  125 + window.open(`/details/${id}`);
  126 +}
  127 +</script>
  1 +<template>
  2 + <div class="md:hidden block">
  3 + <input ref="checkboxRef" id="checkbox" type="checkbox" />
  4 + <label class="toggle" for="checkbox" @click="drawer = !drawer">
  5 + <div id="bar1" class="bars"></div>
  6 + <div id="bar2" class="bars"></div>
  7 + <div id="bar3" class="bars"></div>
  8 + </label>
  9 + </div>
  10 + <el-drawer
  11 + v-model="drawer"
  12 + :with-header="false"
  13 + direction="ltr"
  14 + :append-to-body="true"
  15 + :lock-scroll="true"
  16 + size="266"
  17 + custom-class="drawer-sidebar"
  18 + @close="onClose"
  19 + >
  20 + <AppSidebar />
  21 + </el-drawer>
  22 +</template>
  23 +<script setup>
  24 +const drawer = ref(false);
  25 +const checkboxRef = ref(null);
  26 +
  27 +// 关闭弹出框
  28 +function onClose() {
  29 + checkboxRef.value.checked = false;
  30 + drawer.value = false;
  31 +}
  32 +
  33 +onMounted(() => {
  34 + // 获取复选框元素
  35 + checkboxRef.value = document.getElementById("checkbox");
  36 +});
  37 +</script>
  38 +
  39 +<style scoped>
  40 +/* From Uiverse.io by Yaya12085 */
  41 +#checkbox {
  42 + display: none;
  43 +}
  44 +.toggle {
  45 + position: relative;
  46 + width: 35px;
  47 + height: 35px;
  48 + cursor: pointer;
  49 + display: flex;
  50 + flex-direction: column;
  51 + align-items: flex-start;
  52 + justify-content: center;
  53 + gap: 10px;
  54 + transition-duration: 0.5s;
  55 +}
  56 +
  57 +.bars {
  58 + width: 100%;
  59 + height: 4px;
  60 + background-color: rgb(92, 130, 255);
  61 + border-radius: 4px;
  62 +}
  63 +
  64 +#bar2 {
  65 + transition-duration: 0.8s;
  66 +}
  67 +
  68 +#bar1 {
  69 + width: 50%;
  70 +}
  71 +
  72 +#bar2 {
  73 + width: 75%;
  74 +}
  75 +
  76 +#checkbox:checked + .toggle .bars {
  77 + position: absolute;
  78 + transition-duration: 0.5s;
  79 +}
  80 +
  81 +#checkbox:checked + .toggle #bar2 {
  82 + transform: scaleX(0);
  83 + transition-duration: 0.1s;
  84 +}
  85 +
  86 +#checkbox:checked + .toggle #bar1 {
  87 + width: 100%;
  88 + transform: rotate(45deg);
  89 + transition-duration: 0.5s;
  90 +}
  91 +
  92 +#checkbox:checked + .toggle #bar3 {
  93 + width: 100%;
  94 + transform: rotate(-45deg);
  95 + transition-duration: 0.5s;
  96 +}
  97 +
  98 +#checkbox:checked + .toggle {
  99 + transition-duration: 0.5s;
  100 + transform: rotate(180deg);
  101 +}
  102 +</style>
  1 +<template>
  2 + <!-- From Uiverse.io by ZAKARIAE48CHELLE -->
  3 + <div class="input-wrapper">
  4 + <button class="icon">
  5 + <svg
  6 + width="25px"
  7 + height="25px"
  8 + fill="none"
  9 + xmlns="http://www.w3.org/2000/svg"
  10 + >
  11 + <path
  12 + d="M11.5 21C16.7467 21 21 16.7467 21 11.5C21 6.25329 16.7467 2 11.5 2C6.25329 2 2 6.25329 2 11.5C2 16.7467 6.25329 21 11.5 21Z"
  13 + stroke="#fff"
  14 + stroke-width="1.5"
  15 + stroke-linecap="round"
  16 + stroke-linejoin="round"
  17 + ></path>
  18 + <path
  19 + d="M22 22L20 20"
  20 + stroke="#fff"
  21 + stroke-width="1.5"
  22 + stroke-linecap="round"
  23 + stroke-linejoin="round"
  24 + ></path>
  25 + </svg>
  26 + </button>
  27 + <input
  28 + v-model="keyWord"
  29 + type="text"
  30 + name="searchKeyword"
  31 + class="input"
  32 + placeholder="输入应用名称回车查找"
  33 + @keyup.enter="onSearch"
  34 + />
  35 + </div>
  36 +</template>
  37 +
  38 +<script setup>
  39 +const keyWord = ref("");
  40 +function onSearch() {
  41 + if (keyWord.value) {
  42 + // 跳转到搜索页面
  43 + // this.$router.push({ path: "/search", query: { q: keyword.value } });
  44 + window.location.href = "/search?keyword=" + keyWord.value;
  45 + }
  46 +}
  47 +</script>
  48 +
  49 +<style scoped>
  50 +/* From Uiverse.io by ZAKARIAE48CHELLE */
  51 +.input-wrapper {
  52 + display: flex;
  53 + align-items: center;
  54 + justify-content: flex-end;
  55 + gap: 15px;
  56 + position: relative;
  57 + width: 250px;
  58 +}
  59 +
  60 +.input {
  61 + border-style: none;
  62 + height: 50px;
  63 + width: 50px;
  64 + padding: 10px;
  65 + outline: none;
  66 + border-radius: 50%;
  67 + transition: 0.5s ease-in-out;
  68 + background-color: #5961f9;
  69 + box-shadow: 0px 0px 3px #5961f9;
  70 + padding-right: 40px;
  71 + color: #fff;
  72 +}
  73 +
  74 +.input::placeholder,
  75 +.input {
  76 + font-family: "Trebuchet MS", "Lucida Sans Unicode", "Lucida Grande",
  77 + "Lucida Sans", Arial, sans-serif;
  78 + font-size: 17px;
  79 +}
  80 +
  81 +.input::placeholder {
  82 + color: #8f8f8f;
  83 +}
  84 +
  85 +.icon {
  86 + display: flex;
  87 + align-items: center;
  88 + justify-content: center;
  89 + position: absolute;
  90 + right: 0px;
  91 + cursor: pointer;
  92 + width: 50px;
  93 + height: 50px;
  94 + outline: none;
  95 + border-style: none;
  96 + border-radius: 50%;
  97 + pointer-events: painted;
  98 + background-color: transparent;
  99 + transition: 0.2s linear;
  100 +}
  101 +
  102 +.icon:focus ~ .input,
  103 +.input:focus {
  104 + box-shadow: none;
  105 + width: 250px;
  106 + border-radius: 0px;
  107 + background-color: transparent;
  108 + border-bottom: 3px solid #5961f9;
  109 + transition: all 500ms cubic-bezier(0, 0.11, 0.35, 2);
  110 +}
  111 +
  112 +/* 当最大宽度为768pxs时 */
  113 +@media (max-width: 768px) {
  114 + .input-wrapper {
  115 + width: 200px;
  116 + gap: 10px;
  117 + }
  118 + .icon {
  119 + width: 40px;
  120 + height: 40px;
  121 + }
  122 + .input {
  123 + width: 40px;
  124 + height: 40px;
  125 + padding-right: 31px;
  126 + }
  127 + .input::placeholder,
  128 + .input {
  129 + font-family: "Trebuchet MS", "Lucida Sans Unicode", "Lucida Grande",
  130 + "Lucida Sans", Arial, sans-serif;
  131 + font-size: 14px;
  132 + }
  133 + .icon:focus ~ .input,
  134 + .input:focus {
  135 + box-shadow: none;
  136 + width: 200px;
  137 + border-radius: 0px;
  138 + background-color: transparent;
  139 + border-bottom: 1.5px solid #5961f9;
  140 + transition: all 500ms cubic-bezier(0, 0.11, 0.35, 2);
  141 + }
  142 +}
  143 +</style>
  1 +<template>
  2 + <AppHeader />
  3 + <div class="flex md:pt-16 pt-[5rem] flex-grow">
  4 + <AppSidebar />
  5 +
  6 + <div class="w-full flex-1">
  7 + <slot></slot>
  8 + <AppFooter />
  9 + </div>
  10 + </div>
  11 +</template>
  12 +
  13 +<script setup></script>
  14 +
  15 +<style></style>
  1 +// https://nuxt.com/docs/api/configuration/nuxt-config
  2 +export default defineNuxtConfig({
  3 + runtimeConfig: {
  4 + public: {
  5 + baseUrl: 'http://aitoolht.crgx.net',
  6 + }
  7 + },
  8 + devtools: { enabled: true },
  9 + modules: [
  10 + '@nuxtjs/tailwindcss',
  11 + '@element-plus/nuxt'
  12 + ],
  13 + devServer: {
  14 + host: 'localhost',
  15 + port: 3666
  16 + },
  17 + css: [
  18 + '~/assets/iconfonts/iconfont.css',
  19 + ],
  20 + plugins: [
  21 + { src: '~/assets/iconfonts/iconfont.js', ssr: false, mode: 'client' }
  22 + ],
  23 + app: {
  24 + head: {
  25 + title: 'Annie网站',
  26 + htmlAttrs: {
  27 + lang: 'en'
  28 + },
  29 + meta: [
  30 + { charset: 'utf-8' },
  31 + { name: 'viewport', content: 'width=device-width, initial-scale=1' },
  32 + { hid: 'description', name: 'description', content: '提供市面上最简洁的导航系统' },
  33 + { name: 'format-detection', content: 'telephone=no' },
  34 + { name: 'keywords', content: '提供市面上最简洁的导航系统,一个完全免费的导航站'}
  35 + ],
  36 + link: [
  37 + { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
  38 + ]
  39 + }
  40 + },
  41 +})
此 diff 太大无法显示。
  1 +{
  2 + "name": "nuxt-app",
  3 + "private": true,
  4 + "scripts": {
  5 + "build": "nuxt build",
  6 + "dev": "nuxt dev",
  7 + "generate": "nuxt generate",
  8 + "preview": "nuxt preview",
  9 + "postinstall": "nuxt prepare"
  10 + },
  11 + "devDependencies": {
  12 + "@element-plus/nuxt": "^1.0.8",
  13 + "@nuxt/devtools": "latest",
  14 + "@nuxtjs/tailwindcss": "^6.11.4",
  15 + "@types/node": "^18",
  16 + "element-plus": "^2.6.3",
  17 + "nuxt": "^3.5.2"
  18 + },
  19 + "dependencies": {
  20 + "less": "^4.2.0"
  21 + }
  22 +}
  1 +<template>
  2 + <div class="md:p-10 p-4 pt-0" style="min-height: calc(100vh - 320px)">
  3 + <HomeRecommend
  4 + :recommendList="list"
  5 + :navTitle="findLabelByAlias(name as string, sortList as any)"
  6 + navIcon="tag"
  7 + />
  8 + <el-pagination
  9 + background
  10 + layout="prev, pager, next"
  11 + :hide-on-single-page="true"
  12 + v-model:page-size="params.pageSize"
  13 + v-model:current-page="params.pageNum"
  14 + :total="total"
  15 + @current-change="onPageChange"
  16 + />
  17 + </div>
  18 +</template>
  19 +
  20 +<script lang="ts" setup>
  21 +const sortList = useState("sortTree");
  22 +const route = useRoute();
  23 +const router = useRouter();
  24 +const { name } = route.params;
  25 +const list = ref<any[]>([]);
  26 +const total = ref<number>(0);
  27 +const params = ref<any>({
  28 + pageNum: 1,
  29 + pageSize: 5,
  30 + typeAlias: name as string,
  31 +});
  32 +
  33 +// 返回分类名称
  34 +function findLabelByAlias<
  35 + T extends { alias?: string; label?: string; children?: T[] }
  36 +>(alias: string, data: T[], childrenKey: string = "children"): string {
  37 + if (!data || data.length === 0) {
  38 + return "";
  39 + }
  40 +
  41 + // 1. 首先在当前层级查找
  42 + for (const item of data) {
  43 + if (item.alias === alias) {
  44 + return item.label || ""; // 返回 label 或空字符串
  45 + }
  46 + }
  47 +
  48 + // 2. 如果当前层级没找到,递归查找子节点
  49 + for (const item of data) {
  50 + const children = (item as any)[childrenKey] as T[];
  51 + if (children && children.length > 0) {
  52 + const foundLabel = findLabelByAlias(alias, children, childrenKey);
  53 + if (foundLabel !== "") {
  54 + return foundLabel;
  55 + }
  56 + }
  57 + }
  58 +
  59 + return "";
  60 +}
  61 +
  62 +function onPageChange(pageNum: number) {
  63 + router.push({
  64 + path: route.path + "/page/" + pageNum,
  65 + });
  66 +}
  67 +const { data } = await useFetch(
  68 + "http://aitoolht.crgx.net/dh/app/listFrontApp",
  69 + {
  70 + method: "get",
  71 + params: params.value,
  72 + }
  73 +);
  74 +list.value = data.value.rows;
  75 +total.value = data.value.total;
  76 +</script>
  1 +<template>
  2 + <div class="p-10">
  3 + <!-- 推荐工具区 -->
  4 + <HomeRecommend
  5 + :recommendList="list"
  6 + :navTitle="findLabelByAlias(name as string, sortList as any)"
  7 + navIcon="tag"
  8 + :navTitleWidth="120.5"
  9 + />
  10 + <el-pagination
  11 + background
  12 + layout="prev, pager, next"
  13 + :hide-on-single-page="true"
  14 + v-model:page-size="params.pageSize"
  15 + v-model:current-page="params.pageNum"
  16 + :total="total"
  17 + @current-change="onPageChange"
  18 + />
  19 + </div>
  20 +</template>
  21 +
  22 +<script lang="ts" setup>
  23 +import { ref } from "vue";
  24 +const sortList = useState("sortTree");
  25 +const route = useRoute();
  26 +const router = useRouter();
  27 +const { pageNum, name } = route.params;
  28 +const list = ref<any[]>([]);
  29 +const total = ref<number>(0);
  30 +const params = ref<any>({
  31 + pageNum: Number(pageNum),
  32 + pageSize: 5,
  33 + typeAlias: name as string,
  34 +});
  35 +
  36 +// 返回分类名称
  37 +function findLabelByAlias<
  38 + T extends { alias?: string; label?: string; children?: T[] }
  39 +>(alias: string, data: T[], childrenKey: string = "children"): string {
  40 + if (!data || data.length === 0) {
  41 + return "";
  42 + }
  43 +
  44 + // 1. 首先在当前层级查找
  45 + for (const item of data) {
  46 + if (item.alias === alias) {
  47 + return item.label || ""; // 返回 label 或空字符串
  48 + }
  49 + }
  50 +
  51 + // 2. 如果当前层级没找到,递归查找子节点
  52 + for (const item of data) {
  53 + const children = (item as any)[childrenKey] as T[];
  54 + if (children && children.length > 0) {
  55 + const foundLabel = findLabelByAlias(alias, children, childrenKey);
  56 + if (foundLabel !== "") {
  57 + return foundLabel;
  58 + }
  59 + }
  60 + }
  61 +
  62 + return "";
  63 +}
  64 +
  65 +function onPageChange(pageNum: number) {
  66 + if (pageNum === 1) {
  67 + router.push({
  68 + path: "/category/" + name,
  69 + });
  70 + } else if (pageNum > 1) {
  71 + router.push({
  72 + path: route.path + "/page/" + pageNum,
  73 + });
  74 + }
  75 +}
  76 +
  77 +const { data } = await useFetch(
  78 + "http://aitoolht.crgx.net/dh/app/listFrontApp",
  79 + {
  80 + method: "get",
  81 + params: params.value,
  82 + }
  83 +);
  84 +list.value = data.value.rows;
  85 +total.value = data.value.total;
  86 +</script>
  1 +<script setup>
  2 +const route = useRoute();
  3 +const config = useRuntimeConfig();
  4 +const showAd = ref(true);
  5 +const appDetail = ref({
  6 + types: [],
  7 +});
  8 +const webSite = useState("webSite");
  9 +const detailAd = ref({
  10 + width: 300,
  11 + height: 177,
  12 + frontAdVos: [],
  13 +});
  14 +function mergeDuplicates(data) {
  15 + const map = new Map();
  16 +
  17 + data.forEach((item) => {
  18 + if (!map.has(item.id)) {
  19 + // 如果是第一次遇到这个id,创建新对象
  20 + map.set(item.id, {
  21 + id: item.id,
  22 + label: item.label,
  23 + children: [...(item.children || [])],
  24 + });
  25 + } else {
  26 + // 如果已经存在,合并children
  27 + const existing = map.get(item.id);
  28 + // 避免重复的子项(基于子项id)
  29 + const existingChildIds = new Set(
  30 + existing.children.map((child) => child.id)
  31 + );
  32 + item.children.forEach((child) => {
  33 + if (!existingChildIds.has(child.id)) {
  34 + existing.children.push(child);
  35 + }
  36 + });
  37 + }
  38 + });
  39 +
  40 + return Array.from(map.values());
  41 +}
  42 +const { data: detailData } = await useFetch(
  43 + `http://aitoolht.crgx.net/dh/app/${route.params.id}`
  44 +);
  45 +const { data: adData } = await useFetch(
  46 + "http://aitoolht.crgx.net/dh/ad/listFrontAd",
  47 + {
  48 + method: "get",
  49 + params: { pageSize: 10, pageNum: 1, code: "top" },
  50 + }
  51 +);
  52 +detailAd.value = adData.value.rows[0];
  53 +appDetail.value = detailData.value.data;
  54 +appDetail.value.types = mergeDuplicates(detailData.value.data.types);
  55 +
  56 +useHead({
  57 + title: appDetail.value.popupContent
  58 + ? `${appDetail.value.title} - ${appDetail.value.popupContent}`
  59 + : appDetail.value.title,
  60 + meta: [
  61 + { name: "description", content: appDetail.value.description },
  62 + {
  63 + name: "og:title",
  64 + content: `${appDetail.value.title}-${appDetail.value.popupContent}`,
  65 + },
  66 + { name: "og:description", content: appDetail.value.description },
  67 + {
  68 + name: "og:image",
  69 + content: config.public.baseUrl + appDetail.value.image,
  70 + },
  71 + { name: "og:url", content: route.fullPath },
  72 + { name: "og:site_name", content: webSite.value.webname },
  73 + ],
  74 +});
  75 +</script>
  76 +
  77 +<template>
  78 + <div class="flex flex-col min-h-screen bg-white">
  79 + <main class="flex-grow md:p-6 bg-white p-1">
  80 + <!-- Top Application Info Bar -->
  81 + <header
  82 + v-show="appDetail.types.length > 0"
  83 + class="bg-white shadow-sm md:py-4 md:px-8 py-2 px-4 flex md:items-center md:justify-between flex-col md:flex-row"
  84 + >
  85 + <div class="flex items-center space-x-4">
  86 + <img
  87 + :src="config.public.baseUrl + appDetail.image"
  88 + alt="App Icon"
  89 + class="w-16 h-16 object-contain"
  90 + />
  91 + <div>
  92 + <h1 class="text-2xl font-bold text-[#5961f9]">
  93 + {{ appDetail.title }}
  94 + </h1>
  95 + <p class="text-sm text-gray-600 mt-1">
  96 + {{ appDetail.description }}
  97 + </p>
  98 + <div class="mt-2 flex items-center space-x-2">
  99 + <div
  100 + v-for="tag in appDetail.types"
  101 + class="flex items-center space-x-2"
  102 + >
  103 + <template v-if="tag.children.length > 0">
  104 + <NuxtLink
  105 + v-for="child in tag.children"
  106 + :to="'/category/' + child.alias"
  107 + class="px-2 py-1 bg-blue-100 text-[#5961f9] rounded-full text-xs"
  108 + >
  109 + {{ child.label }}
  110 + </NuxtLink>
  111 + </template>
  112 + <template v-else>
  113 + <NuxtLink
  114 + :to="'/category/' + tag.alias"
  115 + class="px-2 py-1 bg-blue-100 text-[#5961f9] rounded-full text-xs"
  116 + >
  117 + {{ tag.label }}
  118 + </NuxtLink>
  119 + </template>
  120 + </div>
  121 + </div>
  122 + </div>
  123 + </div>
  124 + <div class="flex md:space-x-3 md:mt-0 mt-4">
  125 + <a
  126 + :href="appDetail.link"
  127 + target="_blank"
  128 + class="!rounded-button whitespace-nowrap px-4 py-2 bg-[#5961f9] max-[768px]:text-xs text-white hover:bg-blue-600 transition-colors"
  129 + >
  130 + <i class="iconfont icon-guide"></i>访问官网
  131 + </a>
  132 + </div>
  133 + </header>
  134 +
  135 + <main class="relative w-full">
  136 + <!-- 悬浮广告弹窗 -->
  137 + <div
  138 + class="md:absolute top-0 right-0 md:m-4 z-50 relative max-[768px]:m-auto"
  139 + v-show="showAd"
  140 + :style="{
  141 + width: `${detailAd.width}px`,
  142 + height: `${detailAd.height}px`,
  143 + }"
  144 + >
  145 + <div
  146 + class="w-full h-full relative"
  147 + v-for="item in detailAd.frontAdVos"
  148 + >
  149 + <img
  150 + class="w-full h-full object-contain"
  151 + :src="config.public.baseUrl + item.image"
  152 + :alt="item.title"
  153 + />
  154 + <div
  155 + class="absolute top-1 right-1 cursor-pointer bg-white w-4 h-4 text-center rounded-[50%] text-xs"
  156 + @click="showAd = false"
  157 + >
  158 + X
  159 + </div>
  160 + </div>
  161 + </div>
  162 + <div class="md:max-w-5xl mx-auto md:p-8 p-2 w-full">
  163 + <div v-html="appDetail.content"></div>
  164 + </div>
  165 + </main>
  166 + </main>
  167 + </div>
  168 +</template>
  1 +<script setup>
  2 +const recommendList = ref([]);
  3 +const appList = ref([]);
  4 +const { data: RecommendData } = await useFetch(
  5 + "http://aitoolht.crgx.net/dh/app/listFrontApp",
  6 + {
  7 + method: "get",
  8 + params: { pageSize: 10, pageNum: 1, isRecommend: "1" },
  9 + }
  10 +);
  11 +const { data: allData } = await useFetch(
  12 + "http://aitoolht.crgx.net/dh/app/allFrontApp"
  13 +);
  14 +recommendList.value = RecommendData.value.rows;
  15 +appList.value = allData.value.data;
  16 +</script>
  17 +
  18 +<template>
  19 + <div class="flex flex-col min-h-screen bg-white">
  20 + <main class="flex-grow md:p-6 p-2 bg-white">
  21 + <!-- Banner 区域 -->
  22 + <HomeBanner />
  23 + <!-- 广告区域 -->
  24 + <!-- 工具展示区 -->
  25 + <section class="md:mb-12 mb-6">
  26 + <!-- 推荐工具区 -->
  27 + <HomeRecommend
  28 + :recommendList="recommendList"
  29 + navTitle="推荐工具"
  30 + navIcon="star"
  31 + />
  32 +
  33 + <div v-for="appItem in appList" class="md:mb-12 mb-6">
  34 + <!-- 分类导航及内容 -->
  35 + <HomeContent :appData="appItem" />
  36 + </div>
  37 + </section>
  38 + </main>
  39 + </div>
  40 +</template>
  1 +<template>
  2 + <div class="p-10" style="min-height: calc(100vh - 320px)">
  3 + <HomeRecommend :recommendList="list" :navTitle="keyword" navIcon="tag" />
  4 + <el-pagination
  5 + background
  6 + layout="prev, pager, next"
  7 + :hide-on-single-page="true"
  8 + v-model:page-size="params.pageSize"
  9 + v-model:current-page="params.pageNum"
  10 + :total="total"
  11 + @current-change="onPageChange"
  12 + />
  13 + </div>
  14 +</template>
  15 +
  16 +<script lang="ts" setup>
  17 +const route = useRoute();
  18 +const router = useRouter();
  19 +
  20 +const { keyword } = route.query as { keyword: string };
  21 +const list = ref<any[]>([]);
  22 +const total = ref<number>(0);
  23 +const params = ref<any>({
  24 + pageNum: 1,
  25 + pageSize: 10,
  26 + title: keyword,
  27 +});
  28 +
  29 +function onPageChange(pageNum: number) {
  30 + router.push({
  31 + path: route.path + "/page/" + pageNum,
  32 + query: {
  33 + keyword: keyword,
  34 + },
  35 + });
  36 +}
  37 +
  38 +const { data } = await useFetch(
  39 + "http://aitoolht.crgx.net/dh/app/listFrontApp",
  40 + {
  41 + method: "get",
  42 + params: params.value,
  43 + }
  44 +);
  45 +list.value = data.value.rows;
  46 +total.value = data.value.total;
  47 +</script>
  1 +<template>
  2 + <div class="p-10">
  3 + <!-- 推荐工具区 -->
  4 + <HomeRecommend
  5 + :recommendList="list"
  6 + :navTitle="keyword"
  7 + navIcon="tag"
  8 + :navTitleWidth="120.5"
  9 + />
  10 + <el-pagination
  11 + background
  12 + layout="prev, pager, next"
  13 + :hide-on-single-page="true"
  14 + v-model:page-size="params.pageSize"
  15 + v-model:current-page="params.pageNum"
  16 + :total="total"
  17 + @current-change="onPageChange"
  18 + />
  19 + </div>
  20 +</template>
  21 +
  22 +<script lang="ts" setup>
  23 +import { ref } from "vue";
  24 +const route = useRoute();
  25 +const router = useRouter();
  26 +const { pageNum } = route.params;
  27 +const { keyword } = route.query as { keyword: string };
  28 +const list = ref<any[]>([]);
  29 +const total = ref<number>(0);
  30 +const params = ref<any>({
  31 + pageNum: Number(pageNum),
  32 + pageSize: 10,
  33 + title: keyword,
  34 +});
  35 +
  36 +function onPageChange(pageNum: number) {
  37 + if (pageNum === 1) {
  38 + router.push({
  39 + path: "/search",
  40 + query: {
  41 + keyword: keyword,
  42 + },
  43 + });
  44 + } else if (pageNum > 1) {
  45 + router.push({
  46 + path: route.path + "/page/" + pageNum,
  47 + query: {
  48 + keyword: keyword,
  49 + },
  50 + });
  51 + }
  52 +}
  53 +
  54 +const { data } = await useFetch(
  55 + "http://aitoolht.crgx.net/dh/app/listFrontApp",
  56 + {
  57 + method: "get",
  58 + params: params.value,
  59 + }
  60 +);
  61 +list.value = data.value.rows;
  62 +total.value = data.value.total;
  63 +</script>
不能预览此文件类型
  1 +{
  2 + "extends": "../.nuxt/tsconfig.server.json"
  3 +}
  1 +{
  2 + // https://nuxt.com/docs/guide/concepts/typescript
  3 + "extends": "./.nuxt/tsconfig.json"
  4 +}