正在显示
33 个修改的文件
包含
1973 行增加
和
0 行删除
.gitignore
0 → 100644
README.en.md
0 → 100644
| 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/) |
README.md
0 → 100644
| 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/) |
app.vue
0 → 100644
| 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> |
assets/iconfonts/iconfont.css
0 → 100644
| 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 | + |
assets/iconfonts/iconfont.js
0 → 100644
此 diff 太大无法显示。
assets/iconfonts/iconfont.ttf
0 → 100644
不能预览此文件类型
assets/iconfonts/iconfont.woff
0 → 100644
不能预览此文件类型
assets/iconfonts/iconfont.woff2
0 → 100644
不能预览此文件类型
components/App/Footer.vue
0 → 100644
| 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>© {{ 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> |
components/App/Header.vue
0 → 100644
| 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> |
components/App/Sidebar.vue
0 → 100644
| 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> |
components/Home/Banner.vue
0 → 100644
| 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> |
components/Home/Content/HasChildren.vue
0 → 100644
| 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 | + >查看更多 >></a | ||
| 54 | + > | ||
| 55 | + <a | ||
| 56 | + class="md:hidden text-xs ml-2 text-[#282a2d] hover:text-[#5961f9]" | ||
| 57 | + :href="`/category/${childAlias}`" | ||
| 58 | + >>></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> |
components/Home/Content/NoChildren.vue
0 → 100644
| 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 | + >查看更多 >></a | ||
| 18 | + > | ||
| 19 | + <a | ||
| 20 | + class="md:hidden text-xs ml-2 text-[#282a2d] hover:text-[#5961f9]" | ||
| 21 | + :href="`/category/${childData.alias}`" | ||
| 22 | + >>></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> |
components/Home/Content/index.vue
0 → 100644
components/Home/Recommend.vue
0 → 100644
| 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> |
components/MyMenu/index.vue
0 → 100644
| 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> |
components/MySearch/index.vue
0 → 100644
| 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> |
layouts/default.vue
0 → 100644
nuxt.config.ts
0 → 100644
| 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 | +}) |
package-lock.json
0 → 100644
此 diff 太大无法显示。
package.json
0 → 100644
| 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 | +} |
pages/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 | + <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> |
pages/category/[name]/page/[pageNum].vue
0 → 100644
| 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> |
pages/details/[id].vue
0 → 100644
| 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> |
pages/index.vue
0 → 100644
| 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> |
pages/search/index.vue
0 → 100644
| 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> |
pages/search/page/[pageNum].vue
0 → 100644
| 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> |
public/favicon.ico
0 → 100644
不能预览此文件类型
server/tsconfig.json
0 → 100644
tsconfig.json
0 → 100644
-
请 注册 或 登录 后发表评论