Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 

343 rader
7.8 KiB

  1. <template>
  2. <view class="toolbox">
  3. <ai-nav-title title="AI在线-解放你的生产力" />
  4. <scroll-view
  5. class="page-scroll"
  6. scroll-y
  7. refresher-enabled
  8. :refresher-triggered="refreshing"
  9. @refresherrefresh="onRefresh"
  10. >
  11. <view class="toolbox-content">
  12. <swiper class="hero" circular autoplay :interval="4000" @change="onBannerChange">
  13. <swiper-item v-for="(banner, i) in banners" :key="i" @click="onTool(banner)">
  14. <image class="hero-img" :src="banner.image" mode="aspectFill" />
  15. </swiper-item>
  16. </swiper>
  17. <view v-if="banners.length" class="hero-indicator">
  18. <view
  19. v-for="(banner, i) in banners"
  20. :key="banner.id || i"
  21. class="indicator-bar"
  22. :class="{ active: i === bannerIndex }"
  23. ></view>
  24. </view>
  25. <view v-if="recommends.length" class="recommend">
  26. <view v-for="item in recommends" :key="item.id || item.type" class="recommend-card" @click="onTool(item)">
  27. <image class="recommend-img" :src="item.image" mode="aspectFill" />
  28. </view>
  29. </view>
  30. <view class="tool-list">
  31. <view v-for="item in apps" :key="item.id || item.type || item.title" class="tool-row" @click="onTool(item)">
  32. <image class="tool-icon" :src="item.icon" mode="aspectFit" />
  33. <view class="tool-main">
  34. <text class="tool-name">{{ item.name }}</text>
  35. <text class="tool-desc">{{ item.desc }}</text>
  36. </view>
  37. <text class="chevron">›</text>
  38. </view>
  39. </view>
  40. </view>
  41. </scroll-view>
  42. </view>
  43. </template>
  44. <script setup>
  45. import { ref } from 'vue'
  46. import { onLoad } from '@dcloudio/uni-app'
  47. import { commonApi } from '@/api/index.js'
  48. import { useApp } from '@/utils/useApp.js'
  49. const { toast } = useApp()
  50. const refreshing = ref(false)
  51. const bannerIndex = ref(0)
  52. const defaultBanners = [
  53. {
  54. image: '/static/toolbox/home_banner_note17.png',
  55. title: 'Note 17 系列',
  56. link_type: 'none',
  57. type: ''
  58. }
  59. ]
  60. const defaultRecommends = [
  61. {
  62. name: 'AI动漫头像',
  63. title: 'AI动漫头像',
  64. image: '/static/toolbox/home_card_avatar.png',
  65. link_type: 'app',
  66. type: 'anime-avatar'
  67. },
  68. {
  69. name: 'AI取名',
  70. title: 'AI取名',
  71. image: '/static/toolbox/home_card_name.png',
  72. link_type: 'app',
  73. type: 'ai-name'
  74. }
  75. ]
  76. const defaultApps = [
  77. {
  78. icon: '/static/toolbox/tool_id_photo.png',
  79. name: 'AI证件照/签证照',
  80. title: 'AI证件照/签证照',
  81. desc: '各类规格证件照、免冠照、换底色换正装',
  82. subtitle: '各类规格证件照、免冠照、换底色换正装',
  83. link_type: 'app',
  84. type: 'id-photo'
  85. },
  86. {
  87. icon: '/static/toolbox/tool_portrait.png',
  88. name: 'AI形象照/职业照/写真',
  89. title: 'AI形象照/职业照/写真',
  90. desc: '上传自拍生成职业照、艺术写真、古风',
  91. subtitle: '上传自拍生成职业照、艺术写真、古风',
  92. link_type: 'app',
  93. type: 'portrait-photo'
  94. },
  95. {
  96. icon: '/static/toolbox/tool_id_photo.png',
  97. name: '老照片上色',
  98. title: '老照片上色',
  99. desc: '模糊破损老照片修复、黑白照片自然上色',
  100. subtitle: '模糊破损老照片修复、黑白照片自然上色',
  101. link_type: 'app',
  102. type: 'old-photo'
  103. }
  104. ]
  105. const banners = ref(defaultBanners)
  106. const recommends = ref(defaultRecommends)
  107. const apps = ref(defaultApps)
  108. onLoad(() => {
  109. loadHomeConfig()
  110. })
  111. async function loadHomeConfig() {
  112. try {
  113. const res = await commonApi.getHomeConfig()
  114. const data = res.data || {}
  115. const nextBanners = normalizeBanners(data.banners)
  116. const nextRecommends = normalizeApps(data.recommends)
  117. const nextApps = normalizeApps(data.apps)
  118. if (nextBanners.length) banners.value = nextBanners
  119. if (nextRecommends.length) recommends.value = nextRecommends
  120. if (nextApps.length) apps.value = nextApps
  121. bannerIndex.value = 0
  122. } catch (e) {}
  123. }
  124. function normalizeBanners(list) {
  125. if (!Array.isArray(list)) return []
  126. return list
  127. .filter(item => item && item.image)
  128. .map(item => ({
  129. ...item,
  130. name: item.title,
  131. type: item.type || item.app_key || (item.link_type === 'app' ? item.link_url : '')
  132. }))
  133. }
  134. function normalizeApps(list) {
  135. if (!Array.isArray(list)) return []
  136. return list
  137. .filter(item => item && (item.image || item.icon))
  138. .map(item => ({
  139. ...item,
  140. name: item.name || item.title,
  141. desc: item.desc || item.subtitle,
  142. type: item.type || item.app_key || (item.link_type === 'app' ? item.link_url : '')
  143. }))
  144. }
  145. function onTool(item) {
  146. if (!item || item.link_type === 'none') return
  147. if (item.soon) {
  148. if (item && (item.name || item.title)) toast(`${item.name || item.title} 即将上线`)
  149. return
  150. }
  151. if (item.link_type === 'page' && item.link_url) {
  152. uni.navigateTo({ url: item.link_url })
  153. return
  154. }
  155. if (item.link_type === 'web' && item.link_url) {
  156. uni.navigateTo({ url: `/pages/webview/webview?url=${encodeURIComponent(item.link_url)}&title=${encodeURIComponent(item.name || item.title || '')}` })
  157. return
  158. }
  159. if (!item.type) {
  160. toast(`${item.name || item.title} 即将上线`)
  161. return
  162. }
  163. const routeMap = {
  164. 'anime-avatar': '/pages/tool/anime-avatar',
  165. 'ai-name': '/pages/tool/ai-name',
  166. 'id-photo': '/pages/tool/id-photo',
  167. 'portrait-photo': '/pages/tool/portrait-photo',
  168. 'old-photo': '/pages/tool/old-photo'
  169. }
  170. if (routeMap[item.type]) {
  171. uni.navigateTo({ url: routeMap[item.type] })
  172. return
  173. }
  174. uni.navigateTo({ url: `/pages/tool/detail?type=${item.type}&title=${encodeURIComponent(item.name || item.title)}` })
  175. }
  176. function onBannerChange(e) {
  177. bannerIndex.value = e.detail.current || 0
  178. }
  179. function onRefresh() {
  180. refreshing.value = true
  181. loadHomeConfig().finally(() => {
  182. refreshing.value = false
  183. })
  184. }
  185. </script>
  186. <style lang="scss" scoped>
  187. .toolbox {
  188. height: 100vh;
  189. display: flex;
  190. flex-direction: column;
  191. overflow: hidden;
  192. background: #f3f3f3;
  193. }
  194. .page-scroll {
  195. flex: 1;
  196. height: 0;
  197. }
  198. .toolbox-content {
  199. min-height: 100vh;
  200. padding: 30rpx 30rpx 96rpx;
  201. box-sizing: border-box;
  202. background: #f3f3f3;
  203. }
  204. .hero {
  205. width: 100%;
  206. height: 341rpx;
  207. border-radius: 21rpx;
  208. overflow: hidden;
  209. }
  210. .hero-img {
  211. display: block;
  212. width: 100%;
  213. height: 100%;
  214. }
  215. .hero-indicator {
  216. height: 8rpx;
  217. margin-top: -30rpx;
  218. margin-bottom: 22rpx;
  219. position: relative;
  220. z-index: 2;
  221. display: flex;
  222. justify-content: center;
  223. gap: 8rpx;
  224. pointer-events: none;
  225. }
  226. .indicator-bar {
  227. width: 22rpx;
  228. height: 6rpx;
  229. border-radius: 999rpx;
  230. background: rgba(255, 255, 255, 0.5);
  231. transition: width 0.2s ease, background 0.2s ease;
  232. }
  233. .indicator-bar.active {
  234. width: 50rpx;
  235. background: #ffffff;
  236. }
  237. .recommend {
  238. display: flex;
  239. gap: 16rpx;
  240. margin-top: 22rpx;
  241. }
  242. .recommend-card {
  243. flex: 1;
  244. min-width: 0;
  245. height: 195rpx;
  246. border-radius: 19rpx;
  247. overflow: visible;
  248. }
  249. .recommend-img {
  250. width: 100%;
  251. height: 195rpx;
  252. border-radius: 19rpx;
  253. display: block;
  254. }
  255. .tool-list {
  256. margin-top: 24rpx;
  257. }
  258. .tool-row {
  259. height: 169rpx;
  260. padding: 0 40rpx 0 44rpx;
  261. border-radius: 21rpx;
  262. background: #ffffff;
  263. display: flex;
  264. align-items: center;
  265. box-sizing: border-box;
  266. & + .tool-row {
  267. margin-top: 19rpx;
  268. }
  269. }
  270. .tool-icon {
  271. width: 91rpx;
  272. height: 91rpx;
  273. flex-shrink: 0;
  274. }
  275. .tool-main {
  276. min-width: 0;
  277. flex: 1;
  278. margin-left: 35rpx;
  279. display: flex;
  280. flex-direction: column;
  281. }
  282. .tool-name {
  283. color: #333333;
  284. font-size: 32rpx;
  285. font-weight: 400;
  286. line-height: 1.2;
  287. }
  288. .tool-desc {
  289. margin-top: 18rpx;
  290. color: #999999;
  291. font-size: 25rpx;
  292. line-height: 1.2;
  293. white-space: nowrap;
  294. overflow: hidden;
  295. text-overflow: ellipsis;
  296. }
  297. .chevron {
  298. margin-left: 16rpx;
  299. color: #c9c9c9;
  300. font-size: 54rpx;
  301. font-weight: 200;
  302. line-height: 1;
  303. }
  304. </style>