Ai_GirlFriend/xuniYou/pages/index/index.vue

2495 lines
58 KiB
Vue
Raw Normal View History

2026-01-31 19:15:41 +08:00
<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">
2026-02-01 10:56:35 +08:00
<!-- Tab - 固定在顶部 -->
<view class="tab-container" v-if="getBobbiesList.reg_step == 4">
2026-02-02 20:08:28 +08:00
<scroll-view class="tab-scroll" scroll-x="true" show-scrollbar="false" :scroll-into-view="tabIntoView" scroll-with-animation="true">
2026-02-01 10:56:35 +08:00
<view class="tab-list">
<view
v-for="(tab, index) in tabs"
:key="index"
2026-02-02 20:08:28 +08:00
:id="'tab-' + index"
2026-02-01 10:56:35 +08:00
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) -->
2026-02-01 10:56:35 +08:00
<swiper-item>
<scroll-view class="swiper-scroll" scroll-y="true">
2026-01-31 19:15:41 +08:00
<view class="backA"></view>
<view class="bodyA">
<view class="listA">
<image v-if="loverBasicList" class="listA_back"
:src="loverBasicList.image_url ? loverBasicList.image_url : 'https://nvlovers.oss-cn-qingdao.aliyuncs.com/uploads/20251226/008a06f2c3fccdac714f25e6577ccdc4.png'"
mode="aspectFill"></image>
<view class="listA_content">
<view class="listA_title">你好{{ getBobbiesList.nickname ? getBobbiesList.nickname : '匿名' }}</view>
<view class="listA_module fa" @click="tointimacy">
<view class="listA_dight">
<image src="/static/images/index_intimate.png" mode="aspectFill"></image>
<view class="listA_grade faj">{{ getBobbiesList.level ? getBobbiesList.level : 0 }}
</view>
</view>
<view class="listA_item">
<view class="listA_level fa">
Lv.{{ getBobbiesList.level ? getBobbiesList.level : 0 }}<text>{{
getBobbiesList.intimacy ? getBobbiesList.intimacy : 0 }}/{{
getBobbiesList.next_level_intimacy ? getBobbiesList.next_level_intimacy : 0
}}</text>
</view>
<view class="listA_line"><text :style="{ width: calculateProgressWidth() + 'rpx' }"></text></view>
</view>
</view>
<view class="listA_progress" @click="toties">
<view>
<view class="listA_charts">
2026-02-01 10:56:35 +08:00
<qiun-data-charts v-if="chartData.series" type="arcbar" :opts="opts" :chartData="chartData" />
2026-01-31 19:15:41 +08:00
<view class="listA_number faj">
{{ getBobbiesList.bond_today ? getBobbiesList.bond_today : 0 }}<text>/{{
getBobbiesList.bond_today_all ? getBobbiesList.bond_today_all : 0
}}</text>
</view>
</view>
</view>
<view class="listA_text">每日牵绊</view>
</view>
<image @click="todynamics" class="listA_dynamics" src="/static/images/dynamics_logo.png"
mode="aspectFill"></image>
</view>
<!-- 操作菜单 -->
<view class="listA_detail">
<view class="" style="position: relative;">
<image class="listA_white" src="/static/images/white.png" mode="widthFix"></image>
<view class="listA_chat" @click="tochat">
<image class="listA_backA"
src="https://nvlovers.oss-cn-qingdao.aliyuncs.com/uploads/20251226/024550444b3bd11063b3adf42bcbd5a0.png"
mode="widthFix"></image>
<view class="listA_exchange faj">
<image class="listA_chatA" src="/static/images/index_chat.png" mode="widthFix"></image>
去聊天
</view>
</view>
</view>
</view>
<view class="listA_opt fa">
<image class="listA_phone" @click="tophone" src="/static/images/index_phone.png" mode="widthFix"></image>
<view class="listA_gift fa" @click="togift">
<image src="/static/images/index_gift.png" mode="widthFix"></image>
礼物匣
</view>
</view>
</view>
<view class="bannerA" style="margin-bottom: 40rpx;">
<image @click="toreplacement"
src="https://nvlovers.oss-cn-qingdao.aliyuncs.com/uploads/20251226/1b394cf9d5e731d8f34fc267ed59ac3d.png"
mode="aspectFill">
</image>
<view class="bannerA_text faj">装束设置</view>
</view>
</view>
2026-02-01 10:56:35 +08:00
</scroll-view>
</swiper-item>
<!-- 聊天页面 (index 1) - 嵌入完整聊天功能 -->
2026-02-01 10:56:35 +08:00
<swiper-item>
<view class="chat-swiper-page">
<!-- 聊天消息列表 -->
<scroll-view
class="chat-message-scroll"
scroll-y="true"
2026-02-02 20:08:28 +08:00
: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>
2026-02-02 20:08:28 +08:00
<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"
2026-02-02 20:08:28 +08:00
:adjust-position="true"
:hold-keyboard="false"
@confirm="sendChatMessage"
2026-02-02 20:08:28 +08:00
@blur="onInputBlur"
/>
<view class="chat-send-btn" @click="sendChatMessage">
<text>发送</text>
</view>
2026-02-01 10:56:35 +08:00
</view>
</view>
2026-02-01 10:56:35 +08:00
</swiper-item>
<!-- 唱歌页面 (index 2) -->
2026-02-01 10:56:35 +08:00
<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">
2026-02-02 20:08:28 +08:00
<!-- 历史视频 -->
<view class="history-section" v-if="singHistoryList.length > 0">
<view class="section-title" @click="openHistoryModal('sing')">历史视频</view>
<view class="history-list">
<view class="history-item" v-for="(item,index) in singHistoryList.slice(0, 2)" :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>
<!-- 歌曲列表 -->
<view class="section-title">选择歌曲</view>
2026-02-01 10:56:35 +08:00
<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>
</scroll-view>
</view>
</swiper-item>
<!-- 跳舞页面 (index 3) -->
2026-02-01 10:56:35 +08:00
<swiper-item>
<view class="swiper-content feature-page">
<view class="feature-icon">💃</view>
<view class="feature-title">跳舞功能</view>
<view class="feature-desc">让她为你跳一支舞</view>
2026-02-02 20:08:28 +08:00
<scroll-view class="feature-scroll" scroll-y="true">
<view class="history-section" v-if="danceHistoryList.length > 0">
<view class="section-title" @click="openHistoryModal('dance')">历史视频</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>
2026-02-01 10:56:35 +08:00
</view>
</swiper-item>
<!-- 换服装页面 (index 4) - 跳转到独立页面 -->
2026-02-01 10:56:35 +08:00
<swiper-item>
<view class="swiper-content feature-page">
<view class="feature-icon">👗</view>
<view class="feature-title">换装搭配</view>
<view class="feature-desc">正在跳转到装束设置页面...</view>
</view>
2026-02-01 10:56:35 +08:00
</swiper-item>
<!-- 刷礼物页面 (index 5) -->
2026-02-01 10:56:35 +08:00
<swiper-item>
<view class="swiper-content" @click="togift">
<view class="feature-page">
<view class="feature-icon">🎁</view>
<view class="feature-title">送礼物</view>
<view class="feature-desc">送她一份心意</view>
<view class="feature-btn">打开礼物匣</view>
</view>
</view>
</swiper-item>
<!-- 商城页面 (index 6) -->
2026-02-01 10:56:35 +08:00
<swiper-item>
2026-02-02 20:08:28 +08:00
<view class="swiper-content" @click="openShopLink">
<view class="feature-page shop-link-page">
<view class="feature-icon">🛒</view>
<view class="feature-title">商城</view>
<view class="feature-desc">购买更多功能和道具</view>
<view class="shop-link-banner">
<image class="shop-banner-img" src="/static/images/member_logo.png" mode="aspectFit"></image>
<view class="shop-link-text">点击进入商城</view>
2026-02-01 10:56:35 +08:00
</view>
2026-02-02 20:08:28 +08:00
<view class="feature-btn shop-external-btn">立即前往 </view>
</view>
2026-02-01 10:56:35 +08:00
</view>
</swiper-item>
<!-- 短剧页面 (index 7) -->
2026-02-01 10:56:35 +08:00
<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="drama-list">
2026-02-02 20:08:28 +08:00
<!-- 外部链接短剧 -->
<view class="drama-item drama-link-item" @click="openDramaLink">
<image class="drama-cover drama-banner" src="/static/images/chat_a1.png" mode="aspectFill"></image>
<view class="drama-link-badge">推荐</view>
<view class="drama-info">
<text class="drama-title">🎬 精彩短剧推荐</text>
<text class="drama-desc">点击观看更多精彩内容</text>
<view class="drama-play-btn drama-external-btn">立即观看 </view>
</view>
</view>
<!-- 其他短剧项 -->
<view class="drama-item" v-for="i in 3" :key="i">
2026-02-01 10:56:35 +08:00
<image class="drama-cover" src="/static/images/avatar.png" mode="aspectFill"></image>
<view class="drama-info">
<text class="drama-title">短剧标题 {{i}}</text>
<text class="drama-desc">精彩剧情简介...</text>
<view class="drama-play-btn">立即播放</view>
</view>
</view>
</view>
</scroll-view>
</view>
</swiper-item>
</swiper>
2026-02-02 20:08:28 +08:00
<!-- 自定义细线指示器 - 聊天页面时隐藏 -->
<view class="swiper-indicator" v-if="getBobbiesList.reg_step == 4" :class="{ 'indicator-hidden': currentTab === 1 }">
2026-02-01 10:56:35 +08:00
<view
v-for="(tab, index) in tabs"
:key="index"
class="indicator-line"
:class="{ 'active': currentTab === index }">
</view>
</view>
2026-01-31 19:15:41 +08:00
</view>
</view>
<!-- 青少年模式弹框 -->
<under-age :reg-step="getBobbiesList.reg_step" @underAgeStatusChanged="handleUnderAgeStatusChange"></under-age>
2026-02-02 20:08:28 +08:00
<!-- 底部 Tab - 聊天页面时隐藏 -->
<view class="bottom-tab-bar" :class="{ 'tab-bar-hidden': currentTab === 1 }">
<tab-bar></tab-bar>
</view>
2026-01-31 19:15:41 +08:00
<!-- 青少年模式遮罩层 -->
<view v-if="underAgeEnabled" class="underage-overlay">
<view class="underage-text">已开启青少年模式</view>
</view>
2026-02-02 20:08:28 +08:00
<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' ? '跳舞历史视频' : '唱歌历史视频' }}</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>
</view>
<view class="modal-item-actions">
<view class="modal-play" @click="openVideoPlayer(item.video_url)">播放</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>
2026-01-31 19:15:41 +08:00
</view>
</template>
<script>
import {
GetUserBasic,
LoverInit,
2026-02-01 10:56:35 +08:00
LoverBasic,
SingSongs,
SingGenerate,
SingGenerateTask,
DanceGenerate,
SessionInit,
SessionSend
2026-01-31 19:15:41 +08:00
} from '@/utils/api.js'
2026-02-02 20:08:28 +08:00
import { baseURLPy } from '@/utils/request.js'
2026-01-31 19:15:41 +08:00
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 {
useConversationStore
} from '@/stores/conversation';
export default {
components: {
notHave,
topSafety,
tabBar,
UnderAge,
},
data() {
return {
2026-02-01 10:56:35 +08:00
// Tab 相关
currentTab: 0,
2026-02-02 20:08:28 +08:00
tabIntoView: 'tab-0',
2026-02-01 10:56:35 +08:00
tabs: [
{ name: '首页' },
{ name: '聊天' },
{ name: '唱歌' },
{ name: '跳舞' },
{ name: '换服装' },
{ name: '刷礼物' },
{ name: '商城' },
{ name: '短剧' }
],
// 聊天相关
chatMessages: [],
chatInputText: '',
chatScrollTop: 0,
2026-02-02 20:08:28 +08:00
chatIntoView: '',
chatSessionId: null,
chatUserAvatar: '',
chatLoverAvatar: '',
chatSending: false,
2026-02-01 10:56:35 +08:00
dancePrompt: '',
singSongsList: [],
2026-02-02 20:08:28 +08:00
singHistoryList: [],
danceHistoryList: [],
2026-02-01 10:56:35 +08:00
songId: 0,
2026-02-02 20:08:28 +08:00
singGenerating: false, // 唱歌视频生成中状态
2026-01-31 19:15:41 +08:00
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: '',
loverBasicList: '',
underAgeEnabled: false, // 添加青少年模式状态变量
2026-02-02 20:08:28 +08:00
historyModalVisible: false,
historyModalType: 'sing',
historyModalList: [],
videoPlayerVisible: false,
videoPlayerUrl: '',
2026-01-31 19:15:41 +08:00
}
},
onLoad(options) {
2026-01-31 19:15:41 +08:00
//#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;
}
}
2026-01-31 19:15:41 +08:00
},
onReady() {
this.getServerData();
},
onShow() {
// 每次页面显示时检查本地存储的青少年模式状态
this.checkUnderAgeStatus();
if (uni.getStorageSync('token')) {
this.getUserBasic()
this.loverBasic()
}
2026-02-01 10:56:35 +08:00
// 获取歌曲列表
this.getSingSongs();
2026-02-02 20:08:28 +08:00
this.getDanceHistory();
2026-01-31 19:15:41 +08:00
},
methods: {
2026-02-01 10:56:35 +08:00
// Tab 切换方法
switchTab(index) {
console.log('点击 Tab切换到索引:', index, '对应 Tab:', this.tabs[index].name);
// 如果点击的是"换服装" tab索引为4直接跳转到装束设置页面
if (index === 4) {
this.toreplacement();
return;
}
// 如果切换到聊天 tab初始化聊天会话
if (index === 1 && !this.chatSessionId) {
this.initChatSession();
}
2026-02-01 10:56:35 +08:00
this.currentTab = index;
2026-02-02 20:08:28 +08:00
this.updateTabIntoView(index);
2026-02-01 10:56:35 +08:00
},
onSwiperChange(e) {
console.log('Swiper 滑动,当前索引:', e.detail.current, '对应 Tab:', this.tabs[e.detail.current].name);
2026-02-01 10:56:35 +08:00
this.currentTab = e.detail.current;
2026-02-02 20:08:28 +08:00
this.updateTabIntoView(e.detail.current);
// 如果滑动到聊天 tab初始化聊天会话
if (e.detail.current === 1 && !this.chatSessionId) {
this.initChatSession();
}
2026-02-01 10:56:35 +08:00
},
2026-02-02 20:08:28 +08:00
updateTabIntoView(index) {
this.$nextTick(() => {
this.tabIntoView = 'tab-' + index;
});
},
2026-02-01 10:56:35 +08:00
// 选择歌曲
selectSongDirect(song) {
2026-02-02 20:08:28 +08:00
// 防止重复点击
if (this.singGenerating) {
uni.showToast({
title: '视频生成中,请稍候...',
icon: 'none',
duration: 2000
});
return;
}
2026-02-01 10:56:35 +08:00
this.songId = song.id;
2026-02-02 20:08:28 +08:00
this.singGenerating = true;
uni.showLoading({
title: '正在生成视频...',
mask: true // 添加遮罩层防止用户操作
});
2026-02-01 10:56:35 +08:00
SingGenerate({ song_id: song.id }).then(res => {
if (res.code == 1) {
2026-02-02 20:08:28 +08:00
this.getSingGenerateTask(res.data.generation_task_id);
2026-02-01 10:56:35 +08:00
} else {
2026-02-02 20:08:28 +08:00
this.singGenerating = false;
2026-02-01 10:56:35 +08:00
uni.hideLoading();
uni.showToast({ title: res.msg, icon: 'none' });
}
2026-02-02 20:08:28 +08:00
}).catch(err => {
this.singGenerating = false;
uni.hideLoading();
uni.showToast({
title: '请求失败,请重试',
icon: 'none'
});
2026-02-01 10:56:35 +08:00
});
},
// 请求跳舞
requestDance() {
if (!this.dancePrompt || !this.dancePrompt.trim()) {
uni.showToast({ title: '请输入舞蹈描述', icon: 'none' });
return;
}
uni.showLoading({ title: '生成中...' });
DanceGenerate({ prompt: this.dancePrompt }).then(res => {
if (res.code == 1) {
uni.hideLoading();
uni.showToast({ title: '生成成功', icon: 'success' });
2026-02-02 20:08:28 +08:00
this.getDanceHistory();
2026-02-01 10:56:35 +08:00
} else {
uni.hideLoading();
uni.showToast({ title: res.msg, icon: 'none' });
}
});
},
2026-02-02 20:08:28 +08:00
// 打开商城外部链接
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);
2026-02-01 10:56:35 +08:00
}
});
},
2026-02-02 20:08:28 +08:00
playDanceVideo(videoUrl) {
this.openVideoPlayer(videoUrl);
},
openHistoryModal(type) {
this.historyModalType = type;
this.historyModalList = type === 'dance' ? (this.danceHistoryList || []) : (this.singHistoryList || []);
this.historyModalVisible = true;
},
closeHistoryModal() {
this.historyModalVisible = false;
},
openVideoPlayer(url) {
if (!url) return;
this.videoPlayerUrl = url;
this.videoPlayerVisible = true;
},
closeVideoPlayer() {
this.videoPlayerVisible = false;
this.videoPlayerUrl = '';
},
// 获取歌曲列表
2026-02-01 10:56:35 +08:00
// 获取歌曲列表
getSingSongs() {
SingSongs().then(res => {
if (res.code == 1) {
this.singSongsList = res.data.songs || [];
}
});
2026-02-02 20:08:28 +08:00
// 同时获取历史记录
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}`;
2026-02-01 10:56:35 +08:00
},
// 加载换装数据
2026-02-01 10:56:35 +08:00
// 轮询监听唱歌任务结果
getSingGenerateTask(task_id) {
const that = this;
let attempts = 0;
2026-02-02 20:08:28 +08:00
const maxAttempts = 60; // 增加到60次每次4秒总共4分钟
2026-02-01 10:56:35 +08:00
const doPoll = () => {
attempts++;
if (attempts > maxAttempts) {
2026-02-02 20:08:28 +08:00
that.singGenerating = false;
2026-02-01 10:56:35 +08:00
uni.hideLoading();
2026-02-02 20:08:28 +08:00
uni.showToast({
title: '处理超时,请稍后查看',
icon: 'none',
duration: 2000
});
2026-02-01 10:56:35 +08:00
return;
}
2026-02-02 20:08:28 +08:00
// 更新加载提示
uni.showLoading({
title: `生成中... (${attempts}/${maxAttempts})`,
mask: true
});
2026-02-01 10:56:35 +08:00
SingGenerateTask(task_id).then(res => {
if (res.code == 1) {
const data = res.data;
if (data.status == 'succeeded') {
2026-02-02 20:08:28 +08:00
that.singGenerating = false;
2026-02-01 10:56:35 +08:00
uni.hideLoading();
2026-02-02 20:08:28 +08:00
uni.showToast({
title: '生成成功!',
icon: 'success',
duration: 2000
});
// 可以在这里刷新聊天消息或显示视频
2026-02-01 10:56:35 +08:00
} else if (data.status == 'failed') {
2026-02-02 20:08:28 +08:00
that.singGenerating = false;
2026-02-01 10:56:35 +08:00
uni.hideLoading();
2026-02-02 20:08:28 +08:00
uni.showToast({
title: data.error_msg || '生成失败',
icon: 'none',
duration: 2000
});
2026-02-01 10:56:35 +08:00
} else {
2026-02-02 20:08:28 +08:00
// 继续轮询
2026-02-01 10:56:35 +08:00
setTimeout(doPoll, 4000);
}
} else {
2026-02-02 20:08:28 +08:00
that.singGenerating = false;
2026-02-01 10:56:35 +08:00
uni.hideLoading();
2026-02-02 20:08:28 +08:00
uni.showToast({
title: res.msg,
icon: 'none',
duration: 2000
});
2026-02-01 10:56:35 +08:00
}
2026-02-02 20:08:28 +08:00
}).catch(err => {
that.singGenerating = false;
uni.hideLoading();
uni.showToast({
title: '查询失败,请重试',
icon: 'none',
duration: 2000
});
2026-02-01 10:56:35 +08:00
});
};
doPoll();
},
2026-01-31 19:15:41 +08:00
// 处理青少年模式状态改变事件
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;
},
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';
// 获取恋人头像
this.chatLoverAvatar = this.loverBasicList?.image_url || '/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 = '';
2026-02-02 20:08:28 +08:00
// 让输入框失去焦点,收起键盘
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' });
});
},
2026-02-02 20:08:28 +08:00
// 输入框失去焦点时的处理
onInputBlur() {
// 确保输入框恢复正常状态
this.$nextTick(() => {
// 强制重新渲染,确保样式正确
});
},
// 滚动聊天到底部
scrollChatToBottom() {
2026-02-02 20:08:28 +08:00
this.chatIntoView = '';
this.$nextTick(() => {
this.chatIntoView = 'chat-bottom';
});
}
2026-01-31 19:15:41 +08:00
}
}
</script>
<style>
page {
background: #FFFFFF;
}
</style>
<style>
.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;
}
.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;
}
2026-02-01 10:56:35 +08:00
/* 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;
2026-02-02 20:08:28 +08:00
width: 100%;
2026-02-01 10:56:35 +08:00
height: calc(100vh - 120rpx);
margin-top: calc(80rpx + var(--status-bar-height));
2026-02-02 20:08:28 +08:00
overflow-x: hidden;
2026-02-01 10:56:35 +08:00
}
.swiper-scroll {
height: 100%;
2026-02-02 20:08:28 +08:00
width: 100%;
overflow-x: hidden;
2026-02-01 10:56:35 +08:00
}
.swiper-content {
height: 100%;
2026-02-02 20:08:28 +08:00
width: 100%;
2026-02-01 10:56:35 +08:00
display: flex;
align-items: center;
justify-content: center;
2026-02-02 20:08:28 +08:00
overflow-x: hidden;
2026-02-01 10:56:35 +08:00
}
/* 功能页面样式 */
.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;
2026-02-02 20:08:28 +08:00
box-sizing: border-box;
overflow-x: hidden;
}
/* 历史视频样式 */
.history-section {
width: 100%;
padding: 20rpx;
margin-bottom: 30rpx;
box-sizing: border-box;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
padding-left: 10rpx;
}
.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;
2026-02-01 10:56:35 +08:00
}
/* 歌曲列表样式 */
.song-list {
width: 100%;
padding: 20rpx;
2026-02-02 20:08:28 +08:00
box-sizing: border-box;
2026-02-01 10:56:35 +08:00
}
.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;
}
/* 商城列表样式 */
.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;
}
2026-02-02 20:08:28 +08:00
/* 商城外部链接页面样式 */
.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);
}
2026-02-01 10:56:35 +08:00
/* 短剧列表样式 */
.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;
}
2026-02-02 20:08:28 +08:00
/* 短剧外部链接特殊样式 */
.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;
}
2026-02-01 10:56:35 +08:00
/* 自定义细线指示器样式 */
.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;
2026-02-02 20:08:28 +08:00
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;
2026-02-01 10:56:35 +08:00
}
.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;
2026-02-02 20:08:28 +08:00
min-height: 0;
background: #f5f5f5;
}
.chat-message-scroll {
flex: 1;
2026-02-02 20:08:28 +08:00
height: 0;
min-height: 0;
padding: 20rpx;
2026-02-02 20:08:28 +08:00
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;
2026-02-02 20:08:28 +08:00
justify-content: flex-start;
}
.message-right {
flex-direction: row-reverse;
2026-02-02 20:08:28 +08:00
justify-content: flex-start;
padding-left: 0;
}
.chat-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
flex-shrink: 0;
}
.chat-message-bubble {
2026-02-02 20:08:28 +08:00
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;
2026-02-02 20:08:28 +08:00
padding: 10rpx 20rpx;
padding-bottom: calc(10rpx + env(safe-area-inset-bottom));
background: #fff;
border-top: 1rpx solid #e5e5e5;
2026-02-02 20:08:28 +08:00
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;
2026-02-02 20:08:28 +08:00
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;
2026-02-02 20:08:28 +08:00
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;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx;
border-bottom: 1px solid rgba(0,0,0,0.06);
}
.modal-title {
font-size: 30rpx;
font-weight: 600;
color: #333333;
}
.modal-close {
font-size: 26rpx;
color: #666666;
padding: 6rpx 12rpx;
}
.modal-list {
max-height: 60vh;
padding: 12rpx 24rpx 24rpx;
}
.modal-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx 0;
border-bottom: 1px solid rgba(0,0,0,0.06);
}
.modal-item-info {
display: flex;
flex-direction: column;
flex: 1;
padding-right: 12rpx;
}
.modal-item-title {
font-size: 28rpx;
color: #333333;
}
.modal-item-date {
font-size: 22rpx;
color: #999999;
margin-top: 6rpx;
}
.modal-item-actions {
width: 120rpx;
display: flex;
justify-content: flex-end;
}
.modal-play {
font-size: 24rpx;
color: #ffffff;
background: #F661B5;
padding: 10rpx 16rpx;
border-radius: 12rpx;
text-align: center;
}
.modal-video {
width: 100%;
height: 420rpx;
background: #000000;
}
</style>