Ai_GirlFriend/xuniYou/pages/index/index.vue
2026-02-01 17:46:31 +08:00

1952 lines
47 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">
<view class="tab-list">
<view
v-for="(tab, index) in tabs"
:key="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>
<scroll-view class="swiper-scroll" scroll-y="true">
<view class="chat-preview-container">
<view class="chat-preview-header">
<view class="chat-preview-title">💬 与她聊天 (Tab索引: {{currentTab}})</view>
<view class="chat-preview-btn" @click="tochat">进入完整聊天</view>
</view>
<!-- 聊天记录预览 -->
<view class="chat-preview-list">
<view class="chat-preview-tip">点击上方按钮进入完整聊天页面</view>
<view class="chat-preview-image">
<image
v-if="loverBasicList"
:src="loverBasicList.image_url || '/static/images/avatar.png'"
mode="aspectFill">
</image>
</view>
<view class="chat-preview-name">{{ loverBasicList.name || '你的恋人' }}</view>
<view class="chat-preview-desc">开始你们的对话吧~</view>
</view>
</view>
</scroll-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="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>
<view class="dance-form">
<textarea
class="dance-input"
v-model="dancePrompt"
placeholder="描述你想看的舞蹈动作"
maxlength="200" />
<view class="dance-btn" @click="requestDance">生成舞蹈视频</view>
</view>
</view>
</swiper-item>
<!-- 换服装页面 (index 4) -->
<swiper-item>
<scroll-view class="swiper-scroll" scroll-y="true">
<view class="outfit-container">
<view class="outfit-header">
<text class="outfit-title">👗 换装搭配</text>
<text class="outfit-count">剩余次数: {{ outfitData ? outfitData.clothes_num : 0 }}</text>
</view>
<view v-if="!outfitData" class="outfit-loading">
<text>加载中...</text>
</view>
<view v-else class="outfit-content">
<!-- 上衣 -->
<view class="outfit-section">
<view class="section-title-top">上衣</view>
<view class="outfit-grid-wrapper">
<view
v-for="(item, index) in outfitData.top"
:key="item.id"
@click="selectOutfitTop(item)"
class="outfit-item-wrapper">
<view class="outfit-grid-inner" :style="getOutfitBorderStyle(item.id, 'top')">
<image class="outfit-grid-img" :src="item.image_url" mode="aspectFill"></image>
<view v-if="item.is_lock && !item.is_free" class="outfit-grid-lock">🔒</view>
</view>
</view>
<view style="clear: both;"></view>
</view>
</view>
<!-- 下装 -->
<view class="outfit-section">
<view class="section-title-top">下装</view>
<view class="outfit-grid-wrapper">
<view
v-for="(item, index) in outfitData.bottom"
:key="item.id"
@click="selectOutfitBottom(item)"
class="outfit-item-wrapper">
<view class="outfit-grid-inner" :style="getOutfitBorderStyle(item.id, 'bottom')">
<image class="outfit-grid-img" :src="item.image_url" mode="aspectFill"></image>
<view v-if="item.is_lock && !item.is_free" class="outfit-grid-lock">🔒</view>
</view>
</view>
<view style="clear: both;"></view>
</view>
</view>
<!-- 连体服 -->
<view class="outfit-section">
<view class="section-title-top">连体服</view>
<view class="outfit-grid-wrapper">
<view
v-for="(item, index) in outfitData.dress"
:key="item.id"
@click="selectOutfitDress(item)"
class="outfit-item-wrapper">
<view class="outfit-grid-inner" :style="getOutfitBorderStyle(item.id, 'dress')">
<image class="outfit-grid-img" :src="item.image_url" mode="aspectFill"></image>
<view v-if="item.is_lock && !item.is_free" class="outfit-grid-lock">🔒</view>
</view>
</view>
<view style="clear: both;"></view>
</view>
</view>
<!-- 生成按钮 -->
<view class="outfit-generate-btn" @click="generateOutfit">
<text>生成换装</text>
</view>
</view>
</view>
</scroll-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 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="shop-list">
<view class="shop-item" v-for="i in 5" :key="i">
<view class="shop-info">
<text class="shop-title">套餐 {{i}}</text>
<text class="shop-desc">包含多种功能和道具</text>
</view>
<view class="shop-price">
<text class="price-value">¥{{i * 30}}</text>
<view class="shop-buy-btn">购买</view>
</view>
</view>
</view>
</scroll-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" v-for="i in 4" :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">
<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-bar></tab-bar>
<!-- 青少年模式遮罩层 -->
<view v-if="underAgeEnabled" class="underage-overlay">
<view class="underage-text">已开启青少年模式</view>
</view>
</view>
</template>
<script>
import {
GetUserBasic,
LoverInit,
LoverBasic,
SingSongs,
SingGenerate,
SingGenerateTask,
DanceGenerate,
OutfitList,
OutfitChange
} from '@/utils/api.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,
tabs: [
{ name: '首页' },
{ name: '聊天' },
{ name: '唱歌' },
{ name: '跳舞' },
{ name: '换服装' },
{ name: '刷礼物' },
{ name: '商城' },
{ name: '短剧' }
],
dancePrompt: '',
singSongsList: [],
songId: 0,
// 换服装相关数据
outfitData: null,
selectedTopId: null,
selectedBottomId: null,
selectedDressId: null,
outfitMode: null, // 1=连体服, 2=上衣+下装
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, // 添加青少年模式状态变量
}
},
onLoad() {
//#ifdef MP-WEIXIN
this.checkPermission()
//#endif
console.log('uni.env.',uni.env)
},
onReady() {
this.getServerData();
},
onShow() {
// 每次页面显示时检查本地存储的青少年模式状态
this.checkUnderAgeStatus();
if (uni.getStorageSync('token')) {
this.getUserBasic()
this.loverBasic()
}
// 获取歌曲列表
this.getSingSongs();
},
methods: {
// Tab 切换方法
switchTab(index) {
console.log('点击 Tab切换到索引:', index, '对应 Tab:', this.tabs[index].name);
// 如果点击的是"聊天" tab索引为1直接跳转到聊天页面
if (index === 1) {
this.tochat();
return;
}
// 如果点击的是"换服装" tab索引为4加载服装数据
if (index === 4) {
console.log('切换到换服装 tab准备加载数据');
// 延迟一下再加载,确保页面已经渲染
setTimeout(() => {
this.loadOutfitData();
}, 300);
}
this.currentTab = index;
},
onSwiperChange(e) {
console.log('Swiper 滑动,当前索引:', e.detail.current, '对应 Tab:', this.tabs[e.detail.current].name);
this.currentTab = e.detail.current;
},
// 选择歌曲
selectSongDirect(song) {
this.songId = song.id;
uni.showLoading({ title: '生成中...' });
SingGenerate({ song_id: song.id }).then(res => {
if (res.code == 1) {
this.getSingGenerateTask(res.data.task_id);
} else {
uni.hideLoading();
uni.showToast({ title: res.msg, 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' });
} else {
uni.hideLoading();
uni.showToast({ title: res.msg, icon: 'none' });
}
});
},
// 获取歌曲列表
getSingSongs() {
SingSongs().then(res => {
if (res.code == 1) {
this.singSongsList = res.data.list || [];
}
});
},
// 获取歌曲列表
getSingSongs() {
SingSongs().then(res => {
if (res.code == 1) {
this.singSongsList = res.data.songs || [];
}
});
},
// 加载换装数据
loadOutfitData() {
console.log('开始加载换装数据,当前 outfitData:', this.outfitData);
if (this.outfitData) {
console.log('数据已存在,跳过加载');
return; // 已加载过就不重复加载
}
console.log('显示加载提示...');
uni.showLoading({ title: '加载中...' });
console.log('调用 OutfitList API...');
OutfitList({}).then(res => {
console.log('OutfitList API 返回:', res);
uni.hideLoading();
if (res.code == 1) {
this.outfitData = res.data;
console.log('设置 outfitData 成功:', this.outfitData);
// 处理数据,标记当前使用的服装
this.processOutfitData();
// 自动选中当前使用的服装
this.selectCurrentOutfit();
} else {
console.error('API 返回错误:', res.msg);
uni.showToast({ title: res.msg || '加载失败', icon: 'none' });
}
}).catch(err => {
console.error('OutfitList API 异常:', err);
uni.hideLoading();
uni.showToast({ title: '网络错误,请检查后端服务', icon: 'none' });
});
},
// 处理换装数据
processOutfitData() {
if (!this.outfitData) return;
const { current_outfit, owned_outfit_ids } = this.outfitData;
// 处理上衣
this.outfitData.top.forEach(item => {
item.is_current = current_outfit.top_id === item.id;
item.is_lock = !owned_outfit_ids.includes(item.id);
});
// 处理下装
this.outfitData.bottom.forEach(item => {
item.is_current = current_outfit.bottom_id === item.id;
item.is_lock = !owned_outfit_ids.includes(item.id);
});
// 处理连体服
this.outfitData.dress.forEach(item => {
item.is_current = current_outfit.dress_id === item.id;
item.is_lock = !owned_outfit_ids.includes(item.id);
});
},
// 自动选中当前使用的服装
selectCurrentOutfit() {
if (!this.outfitData || !this.outfitData.current_outfit) return;
const { current_outfit } = this.outfitData;
// 检查连体服
if (current_outfit.dress_id) {
this.outfitMode = 1;
this.selectedDressId = current_outfit.dress_id;
return;
}
// 检查上衣下装
if (current_outfit.top_id || current_outfit.bottom_id) {
this.outfitMode = 2;
this.selectedTopId = current_outfit.top_id;
this.selectedBottomId = current_outfit.bottom_id;
}
},
// 获取服装边框样式
getOutfitBorderStyle(itemId, type) {
let isSelected = false;
if (type === 'top') {
isSelected = this.selectedTopId === itemId;
} else if (type === 'bottom') {
isSelected = this.selectedBottomId === itemId;
} else if (type === 'dress') {
isSelected = this.selectedDressId === itemId;
}
return {
border: isSelected ? '5rpx solid #ff0000' : '5rpx solid #ddd',
boxShadow: isSelected ? '0 4rpx 20rpx rgba(255, 0, 0, 0.4)' : '0 4rpx 16rpx rgba(0, 0, 0, 0.15)'
};
},
// 选择上衣
selectOutfitTop(item) {
console.log('点击上衣item.id:', item.id, '当前selectedTopId:', this.selectedTopId);
if (item.is_lock && !item.is_free) {
uni.showToast({ title: '该服装未解锁', icon: 'none' });
return;
}
if (this.outfitMode === 1 && this.selectedDressId) {
uni.showToast({ title: '连体服模式下不能选择上衣', icon: 'none' });
return;
}
this.outfitMode = 2;
this.selectedTopId = this.selectedTopId === item.id ? null : item.id;
console.log('选择后selectedTopId:', this.selectedTopId);
},
// 选择下装
selectOutfitBottom(item) {
console.log('点击下装item.id:', item.id, '当前selectedBottomId:', this.selectedBottomId);
if (item.is_lock && !item.is_free) {
uni.showToast({ title: '该服装未解锁', icon: 'none' });
return;
}
if (this.outfitMode === 1 && this.selectedDressId) {
uni.showToast({ title: '连体服模式下不能选择下装', icon: 'none' });
return;
}
this.outfitMode = 2;
this.selectedBottomId = this.selectedBottomId === item.id ? null : item.id;
console.log('选择后selectedBottomId:', this.selectedBottomId);
},
// 选择连体服
selectOutfitDress(item) {
console.log('点击连体服item.id:', item.id, '当前selectedDressId:', this.selectedDressId);
if (item.is_lock && !item.is_free) {
uni.showToast({ title: '该服装未解锁', icon: 'none' });
return;
}
if (this.outfitMode === 2 && (this.selectedTopId || this.selectedBottomId)) {
uni.showToast({ title: '上衣下装模式下不能选择连体服', icon: 'none' });
return;
}
this.outfitMode = 1;
this.selectedDressId = this.selectedDressId === item.id ? null : item.id;
if (this.selectedDressId) {
this.selectedTopId = null;
this.selectedBottomId = null;
}
console.log('选择后selectedDressId:', this.selectedDressId);
},
// 生成换装
generateOutfit() {
if (!this.outfitData || !this.outfitData.clothes_num) {
uni.showToast({ title: '换装次数不足', icon: 'none' });
return;
}
let params = {};
if (this.outfitMode === 1 && this.selectedDressId) {
params = { dress_item_id: this.selectedDressId, save_to_look: false };
} else if (this.outfitMode === 2 && (this.selectedTopId || this.selectedBottomId)) {
params = {
top_item_id: this.selectedTopId || '',
bottom_item_id: this.selectedBottomId || '',
save_to_look: false
};
} else {
uni.showToast({ title: '请先选择服装', icon: 'none' });
return;
}
uni.showLoading({ title: '生成中...' });
OutfitChange(params).then(res => {
uni.hideLoading();
if (res.code == 1) {
uni.showToast({ title: '换装成功', icon: 'success' });
// 重新加载数据
this.outfitData = null;
this.loadOutfitData();
} else {
uni.showToast({ title: res.msg, icon: 'none' });
}
}).catch(() => {
uni.hideLoading();
});
},
// 轮询监听唱歌任务结果
getSingGenerateTask(task_id) {
const that = this;
let attempts = 0;
const maxAttempts = 20;
const doPoll = () => {
attempts++;
if (attempts > maxAttempts) {
uni.hideLoading();
uni.showToast({ title: '处理超时,请稍后重试', icon: 'none' });
return;
}
SingGenerateTask(task_id).then(res => {
if (res.code == 1) {
const data = res.data;
if (data.status == 'succeeded') {
uni.hideLoading();
uni.showToast({ title: '生成成功', icon: 'success' });
} else if (data.status == 'failed') {
uni.hideLoading();
uni.showToast({ title: data.error_msg || '生成失败', icon: 'none' });
} else {
setTimeout(doPoll, 4000);
}
} else {
uni.hideLoading();
uni.showToast({ title: res.msg, icon: 'none' });
}
});
};
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'
});
},
}
}
</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;
height: calc(100vh - 120rpx);
margin-top: calc(80rpx + var(--status-bar-height));
}
.swiper-scroll {
height: 100%;
}
.swiper-content {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
/* 功能页面样式 */
.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;
}
/* 歌曲列表样式 */
.song-list {
width: 100%;
padding: 20rpx;
}
.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;
}
/* 短剧列表样式 */
.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;
}
/* 自定义细线指示器样式 */
.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;
}
.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;
}
</style>
/* 聊天预览容器样式 */
.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);
}