|
- <template>
- <view class="toolbox">
- <ai-nav-title title="AI在线-解放你的生产力" />
-
- <scroll-view
- class="page-scroll"
- scroll-y
- refresher-enabled
- :refresher-triggered="refreshing"
- @refresherrefresh="onRefresh"
- >
- <view class="toolbox-content">
- <swiper v-if="banners.length" class="hero" circular autoplay :interval="4000" @change="onBannerChange">
- <swiper-item v-for="(banner, i) in banners" :key="i" @click="onTool(banner)">
- <image class="hero-img" :src="banner.image" mode="aspectFill" />
- </swiper-item>
- </swiper>
- <view v-if="banners.length" class="hero-indicator">
- <view
- v-for="(banner, i) in banners"
- :key="banner.id || i"
- class="indicator-bar"
- :class="{ active: i === bannerIndex }"
- ></view>
- </view>
-
- <view v-if="recommends.length" class="recommend">
- <view v-for="item in recommends" :key="item.id || item.type" class="recommend-card" @click="onTool(item)">
- <image class="recommend-img" :src="item.image" mode="aspectFill" />
- </view>
- </view>
-
- <view class="tool-list">
- <view v-for="item in apps" :key="item.id || item.type || item.title" class="tool-row" @click="onTool(item)">
- <view class="tool-icon-wrap">
- <image class="tool-icon" :src="item.icon" mode="aspectFill" />
- </view>
- <view class="tool-main">
- <text class="tool-name">{{ item.name }}</text>
- <text class="tool-desc">{{ item.desc }}</text>
- </view>
- <text class="chevron">›</text>
- </view>
- </view>
- </view>
- </scroll-view>
- </view>
- </template>
-
- <script setup>
- import { ref } from 'vue'
- import { onLoad } from '@dcloudio/uni-app'
- import { commonApi } from '@/api/index.js'
- import { useApp } from '@/utils/useApp.js'
-
- const { toast } = useApp()
- const refreshing = ref(false)
- const bannerIndex = ref(0)
- const AI_CDN_BASE = 'https://cdn.aionline.cc/ai'
-
- const defaultBanners = []
-
- const defaultRecommends = [
- {
- name: 'AI动漫头像',
- title: 'AI动漫头像',
- image: `${AI_CDN_BASE}/toolbox/home_card_avatar.png`,
- link_type: 'app',
- type: 'anime-avatar'
- },
- {
- name: 'AI取名',
- title: 'AI取名',
- image: `${AI_CDN_BASE}/toolbox/home_card_name.png`,
- link_type: 'app',
- type: 'ai-name'
- }
- ]
-
- const defaultApps = [
- {
- icon: `${AI_CDN_BASE}/toolbox/tool_id_photo.png`,
- name: 'AI证件照/签证照',
- title: 'AI证件照/签证照',
- desc: '各类规格证件照、免冠照、换底色换正装',
- subtitle: '各类规格证件照、免冠照、换底色换正装',
- link_type: 'app',
- type: 'id-photo'
- },
- {
- icon: `${AI_CDN_BASE}/toolbox/tool_portrait.png`,
- name: 'AI形象照/职业照/写真',
- title: 'AI形象照/职业照/写真',
- desc: '上传自拍生成职业照、艺术写真、古风',
- subtitle: '上传自拍生成职业照、艺术写真、古风',
- link_type: 'app',
- type: 'portrait-photo'
- },
- {
- icon: `${AI_CDN_BASE}/toolbox/tool_id_photo.png`,
- name: '老照片上色',
- title: '老照片上色',
- desc: '模糊破损老照片修复、黑白照片自然上色',
- subtitle: '模糊破损老照片修复、黑白照片自然上色',
- link_type: 'app',
- type: 'old-photo'
- }
- ]
-
- const banners = ref(defaultBanners)
- const recommends = ref(defaultRecommends)
- const apps = ref(defaultApps)
-
- onLoad(() => {
- loadHomeConfig()
- })
-
- async function loadHomeConfig() {
- try {
- const res = await commonApi.getHomeConfig()
- const data = res.data || {}
- const nextBanners = normalizeBanners(data.banners)
- const nextRecommends = normalizeApps(data.recommends)
- const nextApps = normalizeApps(data.apps)
- if (nextBanners.length) banners.value = nextBanners
- if (nextRecommends.length) recommends.value = nextRecommends
- if (nextApps.length) apps.value = nextApps
- bannerIndex.value = 0
- } catch (e) {}
- }
-
- function normalizeBanners(list) {
- if (!Array.isArray(list)) return []
- return list
- .filter(item => item && item.image)
- .map(item => ({
- ...item,
- name: item.title,
- type: item.type || item.app_key || (item.link_type === 'app' ? item.link_url : '')
- }))
- }
-
- function normalizeApps(list) {
- if (!Array.isArray(list)) return []
- return list
- .filter(item => item && (item.image || item.icon))
- .map(item => ({
- ...item,
- name: item.name || item.title,
- desc: item.desc || item.subtitle,
- type: item.type || item.app_key || (item.link_type === 'app' ? item.link_url : '')
- }))
- }
-
- function onTool(item) {
- if (!item || item.link_type === 'none') return
- if (item.soon) {
- if (item && (item.name || item.title)) toast(`${item.name || item.title} 即将上线`)
- return
- }
-
- if (item.link_type === 'page' && item.link_url) {
- uni.navigateTo({ url: item.link_url })
- return
- }
-
- if (item.link_type === 'web' && item.link_url) {
- uni.navigateTo({ url: `/pages/webview/webview?url=${encodeURIComponent(item.link_url)}&title=${encodeURIComponent(item.name || item.title || '')}` })
- return
- }
-
- if (!item.type) {
- toast(`${item.name || item.title} 即将上线`)
- return
- }
- const routeMap = {
- 'anime-avatar': '/pages/tool/anime-avatar',
- 'ai-name': '/pages/tool/ai-name',
- 'id-photo': '/pages/tool/id-photo',
- 'portrait-photo': '/pages/tool/portrait-photo',
- 'old-photo': '/pages/tool/old-photo'
- }
- if (routeMap[item.type]) {
- uni.navigateTo({ url: routeMap[item.type] })
- return
- }
- uni.navigateTo({ url: `/pages/tool/detail?type=${item.type}&title=${encodeURIComponent(item.name || item.title)}` })
- }
-
- function onBannerChange(e) {
- bannerIndex.value = e.detail.current || 0
- }
-
- function onRefresh() {
- refreshing.value = true
- loadHomeConfig().finally(() => {
- refreshing.value = false
- })
- }
- </script>
-
- <style lang="scss" scoped>
- .toolbox {
- height: 100vh;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- background: #f3f3f3;
- }
-
- .page-scroll {
- flex: 1;
- height: 0;
- }
-
- .toolbox-content {
- min-height: 100vh;
- padding: 30rpx 30rpx 96rpx;
- box-sizing: border-box;
- background: #f3f3f3;
- }
-
- .hero {
- width: 100%;
- height: 341rpx;
- border-radius: 21rpx;
- overflow: hidden;
- }
-
- .hero-img {
- display: block;
- width: 100%;
- height: 100%;
- }
-
- .hero-indicator {
- height: 8rpx;
- margin-top: -30rpx;
- margin-bottom: 22rpx;
- position: relative;
- z-index: 2;
- display: flex;
- justify-content: center;
- gap: 8rpx;
- pointer-events: none;
- }
-
- .indicator-bar {
- width: 22rpx;
- height: 6rpx;
- border-radius: 999rpx;
- background: rgba(255, 255, 255, 0.5);
- transition: width 0.2s ease, background 0.2s ease;
- }
-
- .indicator-bar.active {
- width: 50rpx;
- background: #ffffff;
- }
-
- .recommend {
- display: flex;
- gap: 16rpx;
- margin-top: 22rpx;
- }
-
- .recommend-card {
- flex: 1;
- min-width: 0;
- height: 195rpx;
- border-radius: 19rpx;
- overflow: visible;
- }
-
- .recommend-img {
- width: 100%;
- height: 195rpx;
- border-radius: 19rpx;
- display: block;
- }
-
- .tool-list {
- margin-top: 24rpx;
- }
-
- .tool-row {
- height: 169rpx;
- padding: 0 40rpx 0 44rpx;
- border-radius: 21rpx;
- background: #ffffff;
- display: flex;
- align-items: center;
- box-sizing: border-box;
-
- & + .tool-row {
- margin-top: 19rpx;
- }
- }
-
- .tool-icon-wrap {
- width: 91rpx;
- height: 91rpx;
- flex-shrink: 0;
- border-radius: 12rpx;
- overflow: hidden;
- background: #edf7ff;
- }
-
- .tool-icon {
- width: 100%;
- height: 100%;
- display: block;
- }
-
- .tool-main {
- min-width: 0;
- flex: 1;
- margin-left: 35rpx;
- display: flex;
- flex-direction: column;
- }
-
- .tool-name {
- color: #333333;
- font-size: 32rpx;
- font-weight: 400;
- line-height: 1.2;
- }
-
- .tool-desc {
- margin-top: 18rpx;
- color: #999999;
- font-size: 25rpx;
- line-height: 1.2;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .chevron {
- margin-left: 16rpx;
- color: #c9c9c9;
- font-size: 54rpx;
- font-weight: 200;
- line-height: 1;
- }
- </style>
|