Ai_GirlFriend/xuniYou/pages/index/index.vue
2026-02-03 18:00:47 +08:00

5661 lines
135 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view>
<view>
<view v-show="getBobbiesList.reg_step == 1 || getBobbiesList.reg_step == 2 || getBobbiesList.reg_step == 3">
<uni-nav-bar fixed statusBar background-color="transparent" :border="false" title="定制专属女友"></uni-nav-bar>
<view class="back"></view>
<view class="body">
<view class="list">
<image class="list_logo" v-if="getBobbiesList.reg_step == 1"
src="https://nvlovers.oss-cn-qingdao.aliyuncs.com/uploads/20251231/a6d2dae70029b6465ea1f1f605f77258.png"
mode="aspectFill"></image>
<image class="list_logo" v-if="getBobbiesList.reg_step == 2"
src="https://nvlovers.oss-cn-qingdao.aliyuncs.com/uploads/20251231/69c72922c0e815086a30c370e7dbb42f.png"
mode="aspectFill"></image>
<image class="list_logo" v-if="getBobbiesList.reg_step == 3"
src="https://nvlovers.oss-cn-qingdao.aliyuncs.com/uploads/20251231/69c72922c0e815086a30c370e7dbb42f.png"
mode="aspectFill"></image>
<view class="list_content faj" v-if="getBobbiesList.reg_step == 1" @click="tocreate">
<view class="list_title">完善个人资料</view>
<image class="list_next" src="/static/images/index_next.png" mode="aspectFill"></image>
</view>
<view class="list_content faj" v-if="getBobbiesList.reg_step == 2" @click="tocreateLover">
<view class="list_title">完善恋人资料
</view>
<image class="list_next" src="/static/images/index_next.png" mode="aspectFill"></image>
</view>
<view class="list_content faj" v-if="getBobbiesList.reg_step == 3" @click="togenerate">
<view class="list_title">生成专属恋人
</view>
<image class="list_next" src="/static/images/index_next.png" mode="aspectFill"></image>
</view>
<view class="list_module faj">
<view class="list_item" :class="{ 'list_active': getBobbiesList.reg_step == 1 }"></view>
<view class="list_item" :class="{ 'list_active': getBobbiesList.reg_step == 2 }"></view>
<view class="list_item" :class="{ 'list_active': getBobbiesList.reg_step == 3 }"></view>
</view>
</view>
</view>
</view>
<view v-show="getBobbiesList.reg_step == 4">
<!-- Tab 栏 - 固定在顶部 -->
<view class="tab-container" v-if="getBobbiesList.reg_step == 4">
<scroll-view class="tab-scroll" scroll-x="true" show-scrollbar="false" :scroll-into-view="tabIntoView" scroll-with-animation="true">
<view class="tab-list">
<view
v-for="(tab, index) in tabs"
:key="index"
:id="'tab-' + index"
class="tab-item"
:class="{ 'active': currentTab === index }"
@click="switchTab(index)">
<text class="tab-name">{{ tab.name }}</text>
</view>
</view>
</scroll-view>
</view>
<!-- Swiper 内容区域 - 自定义细线指示器 -->
<swiper
v-if="getBobbiesList.reg_step == 4"
class="swiper-container"
:current="currentTab"
@change="onSwiperChange"
:duration="300"
:indicator-dots="false">
<!-- 商城页面 (index 0) - 精美设计 -->
<swiper-item>
<view class="swiper-content shop-page">
<!-- 全屏商城背景 -->
<view class="shop-hero" @click="openShopLink">
<!-- 背景渐变 -->
<view class="shop-hero-bg"></view>
<!-- 装饰圆圈 -->
<view class="shop-decoration">
<view class="shop-circle shop-circle-1"></view>
<view class="shop-circle shop-circle-2"></view>
<view class="shop-circle shop-circle-3"></view>
<view class="shop-circle shop-circle-4"></view>
</view>
<!-- 内容区域 -->
<view class="shop-hero-content">
<!-- 顶部标签 -->
<view class="shop-hot-badge">
<text class="shop-hot-icon">💎</text>
<text class="shop-hot-text">精品商城</text>
</view>
<!-- 商城图标 -->
<view class="shop-icon-wrapper">
<image class="shop-main-icon" src="/static/images/member_logo.png" mode="aspectFit"></image>
<view class="shop-icon-glow"></view>
</view>
<!-- 标题 -->
<view class="shop-hero-title">超值优惠</view>
<view class="shop-hero-subtitle">海量道具 · 限时特惠 · 每日上新</view>
<!-- 特色标签 -->
<view class="shop-features-grid">
<view class="shop-feature-card">
<text class="shop-feature-emoji">🎁</text>
<text class="shop-feature-label">新人礼包</text>
</view>
<view class="shop-feature-card">
<text class="shop-feature-emoji">⚡</text>
<text class="shop-feature-label">限时秒杀</text>
</view>
<view class="shop-feature-card">
<text class="shop-feature-emoji">💰</text>
<text class="shop-feature-label">超值折扣</text>
</view>
<view class="shop-feature-card">
<text class="shop-feature-emoji">🔥</text>
<text class="shop-feature-label">热门推荐</text>
</view>
</view>
<!-- 进入按钮 -->
<view class="shop-enter-button">
<text class="shop-enter-text">立即进入商城</text>
<text class="shop-enter-arrow">→</text>
</view>
<!-- 提示文字 -->
<view class="shop-hint">
<text class="shop-hint-icon">✨</text>
<text class="shop-hint-text">点击任意位置进入商城</text>
</view>
</view>
</view>
</view>
</swiper-item>
<!-- 首页内容 (index 1) -->
<swiper-item>
<view class="home-page">
<!-- 顶部操作栏 -->
<view class="home-header">
<view class="home-header-btn" @click="toreplacement">
<image class="home-header-icon" src="/static/images/replacement_switch.png" mode="aspectFit"></image>
<text class="home-header-text">装扮设置</text>
</view>
<view class="home-header-spacer"></view>
<view class="home-header-btn" @click="todynamics">
<image class="home-header-icon" src="/static/images/dynamics_logo.png" mode="aspectFit"></image>
<text class="home-header-text">朋友圈</text>
</view>
<view class="home-header-btn" @click="tointimacy">
<image class="home-header-icon" src="/static/images/index_intimate.png" mode="aspectFit"></image>
<text class="home-header-text">邀请入驻</text>
</view>
</view>
<!-- 选择互动对象 -->
<view class="home-title">
<text class="home-title-icon">✦</text>
<text class="home-title-text">选择一位互动对象</text>
<text class="home-title-icon">✦</text>
<text class="home-title-more" @click="toreplacement">搜索更多</text>
</view>
<!-- 主卡片区域 -->
<view class="home-card">
<view class="home-card-inner">
<!-- 女友名字 -->
<view class="home-card-name">{{ loverBasicList && loverBasicList.name ? loverBasicList.name : '简寻' }}</view>
<!-- 女友图片 -->
<image
class="home-card-image"
:src="loverBasicList && loverBasicList.image_url ? loverBasicList.image_url : 'https://nvlovers.oss-cn-qingdao.aliyuncs.com/uploads/20251226/008a06f2c3fccdac714f25e6577ccdc4.png'"
mode="aspectFill">
</image>
<!-- 性格标签 -->
<view class="home-card-tags">
<view class="home-card-tag">多面伪神</view>
<view class="home-card-tag">甜蜜贴心</view>
</view>
<!-- 对话气泡 -->
<view class="home-card-bubble">
<text class="home-card-bubble-text">靠近点,再近点…你就能看清我眼里藏着什么了。</text>
</view>
<!-- 底部操作按钮 -->
<view class="home-card-actions">
<view class="home-card-btn home-card-btn-home" @click="tointimacy">
<image class="home-card-btn-icon" src="/static/images/index_intimate.png" mode="aspectFit"></image>
</view>
<view class="home-card-btn home-card-btn-chat" @click="tochat">
<text class="home-card-btn-text">和TA聊天</text>
</view>
</view>
</view>
</view>
<!-- 底部头像轮播 -->
<view class="home-avatars">
<view
class="home-avatar-item"
v-for="(look, index) in homeLooksList"
:key="look.id"
:class="{ 'home-avatar-active': selectedLookId === look.id }"
@click="switchLook(look)">
<image
class="home-avatar-img"
:src="look.image_url"
mode="aspectFill">
</image>
</view>
<!-- 如果形象栏为空,显示默认头像 -->
<view class="home-avatar-item" v-if="!homeLooksList || homeLooksList.length === 0">
<image
class="home-avatar-img"
:src="loverBasicList && loverBasicList.image_url ? loverBasicList.image_url : 'https://nvlovers.oss-cn-qingdao.aliyuncs.com/uploads/20251226/008a06f2c3fccdac714f25e6577ccdc4.png'"
mode="aspectFill">
</image>
</view>
</view>
<!-- 商城入口横幅 -->
<view class="home-shop-banner" @click="openShopLink">
<!-- 背景装饰 -->
<view class="home-shop-bg">
<view class="home-shop-circle home-shop-circle-1"></view>
<view class="home-shop-circle home-shop-circle-2"></view>
<view class="home-shop-circle home-shop-circle-3"></view>
</view>
<!-- 内容区域 -->
<view class="home-shop-content">
<!-- 左侧图标和文字 -->
<view class="home-shop-left">
<view class="home-shop-icon-wrapper">
<image class="home-shop-icon" src="/static/images/member_logo.png" mode="aspectFit"></image>
<view class="home-shop-badge">HOT</view>
</view>
<view class="home-shop-text">
<view class="home-shop-title">
<text class="home-shop-title-main">精品商城</text>
<text class="home-shop-title-emoji">🛒</text>
</view>
<view class="home-shop-desc">海量道具 · 超值优惠</view>
</view>
</view>
<!-- 右侧按钮 -->
<view class="home-shop-button">
<text class="home-shop-button-text">立即前往</text>
<text class="home-shop-button-arrow">→</text>
</view>
</view>
<!-- 闪光效果 -->
<view class="home-shop-shine"></view>
</view>
<!-- 好感度信息(可选,放在底部) -->
<view class="home-intimacy" @click="tointimacy">
<view class="home-intimacy-level">Lv.{{ getBobbiesList.level ? getBobbiesList.level : 0 }}</view>
<view class="home-intimacy-bar">
<view class="home-intimacy-progress" :style="{ width: calculateProgressPercent() + '%' }"></view>
</view>
<view class="home-intimacy-text">{{ getBobbiesList.intimacy ? getBobbiesList.intimacy : 0 }}/{{ getBobbiesList.next_level_intimacy ? getBobbiesList.next_level_intimacy : 0 }}</view>
</view>
</view>
</swiper-item>
<!-- 聊天页面 (index 2) - 嵌入完整聊天功能 -->
<swiper-item>
<view class="chat-swiper-page">
<!-- 聊天消息列表 -->
<scroll-view
class="chat-message-scroll"
scroll-y="true"
:scroll-into-view="chatIntoView"
scroll-with-animation="true">
<view class="chat-message-list">
<view
v-for="(msg, index) in chatMessages"
:key="index"
class="chat-message-item"
:class="msg.role === 'user' ? 'message-right' : 'message-left'">
<image
class="chat-avatar"
:src="msg.role === 'user' ? chatUserAvatar : chatLoverAvatar"
mode="aspectFill">
</image>
<view class="chat-message-bubble">
<text class="chat-message-text">{{ msg.content }}</text>
</view>
</view>
<view id="chat-bottom"></view>
</view>
</scroll-view>
<!-- 底部输入框 -->
<view class="chat-input-bar">
<input
class="chat-message-input"
v-model="chatInputText"
placeholder="输入消息..."
confirm-type="send"
:adjust-position="true"
:hold-keyboard="false"
@confirm="sendChatMessage"
@blur="onInputBlur"
/>
<view class="chat-send-btn" @click="sendChatMessage">
<text>发送</text>
</view>
</view>
</view>
</swiper-item>
<!-- 唱歌页面 (index 3) -->
<swiper-item>
<view class="sing-page">
<!-- Tab 切换 -->
<view class="sing-tabs">
<view
class="sing-tab-item"
:class="{ 'sing-tab-active': singCurrentTab === 'songs' }"
@click="switchSingTab('songs')">
<text>系统歌曲</text>
</view>
<view
class="sing-tab-item"
:class="{ 'sing-tab-active': singCurrentTab === 'library' }"
@click="switchSingTab('library')">
<text>音乐库</text>
</view>
<view
class="sing-tab-item"
:class="{ 'sing-tab-active': singCurrentTab === 'history' }"
@click="switchSingTab('history')">
<text>历史记录</text>
</view>
</view>
<scroll-view class="sing-scroll" scroll-y="true">
<!-- 系统歌曲列表 -->
<view v-if="singCurrentTab === 'songs'" class="sing-content">
<view class="section-title">选择歌曲</view>
<view class="song-list">
<view class="song-item" v-for="(item,index) in singSongsList" :key="index" @click="selectSongDirect(item)">
<view class="song-info">
<text class="song-title">{{item.title}}</text>
</view>
<image class="song-icon" src="/static/images/chat_a6.png" mode="widthFix"></image>
</view>
</view>
</view>
<!-- 音乐库 -->
<view v-if="singCurrentTab === 'library'" class="sing-content">
<!-- 添加音乐按钮 -->
<view class="music-add-btns">
<view class="music-add-btn" @click="showAddMusicModal">
<text class="music-add-icon"></text>
<text class="music-add-text">添加音乐</text>
</view>
</view>
<!-- 音乐库列表 -->
<view class="music-library-list">
<view class="music-library-item" v-for="(item, index) in musicLibraryList" :key="item.id">
<view class="music-library-cover">
<image
class="music-cover-img"
:src="item.cover_url || '/static/images/avatar.png'"
mode="aspectFill">
</image>
<view class="music-play-overlay" @click="selectMusicFromLibrary(item)">
<text class="music-play-icon">▶</text>
</view>
</view>
<view class="music-library-info">
<view class="music-library-title">{{item.title}}</view>
<view class="music-library-artist">{{item.artist || '未知艺术家'}}</view>
<view class="music-library-meta">
<text class="music-meta-item">👤 {{item.username}}</text>
<text class="music-meta-item">▶ {{item.play_count}}</text>
<text class="music-meta-item" @click.stop="toggleMusicLike(item)">
{{item.is_liked ? '❤️' : '🤍'}} {{item.like_count}}
</text>
</view>
</view>
</view>
<!-- 加载更多 -->
<view v-if="musicLibraryHasMore" class="music-load-more" @click="loadMoreMusic">
<text>加载更多</text>
</view>
<!-- 空状态 -->
<view v-if="musicLibraryList.length === 0" class="music-empty">
<text class="music-empty-icon">🎵</text>
<text class="music-empty-text">音乐库还没有歌曲</text>
<text class="music-empty-desc">快来添加第一首歌吧!</text>
</view>
</view>
</view>
<!-- 历史记录 -->
<view v-if="singCurrentTab === 'history'" class="sing-content">
<view class="history-list" v-if="singHistoryList.length > 0">
<view class="history-item" v-for="(item,index) in singHistoryList" :key="'history-'+index">
<view class="history-info">
<text class="history-title">{{item.song_title}}</text>
<text class="history-date">{{formatDate(item.created_at)}}</text>
</view>
<view class="history-actions">
<view class="history-btn" @click="playSingVideo(item.video_url)">播放</view>
</view>
</view>
</view>
<view v-else class="music-empty">
<text class="music-empty-icon">📼</text>
<text class="music-empty-text">还没有历史记录</text>
</view>
</view>
</scroll-view>
</view>
</swiper-item>
<!-- 跳舞页面 (index 4) -->
<swiper-item>
<view class="swiper-content feature-page">
<view class="feature-icon">💃</view>
<view class="feature-title">跳舞功能</view>
<view class="feature-desc">让她为你跳一支舞</view>
<scroll-view class="feature-scroll" scroll-y="true">
<view class="history-section" v-if="danceHistoryList.length > 0">
<view class="history-section-header">
<view class="section-title">历史视频</view>
<view class="section-more" @click="openHistoryModal('dance', 'all')">查看全部</view>
</view>
<view class="history-list">
<view class="history-item" v-for="(item,index) in danceHistoryList.slice(0, 2)" :key="'dance-history-'+index">
<view class="history-info">
<text class="history-title">{{item.prompt || '跳舞视频'}}</text>
<text class="history-date">{{formatDate(item.created_at)}}</text>
</view>
<view class="history-actions">
<view class="history-btn" @click="playDanceVideo(item.video_url)">播放</view>
</view>
</view>
</view>
</view>
<view class="dance-form">
<textarea
class="dance-input"
v-model="dancePrompt"
placeholder="描述你想看的舞蹈动作"
maxlength="200" />
<view class="dance-btn" @click="requestDance">生成舞蹈视频</view>
</view>
</scroll-view>
</view>
</swiper-item>
<!-- 换服装页面 (index 5) -->
<swiper-item>
<scroll-view class="swiper-scroll" scroll-y="true">
<view class="outfit-container">
<view class="outfit-header">
<view class="outfit-title">换装</view>
<view class="outfit-count" v-if="outfitListInfo && outfitListInfo.clothes_num != null">剩余次数:{{ outfitListInfo.clothes_num }}次</view>
</view>
<view class="outfit-loading" v-if="!outfitListInfo">加载中...</view>
<view class="outfit-content" v-else>
<view class="outfit-section" v-if="outfitListInfo.top && outfitListInfo.top.length">
<view class="section-title">上衣</view>
<view class="outfit-grid-wrapper">
<view class="outfit-item-wrapper" v-for="(item, idx) in outfitListInfo.top" :key="'top-'+idx" :class="isSelectedTop(item.id) ? 'outfit-grid-item-selected' : ''" @click="selectTop(item)">
<view class="outfit-grid-inner">
<image class="outfit-grid-img" :src="item.image_url ? item.image_url : '/static/images/replacement_pictureA.png'" mode="aspectFill"></image>
<view class="outfit-check-mark" v-if="isSelectedTop(item.id)">✓</view>
<view class="outfit-grid-badge" v-if="item.is_current">使用中</view>
<view class="outfit-grid-lock" v-if="item.is_lock && !item.is_free">🔒</view>
</view>
</view>
</view>
</view>
<view class="outfit-section" v-if="outfitListInfo.bottom && outfitListInfo.bottom.length">
<view class="section-title">下装</view>
<view class="outfit-grid-wrapper">
<view class="outfit-item-wrapper" v-for="(item, idx) in outfitListInfo.bottom" :key="'bottom-'+idx" :class="isSelectedBottom(item.id) ? 'outfit-grid-item-selected' : ''" @click="selectBottom(item)">
<view class="outfit-grid-inner">
<image class="outfit-grid-img" :src="item.image_url ? item.image_url : '/static/images/replacement_pictureA.png'" mode="aspectFill"></image>
<view class="outfit-check-mark" v-if="isSelectedBottom(item.id)">✓</view>
<view class="outfit-grid-badge" v-if="item.is_current">使用中</view>
<view class="outfit-grid-lock" v-if="item.is_lock && !item.is_free">🔒</view>
</view>
</view>
</view>
</view>
<view class="outfit-section" v-if="outfitListInfo.dress && outfitListInfo.dress.length">
<view class="section-title">连体服</view>
<view class="outfit-grid-wrapper">
<view class="outfit-item-wrapper" v-for="(item, idx) in outfitListInfo.dress" :key="'dress-'+idx" :class="isSelectedDress(item.id) ? 'outfit-grid-item-selected' : ''" @click="selectDress(item)">
<view class="outfit-grid-inner">
<image class="outfit-grid-img" :src="item.image_url ? item.image_url : '/static/images/replacement_pictureA.png'" mode="aspectFill"></image>
<view class="outfit-check-mark" v-if="isSelectedDress(item.id)">✓</view>
<view class="outfit-grid-badge" v-if="item.is_current">使用中</view>
<view class="outfit-grid-lock" v-if="item.is_lock && !item.is_free">🔒</view>
</view>
</view>
</view>
</view>
<view class="outfit-generate-btn" @click="toggleSaveToLook">{{ outfitSaveToLook ? '已勾选:保存到形象栏' : '勾选:保存到形象栏' }}</view>
<view class="outfit-generate-btn" @click="confirmOutfitChange">换装</view>
</view>
</view>
</scroll-view>
</swiper-item>
<!-- 刷礼物页面 (index 6) -->
<swiper-item>
<view class="swiper-content gift-page">
<view class="gift-header">
<view class="gift-header-title">🎁 礼物匣</view>
<view class="gift-header-desc">送她一份心意</view>
<view class="gift-intimacy-bar">
<view class="gift-intimacy-label">当前好感度</view>
<view class="gift-intimacy-info">
<text class="gift-intimacy-level">Lv.{{ getBobbiesList.level ? getBobbiesList.level : 0 }}</text>
<view class="gift-intimacy-progress">
<view class="gift-intimacy-progress-bar" :style="{ width: calculateProgressPercent() + '%' }"></view>
</view>
<text class="gift-intimacy-value">{{ getBobbiesList.intimacy ? getBobbiesList.intimacy : 0 }}/{{ getBobbiesList.next_level_intimacy ? getBobbiesList.next_level_intimacy : 0 }}</text>
</view>
</view>
</view>
<scroll-view class="gift-scroll" scroll-y="true">
<view class="gift-list">
<view class="gift-item" v-for="(item, index) in giftOptions" :key="index" @click="giftGiftClick(index)">
<view class="gift-item-inner">
<image class="gift-item-img" :src="giftGlobal + item.image" mode="aspectFit"></image>
<view class="gift-item-name">{{ item.name }}</view>
<view class="gift-item-price">{{ item.price }}金币</view>
<view class="gift-item-intimacy">+{{ item.intimacy_value }}好感</view>
</view>
</view>
</view>
</scroll-view>
<!-- 礼物详情弹窗 -->
<view class="gift-modal" v-if="giftStats" @click.self="giftCloseClick()">
<view class="gift-modal-content">
<view class="gift-modal-header">
<text class="gift-modal-title">礼物详情</text>
<image class="gift-modal-close" src="/static/images/close.png" @click="giftCloseClick()"></image>
</view>
<view class="gift-modal-body">
<image class="gift-modal-img" :src="giftGlobal + (giftInfoOptions && giftInfoOptions.image ? giftInfoOptions.image : '')" mode="aspectFit"></image>
<view class="gift-modal-info">
<view class="gift-modal-name">{{ giftInfoOptions && giftInfoOptions.name ? giftInfoOptions.name : '' }}</view>
<view class="gift-modal-intimacy">+{{ giftInfoOptions && giftInfoOptions.intimacy_value ? giftInfoOptions.intimacy_value : '0' }} 好感度</view>
<view class="gift-modal-price">{{ giftInfoOptions && giftInfoOptions.price ? giftInfoOptions.price : '0' }} 金币</view>
</view>
</view>
<view class="gift-modal-quantity">
<text class="gift-modal-quantity-label">数量</text>
<view class="gift-modal-quantity-control">
<image class="gift-quantity-btn" src="/static/images/minus.png" @click="giftDecreaseNumber"></image>
<input class="gift-quantity-input" v-model="giftForm.nums" type="number" />
<image class="gift-quantity-btn" src="/static/images/add.png" @click="giftIncreaseNumber"></image>
</view>
</view>
<view class="gift-modal-balance">余额:{{ giftUserInfo && giftUserInfo.money != null ? giftUserInfo.money : 0 }} 金币</view>
<view class="gift-modal-btn" @click="giftGiveClick()">
<text v-if="(Number(giftInfoOptions && giftInfoOptions.price ? giftInfoOptions.price : 0) * Number(giftForm.nums || 0)) > Number(giftUserInfo && giftUserInfo.money != null ? giftUserInfo.money : 0)">充值并赠送</text>
<text v-else>赠送Ta</text>
</view>
</view>
</view>
</view>
</swiper-item>
<!-- 短剧页面 (index 7) -->
<swiper-item>
<view class="swiper-content drama-page">
<!-- 全屏海报背景 -->
<view class="drama-hero" @click="openDramaLink">
<!-- 背景图片 -->
<image
class="drama-hero-bg"
src="https://nvlovers.oss-cn-qingdao.aliyuncs.com/uploads/20251231/a6d2dae70029b6465ea1f1f605f77258.png"
mode="aspectFill">
</image>
<!-- 渐变遮罩 -->
<view class="drama-hero-overlay"></view>
<!-- 内容区域 -->
<view class="drama-hero-content">
<!-- 热门标签 -->
<view class="drama-hot-badge">
<text class="drama-hot-icon">🔥</text>
<text class="drama-hot-text">热门推荐</text>
</view>
<!-- 标题 -->
<view class="drama-hero-title">精彩短剧</view>
<view class="drama-hero-subtitle">海量热门短剧等你来看</view>
<!-- 标签组 -->
<view class="drama-tags">
<view class="drama-tag">💕 甜宠</view>
<view class="drama-tag">👑 霸总</view>
<view class="drama-tag">🎭 逆袭</view>
<view class="drama-tag">⚡ 爽文</view>
</view>
<!-- 播放按钮 -->
<view class="drama-play-button">
<view class="drama-play-icon">▶</view>
<text class="drama-play-text">立即观看</text>
</view>
<!-- 提示文字 -->
<view class="drama-hint">
<text class="drama-hint-icon">✨</text>
<text class="drama-hint-text">点击任意位置进入短剧世界</text>
</view>
</view>
</view>
<!-- 底部特色推荐(可选) -->
<view class="drama-features">
<view class="drama-feature-item">
<text class="drama-feature-icon">📺</text>
<text class="drama-feature-text">海量剧集</text>
</view>
<view class="drama-feature-item">
<text class="drama-feature-icon">🆓</text>
<text class="drama-feature-text">免费观看</text>
</view>
<view class="drama-feature-item">
<text class="drama-feature-icon">⚡</text>
<text class="drama-feature-text">每日更新</text>
</view>
</view>
</view>
</swiper-item>
</swiper>
<!-- 自定义细线指示器 - 聊天页面时隐藏 -->
<view class="swiper-indicator" v-if="getBobbiesList.reg_step == 4" :class="{ 'indicator-hidden': currentTab === 2 }">
<view
v-for="(tab, index) in tabs"
:key="index"
class="indicator-line"
:class="{ 'active': currentTab === index }">
</view>
</view>
</view>
</view>
<!-- 青少年模式弹框 -->
<under-age :reg-step="getBobbiesList.reg_step" @underAgeStatusChanged="handleUnderAgeStatusChange"></under-age>
<!-- 底部 Tab 栏 - 聊天页面时隐藏 -->
<view class="bottom-tab-bar" :class="{ 'tab-bar-hidden': currentTab === 2 }">
<tab-bar></tab-bar>
</view>
<!-- 青少年模式遮罩层 -->
<view v-if="underAgeEnabled" class="underage-overlay">
<view class="underage-text">已开启青少年模式</view>
</view>
<view v-if="historyModalVisible" class="modal-mask" @click="closeHistoryModal">
<view class="modal-card" @click.stop>
<view class="modal-header">
<view class="modal-title">{{ historyModalType === 'dance' ? (historyModalMode === 'all' ? '跳舞全部历史' : '跳舞历史视频') : (historyModalMode === 'all' ? '唱歌全部历史' : '唱歌历史视频') }}</view>
<view class="modal-close" @click="closeHistoryModal">关闭</view>
</view>
<scroll-view class="modal-list" scroll-y="true">
<view class="modal-item" v-for="(item,index) in historyModalList" :key="'modal-'+index">
<view class="modal-item-info">
<text class="modal-item-title">{{ historyModalType === 'dance' ? (item.prompt || '跳舞视频') : (item.song_title || '唱歌视频') }}</text>
<text class="modal-item-date">{{ formatDate(item.created_at) }}</text>
<text v-if="historyModalMode === 'all'" class="modal-item-status">{{ formatHistoryStatus(item) }}</text>
</view>
<view class="modal-item-actions">
<view v-if="item.video_url" class="modal-play" @click="openVideoPlayer(item.video_url)">播放</view>
<view v-if="historyModalMode === 'all' && item.status === 'failed'" class="modal-retry" @click="retryHistoryItem(item)">重新下载</view>
</view>
</view>
</scroll-view>
</view>
</view>
<view v-if="videoPlayerVisible" class="modal-mask" @click="closeVideoPlayer">
<view class="modal-card" @click.stop>
<view class="modal-header">
<view class="modal-title">视频播放</view>
<view class="modal-close" @click="closeVideoPlayer">关闭</view>
</view>
<video class="modal-video" :src="videoPlayerUrl" controls></video>
</view>
</view>
<view v-if="singGenerating && currentTab === 2" class="global-block-mask" @touchmove.stop.prevent>
<view class="global-block-card">
<view class="global-block-title">正在生成视频中</view>
<view class="global-block-sub">预计等待15分钟</view>
<view class="global-block-actions">
<view class="global-block-btn" @click.stop="cancelSingGeneration">取消生成</view>
</view>
</view>
</view>
<view v-if="danceGenerating && currentTab === 3" class="global-block-mask" @touchmove.stop.prevent>
<view class="global-block-card">
<view class="global-block-title">正在生成视频中</view>
<view class="global-block-sub">预计等待15分钟</view>
<view class="global-block-actions">
<view class="global-block-btn" @click.stop="cancelDanceGeneration">取消生成</view>
</view>
</view>
</view>
<!-- 添加音乐弹窗 -->
<view v-if="showMusicModal" class="modal-mask" @click="closeMusicModal">
<view class="modal-card music-modal-card" @click.stop>
<view class="modal-header">
<view class="modal-title">添加音乐</view>
<view class="modal-close" @click="closeMusicModal">关闭</view>
</view>
<view class="music-modal-body">
<view class="music-form-item">
<view class="music-form-label">歌曲标题 *</view>
<input class="music-form-input" v-model="musicForm.title" placeholder="请输入歌曲标题" />
</view>
<view class="music-form-item">
<view class="music-form-label">艺术家</view>
<input class="music-form-input" v-model="musicForm.artist" placeholder="请输入艺术家名称" />
</view>
<view class="music-form-item">
<view class="music-form-label">音乐链接 *</view>
<textarea
class="music-form-textarea"
v-model="musicForm.music_url"
placeholder="请粘贴音乐文件链接(支持 mp3、m4a 等格式)"
maxlength="500" />
</view>
<view class="music-form-item">
<view class="music-form-label">封面图链接</view>
<input class="music-form-input" v-model="musicForm.cover_url" placeholder="请输入封面图链接(可选)" />
</view>
<view class="music-form-item">
<view class="music-form-label">时长(秒)</view>
<input class="music-form-input" v-model.number="musicForm.duration" type="number" placeholder="请输入歌曲时长" />
</view>
<view class="music-modal-btn" @click="submitMusic">
<text>提交</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import {
GetUserBasic,
LoverInit,
LoverBasic,
OutfitList,
OutfitPurchase,
OutfitChange,
GiftsLists,
GiftsGive,
GiftsGiveApi,
CreateGiftsOrderApi,
JinbiPayApi,
SingSongs,
SingGenerate,
SingGenerateTask,
SingCurrent,
DanceGenerate,
DanceGenerateTask,
DanceCurrent,
SessionInit,
SessionSend
} from '@/utils/api.js'
import { baseURL, baseURLPy } from '@/utils/request.js'
import notHave from '@/components/not-have.vue';
import topSafety from '@/components/top-safety.vue';
import tabBar from '@/components/tab-bar.vue';
import UnderAge from '@/components/under-age.vue'; // 导入under-age组件
import uniNavBar from '@/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue'; // 导入uni-nav-bar组件
import {
useConversationStore
} from '@/stores/conversation';
export default {
components: {
notHave,
topSafety,
tabBar,
UnderAge,
uniNavBar,
},
data() {
return {
// Tab 相关
currentTab: 1, // 默认显示首页(商城是 0首页是 1
tabIntoView: 'tab-1',
tabs: [
{ name: '商城' },
{ name: '首页' },
{ name: '聊天' },
{ name: '唱歌' },
{ name: '跳舞' },
{ name: '换服装' },
{ name: '刷礼物' },
{ name: '短剧' }
],
// 首页形象栏相关
homeLooksList: [],
selectedLookId: null,
currentLookImageUrl: '',
// 聊天相关
chatMessages: [],
chatInputText: '',
chatScrollTop: 0,
chatIntoView: '',
chatSessionId: null,
chatUserAvatar: '',
chatLoverAvatar: '',
chatSending: false,
dancePrompt: '',
singSongsList: [],
singHistoryList: [],
danceHistoryList: [],
songId: 0,
singGenerating: false, // 唱歌视频生成中状态
singGeneratingTaskId: 0,
danceGenerating: false, // 跳舞视频生成中状态
danceGeneratingTaskId: 0,
singPollTimer: null,
dancePollTimer: null,
// 音乐库相关
singCurrentTab: 'songs', // songs, library, history
musicLibraryList: [],
musicLibraryPage: 1,
musicLibraryPageSize: 20,
musicLibraryTotal: 0,
musicLibraryHasMore: true,
showMusicModal: false,
musicForm: {
title: '',
artist: '',
music_url: '',
cover_url: '',
duration: null
},
outfitListInfo: null,
outfitBuyForm: {
item_id: ''
},
outfitMode: null,
outfitFormTopBottom: {
top_item_id: '',
bottom_item_id: '',
save_to_look: false
},
outfitFormDress: {
dress_item_id: '',
save_to_look: false
},
outfitSaveToLook: false,
giftGlobal: baseURL,
giftStats: false,
successStats: false,
giftForm: {
gifts_id: '',
nums: 1,
to_user_id: ''
},
giftOptions: [],
giftInfoOptions: null,
giftUserInfo: {},
statusBarHeight: uni.getWindowInfo().statusBarHeight,
currentStep: 0,
chartData: {},
opts: {
color: ["#9F47FF", "#91CB74", "#FAC858", "#EE6666", "#73C0DE", "#3CA272", "#FC8452", "#9A60B4",
"#ea7ccc"
],
padding: undefined,
title: {
name: "",
fontSize: 17,
color: "#F661B5"
},
subtitle: {
name: "",
fontSize: 12,
color: "#FFFFFF"
},
extra: {
arcbar: {
type: "default",
width: 5,
backgroundColor: "rgba(255,255,255,0.2)",
startAngle: 0.75,
endAngle: 0.25,
gap: 2,
linearType: "custom",
customColor: ["#F661B5"]
}
}
},
getBobbiesList: {
reg_step: 1, // 默认第一步:完善个人资料
level: 0,
intimacy: 0,
next_level_intimacy: 100
},
loverBasicList: null, // 改为 null 而不是空字符串
underAgeEnabled: false, // 添加青少年模式状态变量
historyModalVisible: false,
historyModalType: 'sing',
historyModalMode: 'preview',
historyModalList: [],
videoPlayerVisible: false,
videoPlayerUrl: '',
}
},
onLoad(options) {
//#ifdef MP-WEIXIN
this.checkPermission()
//#endif
console.log('uni.env.',uni.env)
// 如果有 tab 参数,切换到对应的 tab
if (options && options.tab) {
const tabIndex = parseInt(options.tab);
if (!isNaN(tabIndex) && tabIndex >= 0 && tabIndex < this.tabs.length) {
this.currentTab = tabIndex;
}
}
},
onReady() {
this.getServerData();
},
onShow() {
// 每次页面显示时检查本地存储的青少年模式状态
this.checkUnderAgeStatus();
if (uni.getStorageSync('token')) {
this.getUserBasic()
this.loverBasic()
this.loadHomeLooks() // 加载首页形象栏
}
// 获取歌曲列表
this.getSingSongs();
this.getDanceHistory();
if (this.currentTab === 2) {
this.restoreSingGeneration();
}
if (this.currentTab === 3) {
this.restoreDanceGeneration();
}
if (this.currentTab === 4) {
this.ensureOutfitLoaded();
}
if (this.currentTab === 5) {
this.ensureGiftLoaded();
}
},
methods: {
// Tab 切换方法
switchTab(index) {
console.log('点击 Tab切换到索引:', index, '对应 Tab:', this.tabs[index].name);
// 如果切换到聊天 tab现在是 index 2初始化聊天会话
if (index === 2 && !this.chatSessionId) {
this.initChatSession();
}
this.currentTab = index;
this.updateTabIntoView(index);
if (index === 3) {
this.restoreSingGeneration();
}
if (index === 4) {
this.restoreDanceGeneration();
}
if (index === 5) {
this.ensureOutfitLoaded();
}
if (index === 6) {
this.ensureGiftLoaded();
}
},
onSwiperChange(e) {
console.log('Swiper 滑动,当前索引:', e.detail.current, '对应 Tab:', this.tabs[e.detail.current].name);
this.currentTab = e.detail.current;
this.updateTabIntoView(e.detail.current);
// 如果滑动到聊天 tab现在是 index 2初始化聊天会话
if (e.detail.current === 2 && !this.chatSessionId) {
this.initChatSession();
}
if (e.detail.current === 3) {
this.restoreSingGeneration();
}
if (e.detail.current === 4) {
this.restoreDanceGeneration();
}
if (e.detail.current === 5) {
this.ensureOutfitLoaded();
}
if (e.detail.current === 6) {
this.ensureGiftLoaded();
}
},
updateTabIntoView(index) {
this.$nextTick(() => {
this.tabIntoView = 'tab-' + index;
});
},
// 选择歌曲
selectSongDirect(song) {
// 防止重复点击
if (this.singGenerating) {
uni.showToast({
title: '视频生成中,请稍候...',
icon: 'none',
duration: 2000
});
return;
}
this.songId = song.id;
this.singGenerating = true;
this.singGeneratingTaskId = 0;
uni.setStorageSync('singGeneratingTaskId', 0);
uni.setStorageSync('singGenerating', true);
SingGenerate({ song_id: song.id }).then(res => {
if (res.code == 1) {
this.singGeneratingTaskId = res.data.generation_task_id;
uni.setStorageSync('singGeneratingTaskId', this.singGeneratingTaskId);
this.getSingGenerateTask(res.data.generation_task_id);
} else {
this.singGenerating = false;
this.singGeneratingTaskId = 0;
uni.setStorageSync('singGenerating', false);
uni.setStorageSync('singGeneratingTaskId', 0);
uni.showToast({ title: res.msg, icon: 'none' });
}
}).catch(err => {
this.singGenerating = false;
this.singGeneratingTaskId = 0;
uni.setStorageSync('singGenerating', false);
uni.setStorageSync('singGeneratingTaskId', 0);
uni.showToast({
title: '请求失败,请重试',
icon: 'none'
});
});
},
restoreSingGeneration() {
if (this.currentTab !== 2) {
return;
}
if (!uni.getStorageSync('token')) {
return;
}
if (this.singGenerating) {
return;
}
const storedTaskId = parseInt(uni.getStorageSync('singGeneratingTaskId') || 0);
const storedFlag = !!uni.getStorageSync('singGenerating');
if (storedFlag && storedTaskId > 0) {
this.singGenerating = true;
this.singGeneratingTaskId = storedTaskId;
this.getSingGenerateTask(storedTaskId);
return;
}
SingCurrent({}).then(res => {
if (res && res.code == 1 && res.data && res.data.generation_task_id) {
this.singGenerating = true;
this.singGeneratingTaskId = res.data.generation_task_id;
uni.setStorageSync('singGenerating', true);
uni.setStorageSync('singGeneratingTaskId', this.singGeneratingTaskId);
this.getSingGenerateTask(this.singGeneratingTaskId);
}
}).catch(() => {});
},
restoreDanceGeneration() {
if (this.currentTab !== 3) {
return;
}
if (!uni.getStorageSync('token')) {
return;
}
if (this.danceGenerating) {
return;
}
const storedTaskId = parseInt(uni.getStorageSync('danceGeneratingTaskId') || 0);
const storedFlag = !!uni.getStorageSync('danceGenerating');
if (storedFlag && storedTaskId > 0) {
this.danceGenerating = true;
this.danceGeneratingTaskId = storedTaskId;
this.getDanceGenerateTask(storedTaskId);
return;
}
DanceCurrent({}).then(res => {
if (res && res.code == 1 && res.data && res.data.generation_task_id) {
this.danceGenerating = true;
this.danceGeneratingTaskId = res.data.generation_task_id;
uni.setStorageSync('danceGenerating', true);
uni.setStorageSync('danceGeneratingTaskId', this.danceGeneratingTaskId);
this.getDanceGenerateTask(this.danceGeneratingTaskId);
}
}).catch(() => {});
},
ensureOutfitLoaded() {
if (!uni.getStorageSync('token')) {
return;
}
if (this.outfitListInfo) {
return;
}
this.fetchOutfitList();
},
fetchOutfitList() {
OutfitList({}).then(res => {
if (res && res.code == 1) {
this.outfitListInfo = res.data || null;
this.normalizeOutfitList();
this.selectCurrentOutfitInTab();
} else {
this.outfitListInfo = null;
uni.showToast({ title: (res && res.msg) ? res.msg : '加载失败', icon: 'none' });
}
}).catch(() => {
this.outfitListInfo = null;
uni.showToast({ title: '加载失败', icon: 'none' });
});
},
normalizeOutfitList() {
if (!this.outfitListInfo) return;
const owned = Array.isArray(this.outfitListInfo.owned_outfit_ids) ? this.outfitListInfo.owned_outfit_ids : [];
const current = this.outfitListInfo.current_outfit || {};
const mark = (arr, type) => {
if (!Array.isArray(arr)) return;
for (let i = 0; i < arr.length; i++) {
const it = arr[i];
if (type === 'top') it.is_current = (current.top_id == it.id);
if (type === 'bottom') it.is_current = (current.bottom_id == it.id);
if (type === 'dress') it.is_current = (current.dress_id == it.id);
it.is_lock = !owned.includes(it.id);
}
};
mark(this.outfitListInfo.top, 'top');
mark(this.outfitListInfo.bottom, 'bottom');
mark(this.outfitListInfo.dress, 'dress');
},
selectCurrentOutfitInTab() {
if (!this.outfitListInfo) return;
const cur = this.outfitListInfo.current_outfit || {};
if (cur.dress_id) {
this.outfitMode = 1;
this.outfitFormDress.dress_item_id = cur.dress_id;
this.outfitFormTopBottom.top_item_id = '';
this.outfitFormTopBottom.bottom_item_id = '';
return;
}
if (cur.top_id || cur.bottom_id) {
this.outfitMode = 2;
this.outfitFormTopBottom.top_item_id = cur.top_id || '';
this.outfitFormTopBottom.bottom_item_id = cur.bottom_id || '';
this.outfitFormDress.dress_item_id = '';
}
},
isSelectedTop(id) {
return this.outfitFormTopBottom.top_item_id === id;
},
isSelectedBottom(id) {
return this.outfitFormTopBottom.bottom_item_id === id;
},
isSelectedDress(id) {
return this.outfitFormDress.dress_item_id === id;
},
openOutfitDetail(item) {
if (!item || !item.id) return;
this.outfitBuyForm.item_id = item.id;
const price = item.price_gold ? String(item.price_gold) : '0';
uni.showModal({
title: '购买服装',
content: `${item.name ? item.name : '该服装'}\n价格${price}金币`,
confirmText: '购买',
cancelText: '取消',
success: (r) => {
if (r.confirm) {
this.buyOutfit();
}
}
});
},
buyOutfit() {
if (!this.outfitBuyForm.item_id) return;
OutfitPurchase(this.outfitBuyForm).then(res => {
if (res && res.code == 1) {
uni.showToast({ title: '购买成功', icon: 'none' });
this.fetchOutfitList();
} else {
uni.showToast({ title: (res && res.msg) ? res.msg : '购买失败', icon: 'none' });
}
}).catch(() => {
uni.showToast({ title: '购买失败', icon: 'none' });
});
},
selectTop(item) {
if (!item) return;
if (item.is_lock && !item.is_free) {
this.openOutfitDetail(item);
return;
}
if (this.outfitMode === 1 && this.outfitFormDress.dress_item_id) {
uni.showToast({ title: '连体服模式下不能选择上衣', icon: 'none' });
return;
}
this.outfitMode = 2;
this.outfitFormTopBottom.top_item_id = (this.outfitFormTopBottom.top_item_id === item.id) ? '' : item.id;
},
selectBottom(item) {
if (!item) return;
if (item.is_lock && !item.is_free) {
this.openOutfitDetail(item);
return;
}
if (this.outfitMode === 1 && this.outfitFormDress.dress_item_id) {
uni.showToast({ title: '连体服模式下不能选择下装', icon: 'none' });
return;
}
this.outfitMode = 2;
this.outfitFormTopBottom.bottom_item_id = (this.outfitFormTopBottom.bottom_item_id === item.id) ? '' : item.id;
},
selectDress(item) {
if (!item) return;
if (item.is_lock && !item.is_free) {
this.openOutfitDetail(item);
return;
}
if (this.outfitMode === 2 && (this.outfitFormTopBottom.top_item_id || this.outfitFormTopBottom.bottom_item_id)) {
uni.showToast({ title: '上衣下装模式下不能选择连体服', icon: 'none' });
return;
}
this.outfitMode = 1;
if (this.outfitFormDress.dress_item_id === item.id) {
this.outfitFormDress.dress_item_id = '';
} else {
this.outfitFormTopBottom.top_item_id = '';
this.outfitFormTopBottom.bottom_item_id = '';
this.outfitFormDress.dress_item_id = item.id;
}
},
toggleSaveToLook() {
this.outfitSaveToLook = !this.outfitSaveToLook;
this.outfitFormDress.save_to_look = this.outfitSaveToLook;
this.outfitFormTopBottom.save_to_look = this.outfitSaveToLook;
},
confirmOutfitChange() {
if (!this.outfitListInfo || !this.outfitListInfo.clothes_num) {
uni.showToast({ title: '当前暂无次数', icon: 'none' });
return;
}
const hasDress = this.outfitMode === 1 && this.outfitFormDress.dress_item_id;
const hasTopBottom = this.outfitMode === 2 && (this.outfitFormTopBottom.top_item_id || this.outfitFormTopBottom.bottom_item_id);
if (!hasDress && !hasTopBottom) {
uni.showToast({ title: '请先选择要换装的服装', icon: 'none' });
return;
}
uni.showModal({
title: '提示',
content: '确认是否换装',
success: (r) => {
if (r.confirm) {
this.doOutfitChange();
}
}
});
},
doOutfitChange() {
const payload = (this.outfitMode === 1 && this.outfitFormDress.dress_item_id) ? this.outfitFormDress : this.outfitFormTopBottom;
OutfitChange(payload).then(res => {
if (res && res.code == 1) {
uni.showToast({ title: '换装成功', icon: 'success' });
this.fetchOutfitList();
} else {
uni.showToast({ title: (res && res.msg) ? res.msg : '换装失败', icon: 'none' });
}
}).catch(() => {
uni.showToast({ title: '换装失败', icon: 'none' });
});
},
ensureGiftLoaded() {
if (!uni.getStorageSync('token')) {
return;
}
if (this.giftOptions && this.giftOptions.length > 0 && this.giftUserInfo) {
return;
}
this.giftGiftsLists();
this.giftGetUserBasic();
},
giftGetUserBasic() {
GetUserBasic({}).then(res => {
if (res && res.code == 1) {
this.giftUserInfo = res.data || {};
}
}).catch(() => {});
},
giftGiftsLists() {
GiftsLists({}).then(res => {
if (res && res.code == 1) {
this.giftOptions = (res.data && res.data.data) ? res.data.data : [];
} else {
this.giftOptions = [];
uni.showToast({ title: (res && res.msg) ? res.msg : '加载失败', icon: 'none', position: 'top' });
}
}).catch(() => {
this.giftOptions = [];
uni.showToast({ title: '加载失败', icon: 'none', position: 'top' });
});
},
giftGiftClick(index) {
this.giftInfoOptions = (this.giftOptions && this.giftOptions[index]) ? this.giftOptions[index] : null;
this.giftStats = !this.giftStats;
},
giftGiveClick() {
if (!this.giftInfoOptions) {
uni.showToast({ title: '请选择礼物', icon: 'none', position: 'top' });
return;
}
this.giftForm.gifts_id = this.giftInfoOptions.id;
const total = Number(this.giftInfoOptions.price || 0) * Number(this.giftForm.nums || 0);
const money = Number(this.giftUserInfo && this.giftUserInfo.money != null ? this.giftUserInfo.money : 0);
if (total > money) {
this.giftGiftsTopUp();
} else {
this.giftGiftsGive();
}
},
giftGiftsGive() {
let fn = GiftsGiveApi;
if (this.giftForm.to_user_id && String(this.giftForm.to_user_id).trim()) {
fn = GiftsGive;
}
fn(this.giftForm).then(res => {
if (res && res.code == 1) {
uni.showToast({ title: '赠送成功', icon: 'none', position: 'top' });
setTimeout(() => {
this.giftGetUserBasic();
this.getUserBasic(); // 刷新好感度数据
this.giftStats = false;
}, 1000);
} else {
uni.showToast({ title: (res && res.msg) ? res.msg : '赠送失败', icon: 'none', position: 'top' });
}
}).catch(() => {
uni.showToast({ title: '赠送失败', icon: 'none', position: 'top' });
});
},
giftGiftsTopUp() {
if (!this.giftInfoOptions) return;
CreateGiftsOrderApi({
gifts_id: this.giftInfoOptions.id,
nums: this.giftForm.nums,
pay_type: 'miniWechat'
}).then(res => {
if (res && res.code == 1 && res.data && res.data.out_trade_no) {
this.giftGoPay(res.data.out_trade_no);
}
}).catch(() => {});
},
giftGoPay(orderId) {
JinbiPayApi({ out_trade_no: orderId }).then(res => {
if (res && res.code == 1) {
setTimeout(() => {
this.giftGiftsGive();
}, 1000);
} else {
uni.showToast({ title: (res && res.msg) ? res.msg : '支付失败', icon: 'none', position: 'top' });
}
}).catch(() => {
uni.showToast({ title: '支付失败', icon: 'none', position: 'top' });
});
},
giftCloseClick() {
this.giftStats = false;
this.giftForm.nums = 1;
this.giftInfoOptions = null;
},
giftIncreaseNumber() {
this.giftForm.nums = parseInt(this.giftForm.nums) || 0;
this.giftForm.nums++;
},
giftDecreaseNumber() {
this.giftForm.nums = parseInt(this.giftForm.nums) || 0;
if (this.giftForm.nums > 1) {
this.giftForm.nums--;
}
},
// 请求跳舞
requestDance() {
if (!this.dancePrompt || !this.dancePrompt.trim()) {
this.dancePrompt = '跳一段可爱的舞蹈';
}
if (this.danceGenerating) {
uni.showToast({ title: '视频生成中,请稍候...', icon: 'none', duration: 2000 });
return;
}
this.danceGenerating = true;
this.danceGeneratingTaskId = 0;
uni.setStorageSync('danceGeneratingTaskId', 0);
uni.setStorageSync('danceGenerating', true);
DanceGenerate({ prompt: this.dancePrompt.trim() }).then(res => {
if (res.code == 1) {
this.danceGeneratingTaskId = res.data.generation_task_id;
uni.setStorageSync('danceGeneratingTaskId', this.danceGeneratingTaskId);
this.getDanceGenerateTask(this.danceGeneratingTaskId);
} else {
this.danceGenerating = false;
this.danceGeneratingTaskId = 0;
uni.setStorageSync('danceGenerating', false);
uni.setStorageSync('danceGeneratingTaskId', 0);
uni.showToast({ title: res.msg, icon: 'none' });
}
}).catch(() => {
this.danceGenerating = false;
this.danceGeneratingTaskId = 0;
uni.setStorageSync('danceGenerating', false);
uni.setStorageSync('danceGeneratingTaskId', 0);
uni.showToast({ title: '请求失败,请重试', icon: 'none' });
});
},
// 打开商城外部链接
openShopLink() {
// 在 H5 环境中打开外部链接
// #ifdef H5
window.open('http://apidoc.weipinshang.net/web/#/1?page_id=450', '_blank');
// #endif
// 在 APP 环境中使用 plus.runtime.openURL
// #ifdef APP-PLUS
plus.runtime.openURL('http://apidoc.weipinshang.net/web/#/1?page_id=450');
// #endif
// 在小程序环境中复制链接
// #ifdef MP
uni.setClipboardData({
data: 'http://apidoc.weipinshang.net/web/#/1?page_id=450',
success: function () {
uni.showToast({
title: '链接已复制,请在浏览器中打开',
icon: 'none',
duration: 2000
});
}
});
// #endif
},
// 打开短剧外部链接
openDramaLink() {
// 在 H5 环境中打开外部链接
// #ifdef H5
window.open('https://djcps.meinvclk.top', '_blank');
// #endif
// 在 APP 环境中使用 plus.runtime.openURL
// #ifdef APP-PLUS
plus.runtime.openURL('https://djcps.meinvclk.top');
// #endif
// 在小程序环境中复制链接
// #ifdef MP
uni.setClipboardData({
data: 'https://djcps.meinvclk.top',
success: function () {
uni.showToast({
title: '链接已复制,请在浏览器中打开',
icon: 'none',
duration: 2000
});
}
});
// #endif
},
getDanceHistory() {
uni.request({
url: baseURLPy + '/dance/history',
method: 'GET',
header: {
'Authorization': 'Bearer ' + uni.getStorageSync("token")
},
success: (res) => {
console.log('跳舞历史:', res.data);
if (res.data.code === 1 && res.data.data) {
this.danceHistoryList = res.data.data;
}
},
fail: (err) => {
console.error('获取跳舞历史失败:', err);
}
});
},
playDanceVideo(videoUrl) {
this.openVideoPlayer(videoUrl);
},
openHistoryModal(type, mode = 'preview') {
this.historyModalType = type;
this.historyModalMode = mode;
if (mode === 'all') {
this.fetchAllHistory(type);
return;
}
this.historyModalList = type === 'dance' ? (this.danceHistoryList || []) : (this.singHistoryList || []);
this.historyModalVisible = true;
},
fetchAllHistory(type) {
const endpoint = type === 'dance' ? '/dance/history/all' : '/sing/history/all';
uni.request({
url: baseURLPy + endpoint,
method: 'GET',
header: {
'Authorization': 'Bearer ' + uni.getStorageSync("token")
},
success: (res) => {
if (res.data && res.data.code === 1 && res.data.data) {
this.historyModalList = res.data.data;
} else {
this.historyModalList = [];
}
this.historyModalVisible = true;
},
fail: () => {
this.historyModalList = [];
this.historyModalVisible = true;
}
});
},
isDownloadFailed(item) {
const msg = (item && item.error_msg ? String(item.error_msg) : '') || '';
return msg.indexOf('下载失败') !== -1;
},
formatHistoryStatus(item) {
if (!item) return '';
const status = item.status || '';
if (status === 'succeeded') return '成功';
if (status === 'pending' || status === 'running') return '生成中';
if (status === 'failed') {
if (this.isDownloadFailed(item)) return '文件下载失败';
return '生成失败';
}
return '';
},
retryHistoryItem(item) {
if (!item || !item.id) return;
const id = item.id;
const endpoint = this.historyModalType === 'dance' ? ('/dance/retry/' + id) : ('/sing/retry/' + id);
uni.showLoading({ title: '处理中...', mask: true });
uni.request({
url: baseURLPy + endpoint,
method: 'POST',
header: {
'Authorization': 'Bearer ' + uni.getStorageSync("token")
},
success: () => {
uni.hideLoading();
uni.showToast({ title: '已触发重新下载', icon: 'none', duration: 1500 });
this.fetchAllHistory(this.historyModalType);
},
fail: () => {
uni.hideLoading();
uni.showToast({ title: '重试失败', icon: 'none', duration: 1500 });
}
});
},
closeHistoryModal() {
this.historyModalVisible = false;
},
openVideoPlayer(url) {
if (!url) return;
this.videoPlayerUrl = url;
this.videoPlayerVisible = true;
},
closeVideoPlayer() {
this.videoPlayerVisible = false;
this.videoPlayerUrl = '';
},
// 获取歌曲列表
// 获取歌曲列表
getSingSongs() {
SingSongs().then(res => {
if (res.code == 1) {
this.singSongsList = res.data.songs || [];
}
});
// 同时获取历史记录
this.getSingHistory();
},
// 获取唱歌历史记录
getSingHistory() {
uni.request({
url: baseURLPy + '/sing/history',
method: 'GET',
header: {
'Authorization': 'Bearer ' + uni.getStorageSync("token")
},
success: (res) => {
console.log('唱歌历史:', res.data);
if (res.data.code === 1 && res.data.data) {
this.singHistoryList = res.data.data;
}
},
fail: (err) => {
console.error('获取唱歌历史失败:', err);
}
});
},
// 播放唱歌视频
playSingVideo(videoUrl) {
this.openVideoPlayer(videoUrl);
},
// 格式化日期
formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hour = String(date.getHours()).padStart(2, '0');
const minute = String(date.getMinutes()).padStart(2, '0');
return `${month}-${day} ${hour}:${minute}`;
},
// 加载换装数据
// 轮询监听唱歌任务结果
getSingGenerateTask(task_id) {
const that = this;
let attempts = 0;
const maxAttempts = 225; // 15分钟左右 (225*4s)
const doPoll = () => {
if (!that.singGenerating || that.singGeneratingTaskId !== task_id) {
return;
}
attempts++;
if (attempts > maxAttempts) {
that.singGenerating = false;
that.singGeneratingTaskId = 0;
uni.setStorageSync('singGenerating', false);
uni.setStorageSync('singGeneratingTaskId', 0);
if (that.singPollTimer) {
clearTimeout(that.singPollTimer);
that.singPollTimer = null;
}
uni.showToast({
title: '处理超时,请稍后查看',
icon: 'none',
duration: 2000
});
return;
}
SingGenerateTask(task_id).then(res => {
if (res.code == 1) {
const data = res.data;
if (data.status == 'succeeded') {
that.singGenerating = false;
that.singGeneratingTaskId = 0;
uni.setStorageSync('singGenerating', false);
uni.setStorageSync('singGeneratingTaskId', 0);
uni.showToast({
title: '生成成功!',
icon: 'success',
duration: 2000
});
// 可以在这里刷新聊天消息或显示视频
} else if (data.status == 'failed') {
that.singGenerating = false;
that.singGeneratingTaskId = 0;
uni.setStorageSync('singGenerating', false);
uni.setStorageSync('singGeneratingTaskId', 0);
uni.showToast({
title: data.error_msg || '生成失败',
icon: 'none',
duration: 2000
});
} else {
// 继续轮询
that.singGenerating = true;
that.singGeneratingTaskId = task_id;
uni.setStorageSync('singGenerating', true);
uni.setStorageSync('singGeneratingTaskId', task_id);
that.singPollTimer = setTimeout(doPoll, 4000);
}
} else {
that.singGenerating = false;
that.singGeneratingTaskId = 0;
uni.setStorageSync('singGenerating', false);
uni.setStorageSync('singGeneratingTaskId', 0);
if (that.singPollTimer) {
clearTimeout(that.singPollTimer);
that.singPollTimer = null;
}
uni.showToast({
title: res.msg,
icon: 'none',
duration: 2000
});
}
}).catch(err => {
that.singGenerating = false;
that.singGeneratingTaskId = 0;
uni.setStorageSync('singGenerating', false);
uni.setStorageSync('singGeneratingTaskId', 0);
uni.showToast({
title: '查询失败,请重试',
icon: 'none',
duration: 2000
});
});
};
doPoll();
},
getDanceGenerateTask(task_id) {
const that = this;
let attempts = 0;
const maxAttempts = 225; // 15分钟左右 (225*4s)
const doPoll = () => {
if (!that.danceGenerating || that.danceGeneratingTaskId !== task_id) {
return;
}
attempts++;
if (attempts > maxAttempts) {
that.danceGenerating = false;
that.danceGeneratingTaskId = 0;
uni.setStorageSync('danceGenerating', false);
uni.setStorageSync('danceGeneratingTaskId', 0);
if (that.dancePollTimer) {
clearTimeout(that.dancePollTimer);
that.dancePollTimer = null;
}
uni.showToast({ title: '处理超时,请稍后查看', icon: 'none', duration: 2000 });
return;
}
DanceGenerateTask(task_id).then(res => {
if (res.code == 1) {
const data = res.data;
if (data.status == 'succeeded') {
that.danceGenerating = false;
that.danceGeneratingTaskId = 0;
uni.setStorageSync('danceGenerating', false);
uni.setStorageSync('danceGeneratingTaskId', 0);
uni.showToast({ title: '生成成功!', icon: 'success', duration: 2000 });
that.getDanceHistory();
} else if (data.status == 'failed') {
that.danceGenerating = false;
that.danceGeneratingTaskId = 0;
uni.setStorageSync('danceGenerating', false);
uni.setStorageSync('danceGeneratingTaskId', 0);
uni.showToast({ title: data.error_msg || '生成失败', icon: 'none', duration: 2000 });
} else {
that.danceGenerating = true;
that.danceGeneratingTaskId = task_id;
uni.setStorageSync('danceGenerating', true);
uni.setStorageSync('danceGeneratingTaskId', task_id);
that.dancePollTimer = setTimeout(doPoll, 4000);
}
} else {
that.danceGenerating = false;
that.danceGeneratingTaskId = 0;
uni.setStorageSync('danceGenerating', false);
uni.setStorageSync('danceGeneratingTaskId', 0);
if (that.dancePollTimer) {
clearTimeout(that.dancePollTimer);
that.dancePollTimer = null;
}
uni.showToast({ title: res.msg, icon: 'none', duration: 2000 });
}
}).catch(() => {
that.danceGenerating = false;
that.danceGeneratingTaskId = 0;
uni.setStorageSync('danceGenerating', false);
uni.setStorageSync('danceGeneratingTaskId', 0);
if (that.dancePollTimer) {
clearTimeout(that.dancePollTimer);
that.dancePollTimer = null;
}
uni.showToast({ title: '查询失败,请重试', icon: 'none', duration: 2000 });
});
};
doPoll();
},
cancelSingGeneration() {
this.singGenerating = false;
this.singGeneratingTaskId = 0;
uni.setStorageSync('singGenerating', false);
uni.setStorageSync('singGeneratingTaskId', 0);
if (this.singPollTimer) {
clearTimeout(this.singPollTimer);
this.singPollTimer = null;
}
uni.showToast({ title: '已取消生成', icon: 'none', duration: 1500 });
},
cancelDanceGeneration() {
this.danceGenerating = false;
this.danceGeneratingTaskId = 0;
uni.setStorageSync('danceGenerating', false);
uni.setStorageSync('danceGeneratingTaskId', 0);
if (this.dancePollTimer) {
clearTimeout(this.dancePollTimer);
this.dancePollTimer = null;
}
uni.showToast({ title: '已取消生成', icon: 'none', duration: 1500 });
},
// 处理青少年模式状态改变事件
handleUnderAgeStatusChange(status) {
this.underAgeEnabled = status;
},
// 检查本地存储的青少年模式状态
checkUnderAgeStatus() {
const storedStatus = uni.getStorageSync('underAgeEnabled');
console.log('storedStatus:', storedStatus)
this.underAgeEnabled = !!storedStatus; // 确保是布尔值
},
async checkPermission() {
try {
const settingRes = await new Promise((resolve, reject) => {
uni.getSetting({
success: (res) => resolve(res),
fail: (err) => reject(err)
});
});
console.log(settingRes.authSetting, '当前授权状态');
// 检查摄像头权限
if (!settingRes.authSetting['scope.camera']) {
try {
await new Promise((resolve, reject) => {
uni.authorize({
scope: 'scope.camera',
success: () => resolve(),
fail: (err) => {
console.warn('摄像头授权失败', err);
// 用户拒绝授权,可以引导用户手动开启
this.showPermissionGuide('摄像头');
reject(err);
}
});
});
} catch (err) {
console.warn('摄像头授权失败', err);
}
}
// 检查麦克风权限 - 注意:微信小程序是 scope.recordMicrophone 而不是 scope.record
if (!settingRes.authSetting['scope.record']) {
try {
await new Promise((resolve, reject) => {
uni.authorize({
scope: 'scope.record', // 修正权限名称
success: () => resolve(),
fail: (err) => {
console.warn('麦克风授权失败', err);
this.showPermissionGuide('麦克风');
reject(err);
}
});
});
} catch (err) {
console.warn('麦克风授权失败', err);
}
}
} catch (error) {
console.error('获取授权设置失败', error);
}
},
// 显示权限引导提示
showPermissionGuide(permissionType) {
uni.showModal({
title: '需要授权',
content: `需要${permissionType}权限才能正常使用相关功能,请在设置中开启`,
showCancel: true,
cancelText: '取消',
confirmText: '去设置',
success: (modalRes) => {
if (modalRes.confirm) {
// 跳转到小程序设置页面
uni.openSetting({
success: (settingRes) => {
console.log('用户设置结果', settingRes);
}
});
}
}
});
},
getUserBasic() {
GetUserBasic({}).then(res => {
if (res.code == 1) {
let data = res.data
const conversationStore = useConversationStore();
let listener = {
nickname: data.username,
headImage: data.avatar,
openid: data.wxapp_openid,
}
conversationStore.setUserInfo(listener); //存储用户信息
this.getBobbiesList = data
console.log('data', data)
this.getServerData();
} else {
uni.showToast({
title: res.msg,
icon: 'none',
position: 'top'
})
}
}).catch(() => {})
},
loverInit() {
LoverInit().then(res => {
if (res.code == 1) {
this.getUserBasic()
}
})
},
loverBasic() {
LoverBasic().then(res => {
if (res.code == 1) {
this.loverBasicList = res.data
uni.setStorageSync('loverBasicList', res.data)
} else {
uni.showToast({
title: res.msg,
icon: 'none',
position: 'top'
})
}
})
},
getServerData() {
//模拟从服务器获取数据时的延时
setTimeout(() => {
//模拟服务器返回数据,如果数据格式和标准格式不同,需自行按下面的格式拼接
let todayPercent = this.getBobbiesList.bond_today_percent / 100 ? this.getBobbiesList.bond_today_percent /
100 : 0
console.log('todayPercent', todayPercent)
let res = {
series: [{
name: "正确率",
color: "#9F47FF",
data: todayPercent
}]
};
this.chartData = JSON.parse(JSON.stringify(res));
}, 500);
},
calculateProgressWidth() {
if (this.getBobbiesList.intimacy == null || this.getBobbiesList.next_level_intimacy == null) {
return 0;
}
// 根据比例计算实际宽度确保不超过最大宽度160rpx
const actualWidth = (this.getBobbiesList.intimacy / this.getBobbiesList.next_level_intimacy) * 160;
const result = Math.min(actualWidth, 160);
// 打印计算过程和结果
console.log('最终结果:', result);
return result;
},
calculateProgressPercent() {
if (this.getBobbiesList.intimacy == null || this.getBobbiesList.next_level_intimacy == null) {
return 0;
}
const percent = (this.getBobbiesList.intimacy / this.getBobbiesList.next_level_intimacy) * 100;
return Math.min(percent, 100);
},
togenerate() {
this.loverBasic()
this.loverInit()
this.$nextTick(() => {
this.getServerData();
});
},
// 定制专属女友
tocreate() {
uni.navigateTo({
url: '/pages/create/index'
});
},
tocreateLover() {
uni.navigateTo({
url: '/pages/create/lover'
});
},
toties() {
uni.navigateTo({
url: '/pages/index/ties'
});
},
tointimacy() {
uni.navigateTo({
url: '/pages/index/intimacy'
});
},
toreplacement1(){
uni.navigateTo({
url: '/pages/chat/test1'
});
},
toreplacement() {
// if (this.getBobbiesList.level >= 4) {
// uni.navigateTo({
// url: '/pages/index/replacement'
// });
// } else {
// uni.showToast({
// title: '达到Lv.4才可以解锁换装',
// icon: 'none',
// position: 'top'
// });
// }
uni.navigateTo({
url: '/pages/index/replacement'
});
// uni.navigateTo({
// url: '/pages/chat/test0'
// });
},
tochat() {
uni.navigateTo({
url: '/pages/chat/index'
});
},
tophone() {
// if (this.getBobbiesList.level >= 2) {
// uni.navigateTo({
// url: '/pages/chat/phone'
// });
// } else {
// uni.showToast({
// title: '达到Lv.2才可以解锁电话',
// icon: 'none',
// position: 'top'
// });
// }
uni.navigateTo({
url: '/pages/chat/phone'
});
},
togift() {
uni.navigateTo({
url: '/pages/index/gift'
});
},
todynamics() {
uni.navigateTo({
url: '/pages/index/dynamics'
});
},
// ========== 聊天相关方法 ==========
// 初始化聊天会话
initChatSession() {
// 获取用户头像
const userInfo = uni.getStorageSync('userinfo');
this.chatUserAvatar = userInfo?.avatar || '/static/images/avatar.png';
// 获取恋人头像 - 优先使用当前选中的形象,否则使用基本信息中的头像
if (this.currentLookImageUrl) {
this.chatLoverAvatar = this.currentLookImageUrl;
} else if (this.loverBasicList?.image_url) {
this.chatLoverAvatar = this.loverBasicList.image_url;
} else {
this.chatLoverAvatar = '/static/images/avatar.png';
}
uni.showLoading({ title: '加载中...' });
SessionInit().then(res => {
uni.hideLoading();
if (res.code === 1) {
this.chatSessionId = res.data.session_id;
// 加载历史消息只显示最近10条
const allMessages = res.data.messages || [];
this.chatMessages = allMessages.slice(-10).map(msg => ({
role: msg.role === 'lover' ? 'ai' : 'user',
content: msg.content
}));
// 滚动到底部
this.$nextTick(() => {
this.scrollChatToBottom();
});
} else {
uni.showToast({ title: res.msg || '加载失败', icon: 'none' });
}
}).catch(err => {
uni.hideLoading();
console.error('初始化会话失败:', err);
uni.showToast({ title: '网络错误', icon: 'none' });
});
},
// 发送聊天消息
sendChatMessage() {
if (!this.chatInputText.trim()) {
return;
}
if (this.chatSending) {
return;
}
if (!this.chatSessionId) {
uni.showToast({ title: '会话未初始化', icon: 'none' });
return;
}
const userMessage = this.chatInputText.trim();
this.chatInputText = '';
// 让输入框失去焦点,收起键盘
uni.hideKeyboard();
// 添加用户消息到列表
this.chatMessages.push({
role: 'user',
content: userMessage
});
// 滚动到底部
this.$nextTick(() => {
this.scrollChatToBottom();
});
// 添加"思考中"提示
const thinkingIndex = this.chatMessages.length;
this.chatMessages.push({
role: 'ai',
content: '思考中...'
});
this.chatSending = true;
// 发送到后端
SessionSend({
session_id: this.chatSessionId,
message: userMessage
}).then(res => {
this.chatSending = false;
if (res.code === 1) {
// 移除"思考中"
this.chatMessages.splice(thinkingIndex, 1);
// 添加 AI 回复
this.chatMessages.push({
role: 'ai',
content: res.data.reply || '...'
});
// 滚动到底部
this.$nextTick(() => {
this.scrollChatToBottom();
});
} else {
// 移除"思考中"
this.chatMessages.splice(thinkingIndex, 1);
uni.showToast({ title: res.msg || '发送失败', icon: 'none' });
}
}).catch(err => {
this.chatSending = false;
// 移除"思考中"
this.chatMessages.splice(thinkingIndex, 1);
console.error('发送消息失败:', err);
uni.showToast({ title: '发送失败', icon: 'none' });
});
},
// 输入框失去焦点时的处理
onInputBlur() {
// 确保输入框恢复正常状态
this.$nextTick(() => {
// 强制重新渲染,确保样式正确
});
},
// 滚动聊天到底部
scrollChatToBottom() {
this.chatIntoView = '';
this.$nextTick(() => {
this.chatIntoView = 'chat-bottom';
});
},
// ========== 首页形象栏相关方法 ==========
// 加载首页形象栏
loadHomeLooks() {
OutfitList({}).then(res => {
if (res && res.code == 1 && res.data) {
this.homeLooksList = res.data.looks || [];
// 如果有形象栏数据,默认选中第一个
if (this.homeLooksList.length > 0) {
this.selectedLookId = this.homeLooksList[0].id;
this.currentLookImageUrl = this.homeLooksList[0].image_url;
// 同时更新聊天头像
this.chatLoverAvatar = this.homeLooksList[0].image_url;
}
}
}).catch(err => {
console.error('加载形象栏失败:', err);
});
},
// 切换形象
switchLook(look) {
if (!look || !look.id) return;
// 调用后端 API 切换形象
uni.request({
url: baseURLPy + '/outfit/looks/use/' + look.id,
method: 'POST',
header: {
'Authorization': 'Bearer ' + uni.getStorageSync("token")
},
success: (res) => {
if (res.data && res.data.code === 1) {
this.selectedLookId = look.id;
this.currentLookImageUrl = look.image_url;
// 更新聊天页面的头像
this.chatLoverAvatar = look.image_url;
// 更新女友基本信息(刷新图片)
this.loverBasic();
uni.showToast({
title: '形象切换成功',
icon: 'success',
duration: 1500
});
} else {
uni.showToast({
title: res.data.msg || '切换失败',
icon: 'none',
duration: 1500
});
}
},
fail: (err) => {
console.error('切换形象失败:', err);
uni.showToast({
title: '切换失败,请重试',
icon: 'none',
duration: 1500
});
}
});
},
// ========== 音乐库相关方法 ==========
// 切换唱歌Tab
switchSingTab(tab) {
this.singCurrentTab = tab;
if (tab === 'library' && this.musicLibraryList.length === 0) {
this.loadMusicLibrary();
}
},
// 加载音乐库
loadMusicLibrary(page = 1) {
uni.request({
url: baseURLPy + '/music/library',
method: 'GET',
data: {
page: page,
page_size: this.musicLibraryPageSize
},
header: {
'Authorization': 'Bearer ' + uni.getStorageSync("token")
},
success: (res) => {
if (res.data && res.data.code === 1) {
const data = res.data.data;
if (page === 1) {
this.musicLibraryList = data.list || [];
} else {
this.musicLibraryList = this.musicLibraryList.concat(data.list || []);
}
this.musicLibraryTotal = data.total || 0;
this.musicLibraryPage = page;
this.musicLibraryHasMore = this.musicLibraryList.length < this.musicLibraryTotal;
}
},
fail: (err) => {
console.error('加载音乐库失败:', err);
uni.showToast({ title: '加载失败', icon: 'none' });
}
});
},
// 加载更多音乐
loadMoreMusic() {
if (!this.musicLibraryHasMore) return;
this.loadMusicLibrary(this.musicLibraryPage + 1);
},
// 显示添加音乐弹窗
showAddMusicModal() {
this.showMusicModal = true;
this.musicForm = {
title: '',
artist: '',
music_url: '',
cover_url: '',
duration: null
};
},
// 关闭添加音乐弹窗
closeMusicModal() {
this.showMusicModal = false;
},
// 提交音乐
submitMusic() {
if (!this.musicForm.title || !this.musicForm.music_url) {
uni.showToast({ title: '请填写歌曲标题和音乐链接', icon: 'none' });
return;
}
uni.showLoading({ title: '提交中...' });
uni.request({
url: baseURLPy + '/music/add-link',
method: 'POST',
data: this.musicForm,
header: {
'Authorization': 'Bearer ' + uni.getStorageSync("token"),
'Content-Type': 'application/json'
},
success: (res) => {
uni.hideLoading();
if (res.data && res.data.code === 1) {
uni.showToast({ title: '添加成功', icon: 'success' });
this.closeMusicModal();
this.loadMusicLibrary(1); // 刷新列表
} else {
uni.showToast({ title: res.data.msg || '添加失败', icon: 'none' });
}
},
fail: (err) => {
uni.hideLoading();
console.error('添加音乐失败:', err);
uni.showToast({ title: '添加失败', icon: 'none' });
}
});
},
// 从音乐库选择歌曲
selectMusicFromLibrary(music) {
// 记录播放次数
uni.request({
url: baseURLPy + '/music/' + music.id + '/play',
method: 'POST',
header: {
'Authorization': 'Bearer ' + uni.getStorageSync("token")
}
});
// 使用音乐库的歌曲生成视频
if (this.singGenerating) {
uni.showToast({ title: '视频生成中,请稍候...', icon: 'none', duration: 2000 });
return;
}
// 这里需要调用唱歌API传入音乐库的音乐URL
uni.showModal({
title: '提示',
content: `确定让她唱《${music.title}》吗?`,
success: (modalRes) => {
if (modalRes.confirm) {
// TODO: 调用唱歌API使用 music.music_url
uni.showToast({ title: '功能开发中...', icon: 'none' });
}
}
});
},
// 切换音乐点赞
toggleMusicLike(music) {
uni.request({
url: baseURLPy + '/music/' + music.id + '/like',
method: 'POST',
header: {
'Authorization': 'Bearer ' + uni.getStorageSync("token")
},
success: (res) => {
if (res.data && res.data.code === 1) {
music.is_liked = res.data.data.is_liked;
music.like_count = res.data.data.like_count;
}
},
fail: (err) => {
console.error('点赞失败:', err);
}
});
}
}
}
</script>
<style>
page {
background: #FFFFFF;
}
</style>
<style>
/* ========== 礼物页面样式 ========== */
.gift-page {
display: flex;
flex-direction: column;
height: 100%;
background: linear-gradient(180deg, #FFE5F5 0%, #F5F5FF 100%);
}
.gift-header {
padding: 40rpx 30rpx 20rpx;
text-align: center;
}
.gift-header-title {
font-size: 48rpx;
font-weight: bold;
color: #9F47FF;
margin-bottom: 10rpx;
}
.gift-header-desc {
font-size: 28rpx;
color: #999;
margin-bottom: 20rpx;
}
.gift-intimacy-bar {
background: rgba(255, 255, 255, 0.8);
border-radius: 20rpx;
padding: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(159, 71, 255, 0.1);
}
.gift-intimacy-label {
font-size: 24rpx;
color: #666;
margin-bottom: 10rpx;
}
.gift-intimacy-info {
display: flex;
align-items: center;
gap: 15rpx;
}
.gift-intimacy-level {
font-size: 28rpx;
font-weight: bold;
color: #9F47FF;
flex-shrink: 0;
}
.gift-intimacy-progress {
flex: 1;
height: 20rpx;
background: #E5E5E5;
border-radius: 10rpx;
overflow: hidden;
}
.gift-intimacy-progress-bar {
height: 100%;
background: linear-gradient(90deg, #FF6B9D 0%, #9F47FF 100%);
border-radius: 10rpx;
transition: width 0.3s ease;
}
.gift-intimacy-value {
font-size: 24rpx;
color: #666;
flex-shrink: 0;
}
.gift-scroll {
flex: 1;
padding: 0 20rpx;
}
.gift-list {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding-bottom: 40rpx;
}
.gift-item {
width: 32%;
margin-bottom: 20rpx;
}
.gift-item-inner {
background: #fff;
border-radius: 16rpx;
padding: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(159, 71, 255, 0.15);
display: flex;
flex-direction: column;
align-items: center;
transition: all 0.3s ease;
}
.gift-item-inner:active {
transform: scale(0.95);
box-shadow: 0 2rpx 8rpx rgba(159, 71, 255, 0.2);
}
.gift-item-img {
width: 120rpx;
height: 120rpx;
margin-bottom: 10rpx;
}
.gift-item-name {
font-size: 26rpx;
color: #333;
text-align: center;
margin-bottom: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
.gift-item-price {
font-size: 24rpx;
color: #FF6B9D;
font-weight: bold;
margin-bottom: 4rpx;
}
.gift-item-intimacy {
font-size: 22rpx;
color: #9F47FF;
background: rgba(159, 71, 255, 0.1);
padding: 4rpx 12rpx;
border-radius: 8rpx;
}
/* 礼物详情弹窗 */
.gift-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-end;
z-index: 1000;
}
.gift-modal-content {
width: 100%;
background: linear-gradient(180deg, #FFE5F5 0%, #FFFFFF 100%);
border-radius: 40rpx 40rpx 0 0;
padding: 40rpx 30rpx;
padding-bottom: 60rpx;
}
.gift-modal-header {
display: flex;
justify-content: center;
align-items: center;
position: relative;
margin-bottom: 30rpx;
}
.gift-modal-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.gift-modal-close {
position: absolute;
right: 0;
width: 40rpx;
height: 40rpx;
}
.gift-modal-body {
display: flex;
align-items: center;
margin-bottom: 30rpx;
padding: 20rpx;
background: rgba(255, 255, 255, 0.6);
border-radius: 20rpx;
}
.gift-modal-img {
width: 160rpx;
height: 160rpx;
margin-right: 30rpx;
flex-shrink: 0;
}
.gift-modal-info {
flex: 1;
}
.gift-modal-name {
font-size: 32rpx;
font-weight: bold;
color: #9F47FF;
margin-bottom: 10rpx;
}
.gift-modal-intimacy {
font-size: 26rpx;
color: #FF6B9D;
margin-bottom: 10rpx;
background: rgba(255, 107, 157, 0.1);
padding: 6rpx 12rpx;
border-radius: 8rpx;
display: inline-block;
}
.gift-modal-price {
font-size: 36rpx;
font-weight: bold;
color: #FF0000;
}
.gift-modal-quantity {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding: 20rpx;
background: rgba(255, 255, 255, 0.6);
border-radius: 16rpx;
}
.gift-modal-quantity-label {
font-size: 30rpx;
color: #333;
}
.gift-modal-quantity-control {
display: flex;
align-items: center;
gap: 20rpx;
}
.gift-quantity-btn {
width: 50rpx;
height: 50rpx;
}
.gift-quantity-input {
width: 80rpx;
height: 50rpx;
text-align: center;
font-size: 28rpx;
color: #333;
background: #fff;
border-radius: 8rpx;
}
.gift-modal-balance {
font-size: 28rpx;
color: #666;
text-align: center;
margin-bottom: 20rpx;
}
.gift-modal-btn {
padding: 28rpx 0;
background: linear-gradient(135deg, #FF6B9D 0%, #C239B3 100%);
color: #fff;
text-align: center;
border-radius: 16rpx;
font-size: 32rpx;
font-weight: bold;
box-shadow: 0 8rpx 20rpx rgba(255, 107, 157, 0.3);
}
.gift-modal-btn:active {
transform: scale(0.98);
}
/* ========== 旧的礼物样式(已废弃,保留以防需要) ========== */
.gift-tab .body {
position: relative;
}
.gift-tab .back {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1624rpx;
display: block;
}
.gift-tab .list {
position: relative;
}
.gift-tab .list_logo {
margin: -140rpx 0 0 0;
width: 100%;
height: 556rpx;
display: block;
}
.gift-tab .list_content {
position: relative;
}
.gift-tab .list_backA {
position: absolute;
left: 0;
right: 0;
top: 0;
width: 100%;
display: block;
}
.gift-tab .list_module {
position: relative;
padding: 46rpx 34rpx;
}
.gift-tab .list_detail {
position: relative;
}
.gift-tab .list_title {
font-weight: 400;
font-size: 32rpx;
color: #CC9DFF;
line-height: 50rpx;
}
.gift-tab .list_body {
position: relative;
margin: 54rpx 0 0 0;
}
.gift-tab .gift-container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.gift-tab .list_item {
position: relative;
width: 30%;
margin-bottom: 40rpx;
}
.gift-tab .list_scrollDetail {
position: relative;
}
.gift-tab .list_image {
position: relative;
width: 176rpx;
height: 176rpx;
}
.gift-tab .list_border {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: block;
}
.gift-tab .list_picture {
position: relative;
width: 120rpx;
height: 120rpx;
display: block;
}
.gift-tab .list_name {
margin: 8rpx 0 0 0;
font-weight: 500;
font-size: 30rpx;
color: #F661B5;
line-height: 50rpx;
text-align: center;
}
.gift-tab .list_btn {
margin: 150rpx 0 0 0;
padding: 24rpx 0;
font-weight: 400;
font-size: 32rpx;
color: #FFFFFF;
line-height: 50rpx;
background: linear-gradient(135deg, #F0BDDD 0%, #A89CF7 100%);
border-radius: 12rpx;
}
.gift-tab .alert {
position: fixed;
width: 100%;
height: 100%;
left: 0;
bottom: 0;
box-sizing: border-box;
background: rgba(0, 0, 0, 0.2);
z-index: 2;
}
.gift-tab .alert_content {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(180deg, #EDD6F5 0%, #FFFFFF 100%);
border-radius: 40rpx 40rpx 0rpx 0rpx;
padding: 46rpx 40rpx 68rpx 40rpx;
}
.gift-tab .alert_title {
position: relative;
font-weight: 500;
font-size: 36rpx;
color: #333333;
line-height: 50rpx;
text-align: center;
}
.gift-tab .alert_title image {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 30rpx;
height: 30rpx;
margin: auto 0;
}
.gift-tab .alert_module {
position: relative;
margin: 68rpx 0 58rpx 0;
}
.gift-tab .alert_image {
width: 256rpx;
height: 256rpx;
display: block;
}
.gift-tab .alert_alert {
position: relative;
margin: 0 0 0 62rpx;
}
.gift-tab .alert_item {
position: relative;
margin: 0 0 0 44rpx;
}
.gift-tab .alert_item image {
width: 36rpx;
height: 48rpx;
}
.gift-tab .alert_name {
margin: 0 14rpx;
font-weight: 500;
font-size: 32rpx;
color: #9779FA;
line-height: 50rpx;
}
.gift-tab .alert_explain {
margin: 18rpx 0 0 0;
font-weight: 400;
font-size: 26rpx;
color: #9779FA;
line-height: 50rpx;
}
.gift-tab .alert_add {
margin: 0 0 0 80rpx;
font-weight: 400;
font-size: 26rpx;
color: #9779FA;
line-height: 50rpx;
}
.gift-tab .alert_money {
margin: 28rpx 0 0 0;
font-weight: 700;
font-size: 44rpx;
color: #FF0000;
line-height: 50rpx;
}
.gift-tab .alert_money text {
font-size: 28rpx;
line-height: 65rpx;
}
.gift-tab .alert_number {
position: relative;
}
.gift-tab .alert_number_title {
font-weight: 400;
font-size: 32rpx;
color: #222222;
line-height: 50rpx;
}
.gift-tab .alert_number_content {
margin: 0 0 0 100rpx;
position: relative;
}
.gift-tab .alert_number_content image {
width: 64rpx;
height: 64rpx;
}
.gift-tab .alert_number_content input {
padding: 0 8rpx;
width: 78rpx;
font-weight: 500;
font-size: 28rpx;
color: #000000;
line-height: 40rpx;
text-align: center;
}
.gift-tab .alert_number_value {
color: #333333;
}
.gift-tab .alert_balance {
margin: 78rpx 0 26rpx 0;
font-weight: 500;
font-size: 30rpx;
color: #909090;
line-height: 50rpx;
text-align: center;
}
.gift-tab .alert_btn {
position: relative;
padding: 26rpx 112rpx;
font-weight: 500;
font-size: 30rpx;
line-height: 50rpx;
border-radius: 12rpx;
color: #FFFFFF;
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%), #D8D8D8;
}
.body {
position: relative;
}
.back {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 656rpx;
background: linear-gradient(180deg, #C7A7FF 0%, #F7F7F7 100%);
}
.body {
position: relative;
margin: 44rpx 0 0 0;
padding: 0 40rpx 200rpx 40rpx;
}
.list {
position: relative;
width: 100%;
}
.list_logo {
position: relative;
width: 100%;
height: 1164rpx;
display: block;
border-radius: 12rpx;
}
.list_content {
position: absolute;
left: 0;
right: 0;
bottom: 98rpx;
margin: 0 60rpx;
padding: 22rpx 0;
font-weight: 600;
font-size: 34rpx;
color: #FFFFFF;
line-height: 50rpx;
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
border-radius: 12rpx;
}
.list_title {
font-weight: 600;
font-size: 34rpx;
color: #FFFFFF;
line-height: 50rpx;
}
.list_next {
margin: 0 0 0 8rpx;
width: 34rpx;
height: 8rpx;
}
.list_module {
position: absolute;
left: 0;
right: 0;
bottom: 36rpx;
margin: 0 auto;
}
.list_item {
margin: 0 22rpx 0 0;
width: 60rpx;
height: 4rpx;
background: #FFFFFF;
border-radius: 5rpx;
}
.list_item:nth-child(3) {
margin: 0 0 0 0;
}
.list_active {
width: 90rpx;
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%), #FFFFFF;
}
.global-block-mask {
position: fixed;
top: calc(80rpx + var(--status-bar-height));
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.45);
z-index: 900;
display: flex;
align-items: center;
justify-content: center;
}
.global-block-card {
width: 520rpx;
padding: 36rpx 28rpx;
background: rgba(255, 255, 255, 0.95);
border-radius: 16rpx;
text-align: center;
}
.global-block-title {
font-size: 34rpx;
font-weight: 600;
color: #111111;
line-height: 48rpx;
}
.global-block-sub {
margin-top: 14rpx;
font-size: 26rpx;
color: #666666;
line-height: 38rpx;
}
.global-block-actions {
margin-top: 22rpx;
display: flex;
justify-content: center;
}
.global-block-btn {
padding: 14rpx 28rpx;
border-radius: 999rpx;
border: 1px solid rgba(0, 0, 0, 0.12);
font-size: 26rpx;
color: #333333;
background: rgba(255, 255, 255, 0.95);
}
.backA {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 656rpx;
background: linear-gradient(180deg, #FFE3E9 0%, #F7F7F7 100%);
}
.bodyA {
position: relative;
padding: 0 40rpx 200rpx 40rpx;
z-index: 2;
}
.listA {
position: relative;
height: 1082rpx;
}
.listA_back {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: block;
border-radius: 20rpx;
}
.listA_content {
position: relative;
padding: 52rpx 32rpx;
}
.listA_title {
font-weight: 700;
font-size: 50rpx;
color: #FFFFFF;
line-height: 50rpx;
}
.listA_module {
position: relative;
margin: 34rpx 0 0 0;
}
.listA_dight {
position: relative;
width: 45rpx;
height: 40rpx;
}
.listA_dight image {
width: 100%;
height: 100%;
}
.listA_grade {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
font-weight: 400;
font-size: 26rpx;
color: #FFFFFF;
line-height: 50rpx;
}
.listA_item {
position: relative;
margin: 0 0 0 10rpx;
}
.listA_level {
font-weight: 500;
font-size: 30rpx;
color: #FFFFFF;
line-height: 50rpx;
}
.listA_level text {
font-size: 26rpx;
}
.listA_line {
position: relative;
width: 160rpx;
height: 12rpx;
background: rgba(255, 255, 255, 0.5);
border-radius: 88rpx;
}
.listA_line text {
position: absolute;
left: 5rpx;
top: 0;
bottom: 0;
margin: auto 0;
width: 40rpx;
height: 8rpx;
background: linear-gradient(310deg, #D14499 0%, #9F47FF 100%);
border-radius: 88rpx;
}
.listA_progress {
position: relative;
margin: 40rpx 0 0 0;
}
.listA_charts {
position: relative;
width: 176rpx;
height: 93px;
}
.listA_number {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
font-weight: 700;
font-size: 36rpx;
color: #F661B5;
line-height: 50rpx;
}
.listA_number text {
font-size: 28rpx;
color: #FFFFFF;
}
.listA_text {
margin: -20rpx 0 0 0;
width: 176rpx;
font-size: 28rpx;
color: #FFFFFF;
line-height: 50rpx;
text-align: center;
}
.listA_dynamics {
margin: 20rpx 0 0 0;
width: 100rpx;
height: 100rpx;
display: block;
border-radius: 10rpx;
}
.listA_detail {
position: absolute;
bottom: 0;
left: 0;
}
.listA_white {
/* position: absolute;
bottom: -6rpx;
left: -6rpx;
width: 300rpx; */
position: relative;
/* bottom: -8rpx; */
bottom: -12rpx;
/* left: -8rpx; */
left: -10rpx;
width: 300rpx;
}
.listA_chat {
/* position: relative; */
position: absolute;
left: 0;
bottom: 0;
margin: 0 0 5rpx 5rpx;
width: 230rpx;
}
.listA_backA {
position: relative;
width: 100%;
display: block;
border-radius: 0 20rpx 20rpx 0;
}
.listA_exchange {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
font-weight: 500;
font-size: 32rpx;
color: #FFFFFF;
line-height: 50rpx;
}
.listA_chatA {
margin: 0 16rpx 0 0;
width: 41rpx;
height: 41rpx;
}
.listA_opt {
position: absolute;
right: 25rpx;
bottom: 13rpx;
}
.listA_phone {
margin: 0 25rpx 0 0;
width: 88rpx;
height: 88rpx;
}
.listA_gift {
position: relative;
padding: 12rpx 14rpx;
font-weight: 500;
font-size: 32rpx;
color: #FFFFFF;
line-height: 50rpx;
background: rgba(255, 255, 255, 0.3);
border-radius: 22rpx;
}
.listA_gift image {
width: 66rpx;
height: 66rpx;
}
.bannerA {
position: relative;
margin: 30rpx 0 0 0;
width: 100%;
}
.bannerA image {
width: 100%;
height: 184rpx;
display: block;
border-radius: 0 20rpx 20rpx 0;
}
.bannerA_text {
position: absolute;
left: 102rpx;
top: 0;
bottom: 0;
margin: auto;
font-weight: 700;
font-size: 40rpx;
color: #FEFEFE;
line-height: 50rpx;
}
.promptA {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
}
.promptA text {
padding: 22rpx 42rpx;
font-weight: 500;
font-size: 32rpx;
color: #FFFFFF;
line-height: 50rpx;
background: #9779FA;
border-radius: 20rpx;
}
/* 青少年模式遮罩层样式 */
.underage-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 9;
}
.underage-text {
font-size: 36rpx;
font-weight: bold;
color: white;
text-align: center;
padding: 40rpx;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 20rpx;
}
/* Tab 栏样式 - 固定在顶部 */
.tab-container {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background: rgba(255, 255, 255, 0.95);
padding: 10rpx 0;
padding-top: calc(10rpx + var(--status-bar-height));
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.tab-scroll {
white-space: nowrap;
}
.tab-list {
display: inline-flex;
padding: 0 20rpx;
}
.tab-item {
display: inline-block;
padding: 15rpx 30rpx;
margin: 0 10rpx;
font-size: 28rpx;
color: #666;
border-radius: 30rpx;
transition: all 0.3s;
}
.tab-item.active {
color: #fff;
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
font-weight: bold;
}
.tab-name {
white-space: nowrap;
}
/* Swiper 容器样式 */
.swiper-container {
flex: 1;
width: 100%;
height: calc(100vh - 120rpx);
margin-top: calc(80rpx + var(--status-bar-height));
overflow-x: hidden;
}
.swiper-scroll {
height: 100%;
width: 100%;
overflow-x: hidden;
}
.swiper-content {
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
overflow-x: hidden;
}
/* 功能页面样式 */
.feature-page {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40rpx;
box-sizing: border-box;
}
.feature-icon {
font-size: 120rpx;
margin-bottom: 30rpx;
}
.feature-title {
font-size: 40rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.feature-desc {
font-size: 28rpx;
color: #666;
margin-bottom: 40rpx;
text-align: center;
}
.feature-btn {
padding: 20rpx 60rpx;
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
color: #fff;
border-radius: 50rpx;
font-size: 30rpx;
}
.feature-scroll {
width: 100%;
max-height: 60vh;
margin-top: 20rpx;
box-sizing: border-box;
overflow-x: hidden;
}
/* 历史视频样式 */
.history-section {
width: 100%;
padding: 20rpx;
margin-bottom: 30rpx;
box-sizing: border-box;
}
.history-section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
padding-left: 10rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 0;
padding-left: 0;
}
.section-more {
font-size: 26rpx;
color: #0053FA;
padding: 8rpx 16rpx;
border-radius: 20rpx;
background: rgba(0, 83, 250, 0.08);
}
.history-list {
width: 100%;
}
.history-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 25rpx;
margin-bottom: 15rpx;
background: linear-gradient(135deg, rgba(159, 71, 255, 0.1) 0%, rgba(0, 83, 250, 0.1) 100%);
border-radius: 15rpx;
border: 1px solid rgba(159, 71, 255, 0.2);
}
.history-info {
flex: 1;
display: flex;
flex-direction: column;
}
.history-title {
font-size: 30rpx;
color: #333;
font-weight: 500;
margin-bottom: 8rpx;
}
.history-date {
font-size: 24rpx;
color: #999;
}
.history-actions {
display: flex;
gap: 15rpx;
}
.history-btn {
padding: 12rpx 30rpx;
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
color: #fff;
border-radius: 25rpx;
font-size: 26rpx;
}
/* 歌曲列表样式 */
.song-list {
width: 100%;
padding: 20rpx;
box-sizing: border-box;
}
.song-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 25rpx;
margin-bottom: 20rpx;
background: #f5f5f5;
border-radius: 15rpx;
}
.song-info {
flex: 1;
}
.song-title {
font-size: 30rpx;
color: #333;
}
.song-icon {
width: 40rpx;
height: 40rpx;
}
/* 跳舞表单样式 */
.dance-form {
width: 100%;
padding: 20rpx;
}
.dance-input {
width: 100%;
min-height: 200rpx;
padding: 20rpx;
background: #f5f5f5;
border-radius: 15rpx;
font-size: 28rpx;
margin-bottom: 30rpx;
box-sizing: border-box;
}
.dance-btn {
width: 100%;
padding: 25rpx;
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
color: #fff;
text-align: center;
border-radius: 50rpx;
font-size: 30rpx;
box-sizing: border-box;
}
/* 商城列表样式 */
.shop-list {
width: 100%;
padding: 20rpx;
}
.shop-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
margin-bottom: 20rpx;
background: #f5f5f5;
border-radius: 15rpx;
}
.shop-info {
flex: 1;
}
.shop-title {
font-size: 32rpx;
color: #333;
font-weight: bold;
margin-bottom: 10rpx;
}
.shop-desc {
font-size: 24rpx;
color: #999;
}
.shop-price {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.price-value {
font-size: 36rpx;
color: #ff4444;
font-weight: bold;
margin-bottom: 10rpx;
}
.shop-buy-btn {
padding: 10rpx 30rpx;
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
color: #fff;
border-radius: 30rpx;
font-size: 24rpx;
}
/* 商城外部链接页面样式 */
.shop-link-page {
cursor: pointer;
transition: all 0.3s ease;
}
.shop-link-page:active {
transform: scale(0.98);
}
.shop-link-banner {
width: 80%;
max-width: 500rpx;
margin: 40rpx auto;
padding: 40rpx;
background: linear-gradient(135deg, #FFE5F0 0%, #E5F0FF 100%);
border-radius: 20rpx;
border: 2rpx solid #9F47FF;
box-shadow: 0 8rpx 20rpx rgba(159, 71, 255, 0.2);
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
}
.shop-banner-img {
width: 200rpx;
height: 200rpx;
}
.shop-link-text {
font-size: 32rpx;
color: #9F47FF;
font-weight: bold;
text-align: center;
}
.shop-external-btn {
background: linear-gradient(135deg, #FF6B9D 0%, #C239B3 100%);
font-weight: bold;
box-shadow: 0 4rpx 12rpx rgba(255, 107, 157, 0.3);
}
/* ========== 新商城页面全屏设计样式 ========== */
.shop-page {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
.shop-hero {
flex: 1;
position: relative;
width: 100%;
overflow: hidden;
cursor: pointer;
}
.shop-hero-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg,
#FFE5F5 0%,
#F5E5FF 25%,
#E5F0FF 50%,
#FFE5F0 75%,
#F5F0FF 100%);
animation: gradientShift 10s ease infinite;
}
@keyframes gradientShift {
0%, 100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
/* 装饰圆圈 */
.shop-decoration {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 1;
}
.shop-circle {
position: absolute;
border-radius: 50%;
background: radial-gradient(circle, rgba(159, 71, 255, 0.15) 0%, transparent 70%);
animation: float 6s ease-in-out infinite;
}
.shop-circle-1 {
width: 300rpx;
height: 300rpx;
top: 10%;
left: -10%;
animation-delay: 0s;
}
.shop-circle-2 {
width: 200rpx;
height: 200rpx;
top: 60%;
right: -5%;
animation-delay: 2s;
}
.shop-circle-3 {
width: 250rpx;
height: 250rpx;
bottom: 15%;
left: 10%;
animation-delay: 4s;
}
.shop-circle-4 {
width: 180rpx;
height: 180rpx;
top: 30%;
right: 15%;
animation-delay: 1s;
}
.shop-hero-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 40rpx;
z-index: 2;
box-sizing: border-box;
}
/* 顶部标签 */
.shop-hot-badge {
display: flex;
align-items: center;
gap: 8rpx;
padding: 12rpx 28rpx;
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
border-radius: 30rpx;
margin-bottom: 40rpx;
box-shadow: 0 8rpx 20rpx rgba(255, 215, 0, 0.4);
animation: pulse 2s ease-in-out infinite;
}
.shop-hot-icon {
font-size: 32rpx;
}
.shop-hot-text {
font-size: 28rpx;
font-weight: bold;
color: #FFFFFF;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
}
/* 商城图标 */
.shop-icon-wrapper {
position: relative;
width: 200rpx;
height: 200rpx;
margin-bottom: 40rpx;
}
.shop-main-icon {
width: 100%;
height: 100%;
position: relative;
z-index: 2;
animation: bounce 2s ease-in-out infinite;
}
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-20rpx);
}
}
.shop-icon-glow {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 240rpx;
height: 240rpx;
background: radial-gradient(circle, rgba(159, 71, 255, 0.3) 0%, transparent 70%);
border-radius: 50%;
animation: glow 2s ease-in-out infinite;
z-index: 1;
}
@keyframes glow {
0%, 100% {
opacity: 0.5;
transform: translate(-50%, -50%) scale(1);
}
50% {
opacity: 1;
transform: translate(-50%, -50%) scale(1.2);
}
}
/* 标题 */
.shop-hero-title {
font-size: 64rpx;
font-weight: bold;
background: linear-gradient(135deg, #9F47FF 0%, #FF6B9D 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-align: center;
margin-bottom: 20rpx;
letter-spacing: 4rpx;
}
.shop-hero-subtitle {
font-size: 28rpx;
color: #666;
text-align: center;
margin-bottom: 50rpx;
}
/* 特色网格 */
.shop-features-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
width: 100%;
max-width: 500rpx;
margin-bottom: 50rpx;
}
.shop-feature-card {
display: flex;
flex-direction: column;
align-items: center;
gap: 10rpx;
padding: 30rpx 20rpx;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10rpx);
border-radius: 20rpx;
border: 2rpx solid rgba(159, 71, 255, 0.2);
box-shadow: 0 4rpx 12rpx rgba(159, 71, 255, 0.1);
transition: all 0.3s ease;
}
.shop-feature-card:active {
transform: scale(0.95);
}
.shop-feature-emoji {
font-size: 48rpx;
}
.shop-feature-label {
font-size: 26rpx;
color: #333;
font-weight: 500;
}
/* 进入按钮 */
.shop-enter-button {
display: flex;
align-items: center;
gap: 15rpx;
padding: 28rpx 60rpx;
background: linear-gradient(135deg, #9F47FF 0%, #FF6B9D 100%);
border-radius: 60rpx;
box-shadow: 0 12rpx 30rpx rgba(159, 71, 255, 0.4);
margin-bottom: 30rpx;
transition: all 0.3s ease;
}
.shop-enter-button:active {
transform: scale(0.95);
box-shadow: 0 8rpx 20rpx rgba(159, 71, 255, 0.3);
}
.shop-enter-text {
font-size: 36rpx;
font-weight: bold;
color: #FFFFFF;
letter-spacing: 2rpx;
}
.shop-enter-arrow {
font-size: 36rpx;
font-weight: bold;
color: #FFFFFF;
}
/* 提示文字 */
.shop-hint {
display: flex;
align-items: center;
gap: 8rpx;
padding: 12rpx 24rpx;
background: rgba(159, 71, 255, 0.1);
backdrop-filter: blur(10rpx);
border-radius: 30rpx;
animation: float 3s ease-in-out infinite;
}
.shop-hint-icon {
font-size: 24rpx;
}
.shop-hint-text {
font-size: 24rpx;
color: #666;
}
/* 短剧列表样式 */
.drama-list {
width: 100%;
padding: 20rpx;
}
.drama-item {
display: flex;
margin-bottom: 30rpx;
background: #f5f5f5;
border-radius: 15rpx;
overflow: hidden;
}
.drama-cover {
width: 200rpx;
height: 150rpx;
flex-shrink: 0;
}
.drama-info {
flex: 1;
padding: 20rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.drama-title {
font-size: 30rpx;
color: #333;
font-weight: bold;
margin-bottom: 10rpx;
}
.drama-desc {
font-size: 24rpx;
color: #999;
margin-bottom: 15rpx;
}
.drama-play-btn {
align-self: flex-start;
padding: 8rpx 25rpx;
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
color: #fff;
border-radius: 20rpx;
font-size: 24rpx;
}
/* 短剧外部链接特殊样式 */
.drama-link-item {
position: relative;
background: linear-gradient(135deg, #FFE5F0 0%, #E5F0FF 100%);
border: 2rpx solid #9F47FF;
box-shadow: 0 4rpx 12rpx rgba(159, 71, 255, 0.2);
cursor: pointer;
transition: all 0.3s ease;
}
.drama-link-item:active {
transform: scale(0.98);
box-shadow: 0 2rpx 8rpx rgba(159, 71, 255, 0.3);
}
.drama-banner {
width: 200rpx;
height: 150rpx;
border-right: 2rpx solid rgba(159, 71, 255, 0.2);
}
.drama-link-badge {
position: absolute;
top: 10rpx;
left: 10rpx;
padding: 4rpx 12rpx;
background: linear-gradient(135deg, #FF6B9D 0%, #C239B3 100%);
color: #fff;
font-size: 20rpx;
border-radius: 8rpx;
font-weight: bold;
z-index: 1;
}
.drama-external-btn {
background: linear-gradient(135deg, #FF6B9D 0%, #C239B3 100%);
font-weight: bold;
}
/* ========== 新短剧页面全屏海报样式 ========== */
.drama-page {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: #000;
overflow: hidden;
}
.drama-hero {
flex: 1;
position: relative;
width: 100%;
overflow: hidden;
cursor: pointer;
}
.drama-hero-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.drama-hero-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(180deg,
rgba(0, 0, 0, 0.3) 0%,
rgba(0, 0, 0, 0.5) 50%,
rgba(0, 0, 0, 0.8) 100%);
z-index: 1;
}
.drama-hero-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 40rpx;
z-index: 2;
box-sizing: border-box;
}
/* 热门标签 */
.drama-hot-badge {
display: flex;
align-items: center;
gap: 8rpx;
padding: 12rpx 24rpx;
background: linear-gradient(135deg, #FF6B9D 0%, #FF4757 100%);
border-radius: 30rpx;
margin-bottom: 40rpx;
box-shadow: 0 8rpx 20rpx rgba(255, 107, 157, 0.4);
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
.drama-hot-icon {
font-size: 32rpx;
}
.drama-hot-text {
font-size: 28rpx;
font-weight: bold;
color: #FFFFFF;
}
/* 标题 */
.drama-hero-title {
font-size: 72rpx;
font-weight: bold;
color: #FFFFFF;
text-align: center;
margin-bottom: 20rpx;
text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.5);
letter-spacing: 4rpx;
}
.drama-hero-subtitle {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.9);
text-align: center;
margin-bottom: 50rpx;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.5);
}
/* 标签组 */
.drama-tags {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 15rpx;
margin-bottom: 60rpx;
padding: 0 20rpx;
}
.drama-tag {
padding: 12rpx 24rpx;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10rpx);
border: 1rpx solid rgba(255, 255, 255, 0.3);
border-radius: 30rpx;
font-size: 26rpx;
color: #FFFFFF;
font-weight: 500;
}
/* 播放按钮 */
.drama-play-button {
display: flex;
align-items: center;
gap: 15rpx;
padding: 28rpx 60rpx;
background: linear-gradient(135deg, #FF6B9D 0%, #C239B3 100%);
border-radius: 60rpx;
box-shadow: 0 12rpx 30rpx rgba(255, 107, 157, 0.5);
margin-bottom: 40rpx;
transition: all 0.3s ease;
}
.drama-play-button:active {
transform: scale(0.95);
box-shadow: 0 8rpx 20rpx rgba(255, 107, 157, 0.4);
}
.drama-play-icon {
font-size: 36rpx;
color: #FFFFFF;
font-weight: bold;
}
.drama-play-text {
font-size: 36rpx;
font-weight: bold;
color: #FFFFFF;
letter-spacing: 2rpx;
}
/* 提示文字 */
.drama-hint {
display: flex;
align-items: center;
gap: 8rpx;
padding: 12rpx 24rpx;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10rpx);
border-radius: 30rpx;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-10rpx);
}
}
.drama-hint-icon {
font-size: 24rpx;
}
.drama-hint-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.8);
}
/* 底部特色推荐 */
.drama-features {
display: flex;
justify-content: space-around;
padding: 30rpx 20rpx;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(20rpx);
border-top: 1rpx solid rgba(255, 255, 255, 0.1);
z-index: 3;
}
.drama-feature-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 10rpx;
}
.drama-feature-icon {
font-size: 48rpx;
}
.drama-feature-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.8);
}
/* 自定义细线指示器样式 */
.swiper-indicator {
position: fixed;
bottom: 20rpx;
left: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
gap: 8rpx;
z-index: 100;
padding: 0 20rpx;
transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
transform: translateY(0);
opacity: 1;
}
.swiper-indicator.indicator-hidden {
transform: translateY(100%);
opacity: 0;
pointer-events: none;
}
.indicator-line {
flex: 1;
height: 3rpx;
background: rgba(0, 0, 0, 0.1);
border-radius: 2rpx;
transition: all 0.3s ease;
}
.indicator-line.active {
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
height: 4rpx;
}
/* 聊天预览容器样式 */
.chat-preview-container {
width: 100%;
height: 100%;
padding: 40rpx;
box-sizing: border-box;
}
.chat-preview-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.chat-preview-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.chat-preview-btn {
padding: 15rpx 30rpx;
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
color: #fff;
border-radius: 30rpx;
font-size: 26rpx;
}
.chat-preview-list {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 40rpx;
}
.chat-preview-tip {
font-size: 28rpx;
color: #999;
margin-bottom: 60rpx;
text-align: center;
}
.chat-preview-image {
width: 300rpx;
height: 300rpx;
border-radius: 50%;
overflow: hidden;
margin-bottom: 30rpx;
box-shadow: 0 8rpx 20rpx rgba(159, 71, 255, 0.2);
}
.chat-preview-image image {
width: 100%;
height: 100%;
}
.chat-preview-name {
font-size: 40rpx;
font-weight: bold;
color: #333;
margin-bottom: 15rpx;
}
.chat-preview-desc {
font-size: 28rpx;
color: #666;
}
/* 换装页面样式 - 和谐配色 */
.outfit-container {
width: 100%;
min-height: 100%;
padding: 30rpx;
background: linear-gradient(180deg, #f8f9fa 0%, #ffffff 100%);
box-sizing: border-box;
}
.outfit-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
margin-bottom: 30rpx;
border-bottom: 1rpx solid #e8e8e8;
}
.outfit-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.outfit-count {
font-size: 26rpx;
color: #666;
padding: 8rpx 20rpx;
background: #f5f5f5;
border-radius: 20rpx;
}
.outfit-loading {
display: flex;
justify-content: center;
align-items: center;
padding: 100rpx 0;
color: #999;
font-size: 28rpx;
}
.outfit-content {
width: 100%;
padding-bottom: 40rpx;
}
.outfit-section {
margin-bottom: 50rpx;
background: #fff;
border-radius: 20rpx;
padding: 30rpx 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.section-title {
font-size: 30rpx;
font-weight: 500;
color: #555;
margin-bottom: 25rpx;
padding-left: 10rpx;
border-left: 4rpx solid #8a7cff;
}
.section-title-top {
font-size: 70rpx !important;
font-weight: bold !important;
color: #000 !important;
text-align: center !important;
margin: 0 0 40rpx 0 !important;
padding: 20rpx 0 !important;
background: #f0f0f0 !important;
border-radius: 12rpx !important;
display: block !important;
width: 100% !important;
}
/* 网格容器 - 使用固定宽度 */
.outfit-grid-wrapper {
width: 100%;
overflow: hidden;
text-align: left;
}
.outfit-item-wrapper {
float: left;
width: 220rpx;
margin-right: 15rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
}
.outfit-grid-inner {
position: relative;
width: 100%;
height: 280rpx;
border-radius: 12rpx;
overflow: hidden;
background: #fff;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.15);
transition: all 0.3s ease;
border: 5rpx solid #ddd;
box-sizing: border-box;
}
.outfit-grid-item-selected .outfit-grid-inner {
border: 5rpx solid #ff0000 !important;
box-shadow: 0 4rpx 20rpx rgba(255, 0, 0, 0.4) !important;
}
.outfit-grid-img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.outfit-check-mark {
position: absolute !important;
top: 8rpx !important;
left: 8rpx !important;
width: 40rpx !important;
height: 40rpx !important;
background: #8a7cff;
color: #fff;
font-size: 28rpx;
font-weight: bold;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 10 !important;
pointer-events: none;
line-height: 1;
}
.outfit-grid-badge {
position: absolute;
top: 8rpx;
right: 8rpx;
padding: 8rpx 16rpx;
background: rgba(255, 0, 0, 0.9);
color: #fff;
font-size: 28rpx;
font-weight: bold;
border-radius: 12rpx;
z-index: 2;
}
.outfit-grid-lock {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 60rpx;
opacity: 0.8;
z-index: 3;
pointer-events: none;
}
.outfit-generate-btn {
margin: 20rpx 0;
padding: 28rpx 0;
background: linear-gradient(135deg, #8a7cff 0%, #6b5ce7 100%);
color: #fff;
text-align: center;
border-radius: 16rpx;
font-size: 32rpx;
font-weight: 500;
box-shadow: 0 8rpx 20rpx rgba(138, 124, 255, 0.3);
transition: all 0.3s ease;
}
.outfit-generate-btn:active {
transform: scale(0.98);
box-shadow: 0 4rpx 12rpx rgba(138, 124, 255, 0.3);
}
/* ========== 聊天 Swiper 页面样式 ========== */
.chat-swiper-page {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
min-height: 0;
background: #f5f5f5;
}
.chat-message-scroll {
flex: 1;
height: 0;
min-height: 0;
padding: 20rpx;
padding-left: 30rpx;
padding-right: 30rpx;
box-sizing: border-box;
overflow-y: auto;
}
.chat-message-list {
width: 100%;
}
.chat-message-item {
display: flex;
margin-bottom: 30rpx;
align-items: flex-start;
}
.message-left {
flex-direction: row;
justify-content: flex-start;
}
.message-right {
flex-direction: row-reverse;
justify-content: flex-start;
padding-left: 0;
}
.chat-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
flex-shrink: 0;
}
.chat-message-bubble {
max-width: 420rpx; /* 减小气泡宽度,给头像留更多空间 */
padding: 20rpx 25rpx;
border-radius: 20rpx;
margin: 0 20rpx;
}
.message-left .chat-message-bubble {
background: #fff;
border-top-left-radius: 5rpx;
}
.message-right .chat-message-bubble {
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
}
.chat-message-text {
font-size: 28rpx;
line-height: 1.6;
word-wrap: break-word;
}
.message-left .chat-message-text {
color: #333;
}
.message-right .chat-message-text {
color: #fff;
}
.chat-input-bar {
display: flex;
align-items: center;
padding: 10rpx 20rpx;
padding-bottom: calc(10rpx + env(safe-area-inset-bottom));
background: #fff;
border-top: 1rpx solid #e5e5e5;
box-sizing: border-box;
flex-shrink: 0; /* 防止被压缩 */
}
.chat-message-input {
flex: 1;
height: 80rpx;
padding: 0 25rpx;
background: #f5f5f5;
border-radius: 40rpx;
font-size: 28rpx;
}
.chat-send-btn {
margin-left: 20rpx;
padding: 20rpx 40rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
color: #fff;
border-radius: 40rpx;
font-size: 28rpx;
flex-shrink: 0;
}
/* ========== 底部 Tab 栏隐藏动画 ========== */
.bottom-tab-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
transform: translateY(0);
opacity: 1;
}
.bottom-tab-bar.tab-bar-hidden {
transform: translateY(100%); /* 向下移动,完全隐藏 */
opacity: 0; /* 透明度为0 */
pointer-events: none; /* 禁用点击事件 */
}
.modal-mask {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.modal-card {
width: 92%;
max-height: 80%;
background: #ffffff;
border-radius: 16rpx;
overflow: hidden;
box-sizing: border-box;
}
.modal-header {
position: relative;
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx;
border-bottom: 1px solid rgba(0,0,0,0.06);
}
.modal-title {
position: absolute;
left: 50%;
transform: translateX(-50%);
max-width: 70%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 30rpx;
font-weight: 600;
color: #333333;
}
.modal-close {
position: absolute;
right: 24rpx;
font-size: 26rpx;
color: #666666;
padding: 6rpx 12rpx;
}
.modal-list {
max-height: 60vh;
padding: 12rpx 24rpx 24rpx;
box-sizing: border-box;
}
.modal-item {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 12rpx;
padding: 16rpx 0;
border-bottom: 1px solid rgba(0,0,0,0.06);
}
.modal-item-info {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
padding-right: 12rpx;
}
.modal-item-title {
font-size: 28rpx;
color: #333333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.modal-retry {
min-width: 112rpx;
max-width: 140rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 24rpx;
color: #ffffff;
background: #1DB954;
padding: 10rpx 16rpx;
border-radius: 12rpx;
text-align: center;
box-sizing: border-box;
margin-left: 12rpx;
}
.modal-item-date {
font-size: 22rpx;
color: #999999;
margin-top: 6rpx;
}
.modal-item-status {
font-size: 22rpx;
color: #666666;
margin-top: 6rpx;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.modal-item-actions {
flex: 0 0 auto;
display: flex;
justify-content: flex-end;
margin-left: auto;
padding-left: 12rpx;
}
.modal-play {
min-width: 112rpx;
max-width: 140rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 24rpx;
color: #ffffff;
background: #F661B5;
padding: 10rpx 16rpx;
border-radius: 12rpx;
text-align: center;
box-sizing: border-box;
}
.modal-video {
width: 100%;
height: 420rpx;
background: #000000;
}
/* ========== 首页卡片式设计样式 ========== */
.home-page {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: linear-gradient(180deg, #F8F3FF 0%, #FFF5F9 50%, #F0F8FF 100%);
padding: 20rpx 30rpx;
box-sizing: border-box;
overflow-y: auto;
}
/* 顶部操作栏 */
.home-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 0;
margin-bottom: 20rpx;
}
.home-header-btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
padding: 10rpx 15rpx;
background: rgba(255, 255, 255, 0.8);
border-radius: 16rpx;
transition: all 0.3s ease;
box-shadow: 0 2rpx 8rpx rgba(159, 71, 255, 0.1);
}
.home-header-btn:active {
transform: scale(0.95);
background: rgba(255, 255, 255, 1);
}
.home-header-icon {
width: 40rpx;
height: 40rpx;
}
.home-header-text {
font-size: 22rpx;
color: #666;
}
.home-header-spacer {
flex: 1;
}
/* 选择互动对象标题 */
.home-title {
display: flex;
align-items: center;
justify-content: center;
padding: 15rpx 0;
margin-bottom: 25rpx;
position: relative;
}
.home-title-icon {
font-size: 24rpx;
color: #9F47FF;
margin: 0 10rpx;
}
.home-title-text {
font-size: 28rpx;
font-weight: 500;
color: #333;
}
.home-title-more {
position: absolute;
right: 0;
font-size: 24rpx;
color: #9F47FF;
padding: 6rpx 16rpx;
background: rgba(159, 71, 255, 0.1);
border-radius: 20rpx;
}
/* 主卡片区域 */
.home-card {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30rpx;
padding: 0 10rpx;
}
.home-card-inner {
width: 100%;
max-width: 600rpx;
background: linear-gradient(180deg, #FFFFFF 0%, #FFF5F9 100%);
border-radius: 30rpx;
padding: 30rpx;
box-shadow: 0 12rpx 40rpx rgba(159, 71, 255, 0.15);
display: flex;
flex-direction: column;
align-items: center;
position: relative;
overflow: hidden;
}
.home-card-inner::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(159, 71, 255, 0.05) 0%, transparent 70%);
pointer-events: none;
}
/* 女友名字 */
.home-card-name {
font-size: 40rpx;
font-weight: bold;
color: #9F47FF;
margin-bottom: 20rpx;
text-align: center;
z-index: 1;
}
/* 女友图片 */
.home-card-image {
width: 400rpx;
height: 500rpx;
border-radius: 20rpx;
margin-bottom: 25rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.12);
z-index: 1;
}
/* 性格标签 */
.home-card-tags {
display: flex;
gap: 15rpx;
margin-bottom: 25rpx;
z-index: 1;
}
.home-card-tag {
padding: 8rpx 20rpx;
background: linear-gradient(135deg, rgba(159, 71, 255, 0.15) 0%, rgba(255, 107, 157, 0.15) 100%);
color: #9F47FF;
font-size: 24rpx;
border-radius: 20rpx;
border: 1rpx solid rgba(159, 71, 255, 0.3);
}
/* 对话气泡 */
.home-card-bubble {
width: 100%;
padding: 20rpx 25rpx;
background: rgba(159, 71, 255, 0.08);
border-radius: 20rpx;
border-left: 4rpx solid #9F47FF;
margin-bottom: 30rpx;
z-index: 1;
}
.home-card-bubble-text {
font-size: 26rpx;
color: #555;
line-height: 1.6;
}
/* 底部操作按钮 */
.home-card-actions {
width: 100%;
display: flex;
gap: 20rpx;
z-index: 1;
}
.home-card-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx;
border-radius: 50rpx;
transition: all 0.3s ease;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.home-card-btn:active {
transform: scale(0.95);
}
.home-card-btn-home {
flex: 0 0 auto;
width: 100rpx;
background: linear-gradient(135deg, #FFE5F0 0%, #E5F0FF 100%);
border: 2rpx solid rgba(159, 71, 255, 0.2);
}
.home-card-btn-icon {
width: 40rpx;
height: 40rpx;
}
.home-card-btn-chat {
background: linear-gradient(135deg, #9F47FF 0%, #FF6B9D 100%);
box-shadow: 0 8rpx 20rpx rgba(159, 71, 255, 0.3);
}
.home-card-btn-text {
font-size: 30rpx;
font-weight: bold;
color: #FFFFFF;
}
/* 底部头像轮播 */
.home-avatars {
display: flex;
justify-content: center;
gap: 20rpx;
padding: 20rpx 0;
margin-bottom: 20rpx;
}
.home-avatar-item {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
overflow: hidden;
border: 3rpx solid #FFFFFF;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
cursor: pointer;
}
.home-avatar-item:active {
transform: scale(1.15);
}
/* 选中状态 - 紫色边框 */
.home-avatar-item.home-avatar-active {
border-color: #9F47FF;
border-width: 4rpx;
box-shadow: 0 4rpx 16rpx rgba(159, 71, 255, 0.4);
transform: scale(1.05);
}
.home-avatar-img {
width: 100%;
height: 100%;
}
/* 好感度信息 */
.home-intimacy {
width: 100%;
padding: 20rpx 30rpx;
background: rgba(255, 255, 255, 0.9);
border-radius: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(159, 71, 255, 0.1);
display: flex;
align-items: center;
gap: 15rpx;
}
.home-intimacy-level {
font-size: 28rpx;
font-weight: bold;
color: #9F47FF;
flex-shrink: 0;
}
.home-intimacy-bar {
flex: 1;
height: 16rpx;
background: #E5E5E5;
border-radius: 8rpx;
overflow: hidden;
position: relative;
}
.home-intimacy-progress {
height: 100%;
background: linear-gradient(90deg, #FF6B9D 0%, #9F47FF 100%);
border-radius: 8rpx;
transition: width 0.5s ease;
}
.home-intimacy-text {
font-size: 24rpx;
color: #666;
flex-shrink: 0;
}
/* ========== 首页商城横幅样式 ========== */
.home-shop-banner {
position: relative;
width: 100%;
padding: 0;
margin-bottom: 20rpx;
border-radius: 20rpx;
overflow: hidden;
cursor: pointer;
box-shadow: 0 8rpx 24rpx rgba(159, 71, 255, 0.2);
}
.home-shop-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #FFE5F5 0%, #F5E5FF 50%, #E5F0FF 100%);
z-index: 1;
}
.home-shop-circle {
position: absolute;
border-radius: 50%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.3) 0%, transparent 70%);
}
.home-shop-circle-1 {
width: 120rpx;
height: 120rpx;
top: -30rpx;
left: -20rpx;
}
.home-shop-circle-2 {
width: 80rpx;
height: 80rpx;
bottom: -20rpx;
right: 50rpx;
}
.home-shop-circle-3 {
width: 60rpx;
height: 60rpx;
top: 20rpx;
right: -10rpx;
}
.home-shop-content {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: 25rpx 30rpx;
z-index: 2;
}
.home-shop-left {
display: flex;
align-items: center;
gap: 20rpx;
flex: 1;
}
.home-shop-icon-wrapper {
position: relative;
width: 80rpx;
height: 80rpx;
flex-shrink: 0;
}
.home-shop-icon {
width: 100%;
height: 100%;
border-radius: 50%;
box-shadow: 0 4rpx 12rpx rgba(159, 71, 255, 0.3);
}
.home-shop-badge {
position: absolute;
top: -8rpx;
right: -8rpx;
padding: 4rpx 10rpx;
background: linear-gradient(135deg, #FF6B9D 0%, #FF4757 100%);
color: #FFFFFF;
font-size: 18rpx;
font-weight: bold;
border-radius: 10rpx;
box-shadow: 0 2rpx 8rpx rgba(255, 107, 157, 0.4);
animation: pulse 2s ease-in-out infinite;
}
.home-shop-text {
flex: 1;
}
.home-shop-title {
display: flex;
align-items: center;
gap: 8rpx;
margin-bottom: 6rpx;
}
.home-shop-title-main {
font-size: 32rpx;
font-weight: bold;
background: linear-gradient(135deg, #9F47FF 0%, #FF6B9D 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.home-shop-title-emoji {
font-size: 28rpx;
}
.home-shop-desc {
font-size: 24rpx;
color: #666;
}
.home-shop-button {
display: flex;
align-items: center;
gap: 8rpx;
padding: 15rpx 28rpx;
background: linear-gradient(135deg, #9F47FF 0%, #FF6B9D 100%);
border-radius: 40rpx;
box-shadow: 0 4rpx 12rpx rgba(159, 71, 255, 0.3);
flex-shrink: 0;
transition: all 0.3s ease;
}
.home-shop-banner:active .home-shop-button {
transform: scale(0.95);
}
.home-shop-button-text {
font-size: 26rpx;
font-weight: bold;
color: #FFFFFF;
}
.home-shop-button-arrow {
font-size: 26rpx;
font-weight: bold;
color: #FFFFFF;
}
/* 闪光效果 */
.home-shop-shine {
position: absolute;
top: 0;
left: -100%;
width: 50%;
height: 100%;
background: linear-gradient(90deg,
transparent 0%,
rgba(255, 255, 255, 0.3) 50%,
transparent 100%);
transform: skewX(-20deg);
animation: shine 3s ease-in-out infinite;
z-index: 3;
pointer-events: none;
}
@keyframes shine {
0% {
left: -100%;
}
20%, 100% {
left: 150%;
}
}
</style>
<style>
/* ========== 唱歌页面音乐库样式 ========== */
.sing-page {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: #f5f5f5;
}
.sing-tabs {
display: flex;
background: #fff;
padding: 10rpx 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.sing-tab-item {
flex: 1;
text-align: center;
padding: 20rpx 0;
font-size: 28rpx;
color: #666;
position: relative;
transition: all 0.3s;
}
.sing-tab-active {
color: #9F47FF;
font-weight: bold;
}
.sing-tab-active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background: linear-gradient(90deg, #9F47FF 0%, #FF6B9D 100%);
border-radius: 2rpx;
}
.sing-scroll {
flex: 1;
height: 0;
}
.sing-content {
padding: 20rpx;
}
/* 添加音乐按钮 */
.music-add-btns {
display: flex;
justify-content: center;
margin-bottom: 30rpx;
}
.music-add-btn {
display: flex;
align-items: center;
gap: 10rpx;
padding: 20rpx 40rpx;
background: linear-gradient(135deg, #9F47FF 0%, #FF6B9D 100%);
color: #fff;
border-radius: 50rpx;
box-shadow: 0 8rpx 20rpx rgba(159, 71, 255, 0.3);
transition: all 0.3s;
}
.music-add-btn:active {
transform: scale(0.95);
}
.music-add-icon {
font-size: 32rpx;
}
.music-add-text {
font-size: 28rpx;
font-weight: bold;
}
/* 音乐库列表 */
.music-library-list {
width: 100%;
}
.music-library-item {
display: flex;
background: #fff;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
}
.music-library-cover {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
overflow: hidden;
position: relative;
flex-shrink: 0;
margin-right: 20rpx;
}
.music-cover-img {
width: 100%;
height: 100%;
}
.music-play-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
}
.music-play-overlay:active {
background: rgba(0, 0, 0, 0.5);
}
.music-play-icon {
font-size: 60rpx;
color: #fff;
}
.music-library-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.music-library-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.music-library-artist {
font-size: 24rpx;
color: #999;
margin-bottom: 12rpx;
}
.music-library-meta {
display: flex;
gap: 20rpx;
font-size: 22rpx;
color: #666;
}
.music-meta-item {
display: flex;
align-items: center;
}
/* 加载更多 */
.music-load-more {
text-align: center;
padding: 30rpx;
color: #9F47FF;
font-size: 28rpx;
}
/* 空状态 */
.music-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 40rpx;
}
.music-empty-icon {
font-size: 120rpx;
margin-bottom: 20rpx;
}
.music-empty-text {
font-size: 32rpx;
color: #666;
margin-bottom: 10rpx;
}
.music-empty-desc {
font-size: 24rpx;
color: #999;
}
/* 添加音乐弹窗 */
.music-modal-card {
max-height: 80vh;
overflow-y: auto;
}
.music-modal-body {
padding: 30rpx;
}
.music-form-item {
margin-bottom: 30rpx;
}
.music-form-label {
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
font-weight: 500;
}
.music-form-input {
width: 100%;
padding: 20rpx;
background: #f5f5f5;
border-radius: 12rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.music-form-textarea {
width: 100%;
min-height: 150rpx;
padding: 20rpx;
background: #f5f5f5;
border-radius: 12rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.music-modal-btn {
padding: 28rpx 0;
background: linear-gradient(135deg, #9F47FF 0%, #FF6B9D 100%);
color: #fff;
text-align: center;
border-radius: 16rpx;
font-size: 32rpx;
font-weight: bold;
box-shadow: 0 8rpx 20rpx rgba(159, 71, 255, 0.3);
margin-top: 20rpx;
}
.music-modal-btn:active {
transform: scale(0.98);
}
</style>