Ai_GirlFriend/xuniYou/pages/index/index.vue
2026-02-02 20:08:28 +08:00

2495 lines
58 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>
<scroll-view class="swiper-scroll" scroll-y="true">
<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">
<qiun-data-charts v-if="chartData.series" type="arcbar" :opts="opts" :chartData="chartData" />
<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>
</scroll-view>
</swiper-item>
<!-- 聊天页面 (index 1) - 嵌入完整聊天功能 -->
<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 2) -->
<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="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>
<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) -->
<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="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>
</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>
</view>
</swiper-item>
<!-- 刷礼物页面 (index 5) -->
<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) -->
<swiper-item>
<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>
</view>
<view class="feature-btn shop-external-btn">立即前往 →</view>
</view>
</view>
</swiper-item>
<!-- 短剧页面 (index 7) -->
<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">
<!-- 外部链接短剧 -->
<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">
<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>
<!-- 自定义细线指示器 - 聊天页面时隐藏 -->
<view class="swiper-indicator" v-if="getBobbiesList.reg_step == 4" :class="{ 'indicator-hidden': currentTab === 1 }">
<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 === 1 }">
<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' ? '跳舞历史视频' : '唱歌历史视频' }}</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>
</view>
</template>
<script>
import {
GetUserBasic,
LoverInit,
LoverBasic,
SingSongs,
SingGenerate,
SingGenerateTask,
DanceGenerate,
SessionInit,
SessionSend
} from '@/utils/api.js'
import { 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 {
useConversationStore
} from '@/stores/conversation';
export default {
components: {
notHave,
topSafety,
tabBar,
UnderAge,
},
data() {
return {
// Tab 相关
currentTab: 0,
tabIntoView: 'tab-0',
tabs: [
{ name: '首页' },
{ name: '聊天' },
{ name: '唱歌' },
{ name: '跳舞' },
{ name: '换服装' },
{ name: '刷礼物' },
{ name: '商城' },
{ name: '短剧' }
],
// 聊天相关
chatMessages: [],
chatInputText: '',
chatScrollTop: 0,
chatIntoView: '',
chatSessionId: null,
chatUserAvatar: '',
chatLoverAvatar: '',
chatSending: false,
dancePrompt: '',
singSongsList: [],
singHistoryList: [],
danceHistoryList: [],
songId: 0,
singGenerating: false, // 唱歌视频生成中状态
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, // 添加青少年模式状态变量
historyModalVisible: false,
historyModalType: 'sing',
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.getSingSongs();
this.getDanceHistory();
},
methods: {
// 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();
}
this.currentTab = index;
this.updateTabIntoView(index);
},
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初始化聊天会话
if (e.detail.current === 1 && !this.chatSessionId) {
this.initChatSession();
}
},
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;
uni.showLoading({
title: '正在生成视频...',
mask: true // 添加遮罩层防止用户操作
});
SingGenerate({ song_id: song.id }).then(res => {
if (res.code == 1) {
this.getSingGenerateTask(res.data.generation_task_id);
} else {
this.singGenerating = false;
uni.hideLoading();
uni.showToast({ title: res.msg, icon: 'none' });
}
}).catch(err => {
this.singGenerating = false;
uni.hideLoading();
uni.showToast({
title: '请求失败,请重试',
icon: 'none'
});
});
},
// 请求跳舞
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' });
this.getDanceHistory();
} else {
uni.hideLoading();
uni.showToast({ title: res.msg, 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) {
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 = '';
},
// 获取歌曲列表
// 获取歌曲列表
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 = 60; // 增加到60次每次4秒总共4分钟
const doPoll = () => {
attempts++;
if (attempts > maxAttempts) {
that.singGenerating = false;
uni.hideLoading();
uni.showToast({
title: '处理超时,请稍后查看',
icon: 'none',
duration: 2000
});
return;
}
// 更新加载提示
uni.showLoading({
title: `生成中... (${attempts}/${maxAttempts})`,
mask: true
});
SingGenerateTask(task_id).then(res => {
if (res.code == 1) {
const data = res.data;
if (data.status == 'succeeded') {
that.singGenerating = false;
uni.hideLoading();
uni.showToast({
title: '生成成功!',
icon: 'success',
duration: 2000
});
// 可以在这里刷新聊天消息或显示视频
} else if (data.status == 'failed') {
that.singGenerating = false;
uni.hideLoading();
uni.showToast({
title: data.error_msg || '生成失败',
icon: 'none',
duration: 2000
});
} else {
// 继续轮询
setTimeout(doPoll, 4000);
}
} else {
that.singGenerating = false;
uni.hideLoading();
uni.showToast({
title: res.msg,
icon: 'none',
duration: 2000
});
}
}).catch(err => {
that.singGenerating = false;
uni.hideLoading();
uni.showToast({
title: '查询失败,请重试',
icon: 'none',
duration: 2000
});
});
};
doPoll();
},
// 处理青少年模式状态改变事件
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 = '';
// 让输入框失去焦点,收起键盘
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';
});
}
}
}
</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;
}
/* 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;
}
.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;
}
/* 歌曲列表样式 */
.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;
}
/* 商城列表样式 */
.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);
}
/* 短剧列表样式 */
.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;
}
/* 自定义细线指示器样式 */
.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;
}
.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>