样式:将tab栏切换改好(未完成,样式不合格)
This commit is contained in:
parent
92386f4597
commit
1f3f3b9240
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
归档/
|
归档/
|
||||||
xunifriend_RaeeC/runtime/log/202602/01.log
|
xunifriend_RaeeC/runtime/log/
|
||||||
|
public/
|
||||||
Binary file not shown.
|
|
@ -505,9 +505,24 @@ def save_cloned_voice(
|
||||||
db.add(new_voice)
|
db.add(new_voice)
|
||||||
db.flush()
|
db.flush()
|
||||||
|
|
||||||
|
# 将克隆的音色添加到用户的拥有列表中
|
||||||
|
user_row = db.query(User).filter(User.id == user.id).first()
|
||||||
|
if user_row:
|
||||||
|
owned_ids = _parse_owned_voices(user_row.owned_voice_ids)
|
||||||
|
owned_ids.add(new_voice.id)
|
||||||
|
user_row.owned_voice_ids = ",".join(map(str, sorted(owned_ids)))
|
||||||
|
db.add(user_row)
|
||||||
|
|
||||||
|
# 自动设置为恋人的当前音色
|
||||||
|
if lover:
|
||||||
|
lover.voice_id = new_voice.id
|
||||||
|
db.add(lover)
|
||||||
|
|
||||||
|
db.flush()
|
||||||
|
|
||||||
return success_response({
|
return success_response({
|
||||||
"voice_library_id": new_voice.id,
|
"voice_library_id": new_voice.id,
|
||||||
"message": "音色保存成功",
|
"message": "音色保存成功并已设置为当前音色",
|
||||||
"display_name": display_name
|
"display_name": display_name
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
29
lover/scripts/fix_tts_urls.py
Normal file
29
lover/scripts/fix_tts_urls.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
"""
|
||||||
|
修复数据库中的 TTS URL,将 127.0.0.1 替换为空,让系统重新生成
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from sqlalchemy import create_engine, text
|
||||||
|
from lover.config import settings
|
||||||
|
|
||||||
|
def fix_tts_urls():
|
||||||
|
engine = create_engine(settings.DATABASE_URL)
|
||||||
|
|
||||||
|
with engine.connect() as conn:
|
||||||
|
# 将所有包含 127.0.0.1 的 TTS URL 清空
|
||||||
|
result = conn.execute(
|
||||||
|
text("""
|
||||||
|
UPDATE nf_chat_message
|
||||||
|
SET tts_url = NULL, tts_status = 'pending'
|
||||||
|
WHERE tts_url LIKE '%127.0.0.1%'
|
||||||
|
""")
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
print(f"已清理 {result.rowcount} 条旧的 TTS URL")
|
||||||
|
print("用户下次请求 TTS 时会自动使用新的 URL")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
fix_tts_urls()
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"hash": "58b0dea4",
|
|
||||||
"browserHash": "94d21d6d",
|
|
||||||
"optimized": {
|
|
||||||
"easemob-websdk/uniApp/Easemob-chat": {
|
|
||||||
"src": "../../node_modules/easemob-websdk/uniApp/Easemob-chat.js",
|
|
||||||
"file": "easemob-websdk_uniApp_Easemob-chat.js",
|
|
||||||
"fileHash": "44a13f3e",
|
|
||||||
"needsInterop": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"chunks": {}
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
||||||
{"type":"module"}
|
|
||||||
127
xuniYou/components/main-tabs.vue
Normal file
127
xuniYou/components/main-tabs.vue
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
<template>
|
||||||
|
<view class="main-tabs-container">
|
||||||
|
<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': currentIndex === index }"
|
||||||
|
@click="switchTab(index)">
|
||||||
|
<text class="tab-name">{{ tab.name }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'MainTabs',
|
||||||
|
props: {
|
||||||
|
// 当前激活的 tab 索引
|
||||||
|
current: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentIndex: this.current,
|
||||||
|
tabs: [
|
||||||
|
{ name: '首页', path: '/pages/index/index' },
|
||||||
|
{ name: '聊天', path: '/pages/chat/simple' }, // 改为简化版聊天页面
|
||||||
|
{ name: '唱歌', path: '/pages/index/index?tab=2' },
|
||||||
|
{ name: '跳舞', path: '/pages/index/index?tab=3' },
|
||||||
|
{ name: '换服装', path: '/pages/index/replacement' },
|
||||||
|
{ name: '刷礼物', path: '/pages/index/index?tab=5' },
|
||||||
|
{ name: '商城', path: '/pages/index/index?tab=6' },
|
||||||
|
{ name: '短剧', path: '/pages/index/index?tab=7' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
current(newVal) {
|
||||||
|
this.currentIndex = newVal;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
switchTab(index) {
|
||||||
|
console.log('Tab 组件:切换到索引', index, '对应 Tab:', this.tabs[index].name);
|
||||||
|
|
||||||
|
const tab = this.tabs[index];
|
||||||
|
|
||||||
|
// 如果是当前页面,不跳转
|
||||||
|
if (this.currentIndex === index) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特殊处理:首页、聊天、换服装使用 redirectTo
|
||||||
|
if (index === 0) {
|
||||||
|
// 首页
|
||||||
|
uni.redirectTo({
|
||||||
|
url: '/pages/index/index'
|
||||||
|
});
|
||||||
|
} else if (index === 1) {
|
||||||
|
// 聊天 - 跳转到简化版
|
||||||
|
uni.redirectTo({
|
||||||
|
url: '/pages/chat/simple'
|
||||||
|
});
|
||||||
|
} else if (index === 4) {
|
||||||
|
// 换服装
|
||||||
|
uni.redirectTo({
|
||||||
|
url: '/pages/index/replacement'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 其他 tab 返回首页并切换到对应 tab
|
||||||
|
uni.redirectTo({
|
||||||
|
url: `/pages/index/index?tab=${index}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.main-tabs-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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -71,6 +71,15 @@
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/chat/simple",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "聊天",
|
||||||
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/chat/phone",
|
"path": "pages/chat/phone",
|
||||||
"style": {
|
"style": {
|
||||||
|
|
|
||||||
|
|
@ -732,6 +732,7 @@
|
||||||
|
|
||||||
// 播放音频
|
// 播放音频
|
||||||
this.currentAudioContext.play();
|
this.currentAudioContext.play();
|
||||||
|
this.isPlaying = true; // 设置播放状态为true
|
||||||
|
|
||||||
// 监听播放结束事件,播放结束后清空当前音频上下文
|
// 监听播放结束事件,播放结束后清空当前音频上下文
|
||||||
this.currentAudioContext.onEnded(() => {
|
this.currentAudioContext.onEnded(() => {
|
||||||
|
|
@ -1151,12 +1152,17 @@
|
||||||
this.sessionSend();
|
this.sessionSend();
|
||||||
},
|
},
|
||||||
playVoice(id) {
|
playVoice(id) {
|
||||||
// 如果点击的是当前正在播放的语音,则停止播放
|
console.log('点击播放按钮,消息ID:', id, '当前播放ID:', this.currentPlayingId, '播放状态:', this.isPlaying);
|
||||||
|
|
||||||
|
// 如果点击的是当前正在播放的语音,则暂停播放
|
||||||
if (this.currentPlayingId === id && this.isPlaying) {
|
if (this.currentPlayingId === id && this.isPlaying) {
|
||||||
|
console.log('暂停当前播放的音频');
|
||||||
this.stopCurrentAudio();
|
this.stopCurrentAudio();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 开始播放新的音频
|
||||||
|
console.log('开始播放新音频,消息ID:', id);
|
||||||
this.chatMessagesTtsform.id = id;
|
this.chatMessagesTtsform.id = id;
|
||||||
this.currentPlayingId = id; // 设置当前播放ID
|
this.currentPlayingId = id; // 设置当前播放ID
|
||||||
this.isPlaying = true; // 设置播放状态
|
this.isPlaying = true; // 设置播放状态
|
||||||
|
|
|
||||||
289
xuniYou/pages/chat/simple.vue
Normal file
289
xuniYou/pages/chat/simple.vue
Normal file
|
|
@ -0,0 +1,289 @@
|
||||||
|
<template>
|
||||||
|
<view class="chat-page">
|
||||||
|
<!-- 顶部 Tab 栏 -->
|
||||||
|
<main-tabs :current="1"></main-tabs>
|
||||||
|
|
||||||
|
<!-- 聊天内容区域 -->
|
||||||
|
<scroll-view
|
||||||
|
class="message-container"
|
||||||
|
scroll-y="true"
|
||||||
|
:scroll-top="scrollTop"
|
||||||
|
scroll-with-animation="true">
|
||||||
|
<view class="message-list">
|
||||||
|
<view
|
||||||
|
v-for="(msg, index) in messages"
|
||||||
|
:key="index"
|
||||||
|
class="message-item"
|
||||||
|
:class="msg.role === 'user' ? 'message-right' : 'message-left'">
|
||||||
|
<image
|
||||||
|
class="avatar"
|
||||||
|
:src="msg.role === 'user' ? userAvatar : loverAvatar"
|
||||||
|
mode="aspectFill">
|
||||||
|
</image>
|
||||||
|
<view class="message-bubble">
|
||||||
|
<text class="message-text">{{ msg.content }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 底部输入框 -->
|
||||||
|
<view class="input-bar">
|
||||||
|
<input
|
||||||
|
class="message-input"
|
||||||
|
v-model="inputText"
|
||||||
|
placeholder="输入消息..."
|
||||||
|
confirm-type="send"
|
||||||
|
@confirm="sendMessage"
|
||||||
|
/>
|
||||||
|
<view class="send-btn" @click="sendMessage">
|
||||||
|
<text>发送</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MainTabs from '@/components/main-tabs.vue';
|
||||||
|
import { SessionInit, SessionSend } from '@/utils/api.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
MainTabs
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
messages: [],
|
||||||
|
inputText: '',
|
||||||
|
scrollTop: 0,
|
||||||
|
sessionId: null,
|
||||||
|
userAvatar: '',
|
||||||
|
loverAvatar: '',
|
||||||
|
sending: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
// 获取用户头像
|
||||||
|
const userInfo = uni.getStorageSync('userinfo');
|
||||||
|
this.userAvatar = userInfo?.avatar || '/static/images/avatar.png';
|
||||||
|
|
||||||
|
// 获取恋人头像
|
||||||
|
const loverInfo = uni.getStorageSync('loverBasicList');
|
||||||
|
this.loverAvatar = loverInfo?.image_url || '/static/images/avatar.png';
|
||||||
|
|
||||||
|
// 初始化会话
|
||||||
|
this.initSession();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化会话
|
||||||
|
initSession() {
|
||||||
|
uni.showLoading({ title: '加载中...' });
|
||||||
|
|
||||||
|
SessionInit().then(res => {
|
||||||
|
uni.hideLoading();
|
||||||
|
if (res.code === 1) {
|
||||||
|
this.sessionId = res.data.session_id;
|
||||||
|
// 加载历史消息(只显示最近10条)
|
||||||
|
const allMessages = res.data.messages || [];
|
||||||
|
this.messages = allMessages.slice(-10).map(msg => ({
|
||||||
|
role: msg.role === 'lover' ? 'ai' : 'user',
|
||||||
|
content: msg.content
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 滚动到底部
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: res.msg || '加载失败', icon: 'none' });
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
uni.hideLoading();
|
||||||
|
console.error('初始化会话失败:', err);
|
||||||
|
uni.showToast({ title: '网络错误', icon: 'none' });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
sendMessage() {
|
||||||
|
if (!this.inputText.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.sessionId) {
|
||||||
|
uni.showToast({ title: '会话未初始化', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userMessage = this.inputText.trim();
|
||||||
|
this.inputText = '';
|
||||||
|
|
||||||
|
// 添加用户消息到列表
|
||||||
|
this.messages.push({
|
||||||
|
role: 'user',
|
||||||
|
content: userMessage
|
||||||
|
});
|
||||||
|
|
||||||
|
// 滚动到底部
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加"思考中"提示
|
||||||
|
const thinkingIndex = this.messages.length;
|
||||||
|
this.messages.push({
|
||||||
|
role: 'ai',
|
||||||
|
content: '思考中...'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sending = true;
|
||||||
|
|
||||||
|
// 发送到后端
|
||||||
|
SessionSend({
|
||||||
|
session_id: this.sessionId,
|
||||||
|
message: userMessage
|
||||||
|
}).then(res => {
|
||||||
|
this.sending = false;
|
||||||
|
|
||||||
|
if (res.code === 1) {
|
||||||
|
// 移除"思考中"
|
||||||
|
this.messages.splice(thinkingIndex, 1);
|
||||||
|
|
||||||
|
// 添加 AI 回复
|
||||||
|
this.messages.push({
|
||||||
|
role: 'ai',
|
||||||
|
content: res.data.reply || '...'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 滚动到底部
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 移除"思考中"
|
||||||
|
this.messages.splice(thinkingIndex, 1);
|
||||||
|
uni.showToast({ title: res.msg || '发送失败', icon: 'none' });
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
this.sending = false;
|
||||||
|
// 移除"思考中"
|
||||||
|
this.messages.splice(thinkingIndex, 1);
|
||||||
|
console.error('发送消息失败:', err);
|
||||||
|
uni.showToast({ title: '发送失败', icon: 'none' });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 滚动到底部
|
||||||
|
scrollToBottom() {
|
||||||
|
this.scrollTop = 999999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chat-page {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-container {
|
||||||
|
flex: 1;
|
||||||
|
margin-top: calc(80rpx + var(--status-bar-height));
|
||||||
|
margin-bottom: 120rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-list {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-item {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-left {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-right {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-bubble {
|
||||||
|
max-width: 500rpx;
|
||||||
|
padding: 20rpx 25rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
margin: 0 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-left .message-bubble {
|
||||||
|
background: #fff;
|
||||||
|
border-top-left-radius: 5rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-right .message-bubble {
|
||||||
|
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 1.6;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-left .message-text {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-right .message-text {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-bar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-top: 1rpx solid #e5e5e5;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input {
|
||||||
|
flex: 1;
|
||||||
|
height: 80rpx;
|
||||||
|
padding: 0 25rpx;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
padding: 20rpx 40rpx;
|
||||||
|
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -140,30 +140,47 @@
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
|
|
||||||
<!-- 聊天页面 (index 1) -->
|
<!-- 聊天页面 (index 1) - 嵌入完整聊天功能 -->
|
||||||
<swiper-item>
|
<swiper-item>
|
||||||
<scroll-view class="swiper-scroll" scroll-y="true">
|
<view class="chat-swiper-page">
|
||||||
<view class="chat-preview-container">
|
<!-- 聊天消息列表 -->
|
||||||
<view class="chat-preview-header">
|
<scroll-view
|
||||||
<view class="chat-preview-title">💬 与她聊天 (Tab索引: {{currentTab}})</view>
|
class="chat-message-scroll"
|
||||||
<view class="chat-preview-btn" @click="tochat">进入完整聊天</view>
|
scroll-y="true"
|
||||||
</view>
|
:scroll-top="chatScrollTop"
|
||||||
|
scroll-with-animation="true">
|
||||||
<!-- 聊天记录预览 -->
|
<view class="chat-message-list">
|
||||||
<view class="chat-preview-list">
|
<view
|
||||||
<view class="chat-preview-tip">点击上方按钮进入完整聊天页面</view>
|
v-for="(msg, index) in chatMessages"
|
||||||
<view class="chat-preview-image">
|
:key="index"
|
||||||
|
class="chat-message-item"
|
||||||
|
:class="msg.role === 'user' ? 'message-right' : 'message-left'">
|
||||||
<image
|
<image
|
||||||
v-if="loverBasicList"
|
class="chat-avatar"
|
||||||
:src="loverBasicList.image_url || '/static/images/avatar.png'"
|
:src="msg.role === 'user' ? chatUserAvatar : chatLoverAvatar"
|
||||||
mode="aspectFill">
|
mode="aspectFill">
|
||||||
</image>
|
</image>
|
||||||
|
<view class="chat-message-bubble">
|
||||||
|
<text class="chat-message-text">{{ msg.content }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="chat-preview-name">{{ loverBasicList.name || '你的恋人' }}</view>
|
|
||||||
<view class="chat-preview-desc">开始你们的对话吧~</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 底部输入框 -->
|
||||||
|
<view class="chat-input-bar">
|
||||||
|
<input
|
||||||
|
class="chat-message-input"
|
||||||
|
v-model="chatInputText"
|
||||||
|
placeholder="输入消息..."
|
||||||
|
confirm-type="send"
|
||||||
|
@confirm="sendChatMessage"
|
||||||
|
/>
|
||||||
|
<view class="chat-send-btn" @click="sendChatMessage">
|
||||||
|
<text>发送</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
|
|
||||||
<!-- 唱歌页面 (index 2) -->
|
<!-- 唱歌页面 (index 2) -->
|
||||||
|
|
@ -202,81 +219,13 @@
|
||||||
</view>
|
</view>
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
|
|
||||||
<!-- 换服装页面 (index 4) -->
|
<!-- 换服装页面 (index 4) - 跳转到独立页面 -->
|
||||||
<swiper-item>
|
<swiper-item>
|
||||||
<scroll-view class="swiper-scroll" scroll-y="true">
|
<view class="swiper-content feature-page">
|
||||||
<view class="outfit-container">
|
<view class="feature-icon">👗</view>
|
||||||
<view class="outfit-header">
|
<view class="feature-title">换装搭配</view>
|
||||||
<text class="outfit-title">👗 换装搭配</text>
|
<view class="feature-desc">正在跳转到装束设置页面...</view>
|
||||||
<text class="outfit-count">剩余次数: {{ outfitData ? outfitData.clothes_num : 0 }}</text>
|
|
||||||
</view>
|
</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>
|
</swiper-item>
|
||||||
|
|
||||||
<!-- 刷礼物页面 (index 5) -->
|
<!-- 刷礼物页面 (index 5) -->
|
||||||
|
|
@ -368,8 +317,8 @@
|
||||||
SingGenerate,
|
SingGenerate,
|
||||||
SingGenerateTask,
|
SingGenerateTask,
|
||||||
DanceGenerate,
|
DanceGenerate,
|
||||||
OutfitList,
|
SessionInit,
|
||||||
OutfitChange
|
SessionSend
|
||||||
} from '@/utils/api.js'
|
} from '@/utils/api.js'
|
||||||
import notHave from '@/components/not-have.vue';
|
import notHave from '@/components/not-have.vue';
|
||||||
import topSafety from '@/components/top-safety.vue';
|
import topSafety from '@/components/top-safety.vue';
|
||||||
|
|
@ -400,15 +349,17 @@
|
||||||
{ name: '商城' },
|
{ name: '商城' },
|
||||||
{ name: '短剧' }
|
{ name: '短剧' }
|
||||||
],
|
],
|
||||||
|
// 聊天相关
|
||||||
|
chatMessages: [],
|
||||||
|
chatInputText: '',
|
||||||
|
chatScrollTop: 0,
|
||||||
|
chatSessionId: null,
|
||||||
|
chatUserAvatar: '',
|
||||||
|
chatLoverAvatar: '',
|
||||||
|
chatSending: false,
|
||||||
dancePrompt: '',
|
dancePrompt: '',
|
||||||
singSongsList: [],
|
singSongsList: [],
|
||||||
songId: 0,
|
songId: 0,
|
||||||
// 换服装相关数据
|
|
||||||
outfitData: null,
|
|
||||||
selectedTopId: null,
|
|
||||||
selectedBottomId: null,
|
|
||||||
selectedDressId: null,
|
|
||||||
outfitMode: null, // 1=连体服, 2=上衣+下装
|
|
||||||
statusBarHeight: uni.getWindowInfo().statusBarHeight,
|
statusBarHeight: uni.getWindowInfo().statusBarHeight,
|
||||||
currentStep: 0,
|
currentStep: 0,
|
||||||
chartData: {},
|
chartData: {},
|
||||||
|
|
@ -445,11 +396,19 @@
|
||||||
underAgeEnabled: false, // 添加青少年模式状态变量
|
underAgeEnabled: false, // 添加青少年模式状态变量
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad() {
|
onLoad(options) {
|
||||||
//#ifdef MP-WEIXIN
|
//#ifdef MP-WEIXIN
|
||||||
this.checkPermission()
|
this.checkPermission()
|
||||||
//#endif
|
//#endif
|
||||||
console.log('uni.env.',uni.env)
|
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() {
|
onReady() {
|
||||||
this.getServerData();
|
this.getServerData();
|
||||||
|
|
@ -471,19 +430,15 @@
|
||||||
switchTab(index) {
|
switchTab(index) {
|
||||||
console.log('点击 Tab,切换到索引:', index, '对应 Tab:', this.tabs[index].name);
|
console.log('点击 Tab,切换到索引:', index, '对应 Tab:', this.tabs[index].name);
|
||||||
|
|
||||||
// 如果点击的是"聊天" tab(索引为1),直接跳转到聊天页面
|
// 如果点击的是"换服装" tab(索引为4),直接跳转到装束设置页面
|
||||||
if (index === 1) {
|
if (index === 4) {
|
||||||
this.tochat();
|
this.toreplacement();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果点击的是"换服装" tab(索引为4),加载服装数据
|
// 如果切换到聊天 tab,初始化聊天会话
|
||||||
if (index === 4) {
|
if (index === 1 && !this.chatSessionId) {
|
||||||
console.log('切换到换服装 tab,准备加载数据');
|
this.initChatSession();
|
||||||
// 延迟一下再加载,确保页面已经渲染
|
|
||||||
setTimeout(() => {
|
|
||||||
this.loadOutfitData();
|
|
||||||
}, 300);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentTab = index;
|
this.currentTab = index;
|
||||||
|
|
@ -491,6 +446,11 @@
|
||||||
onSwiperChange(e) {
|
onSwiperChange(e) {
|
||||||
console.log('Swiper 滑动,当前索引:', e.detail.current, '对应 Tab:', this.tabs[e.detail.current].name);
|
console.log('Swiper 滑动,当前索引:', e.detail.current, '对应 Tab:', this.tabs[e.detail.current].name);
|
||||||
this.currentTab = e.detail.current;
|
this.currentTab = e.detail.current;
|
||||||
|
|
||||||
|
// 如果滑动到聊天 tab,初始化聊天会话
|
||||||
|
if (e.detail.current === 1 && !this.chatSessionId) {
|
||||||
|
this.initChatSession();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// 选择歌曲
|
// 选择歌曲
|
||||||
selectSongDirect(song) {
|
selectSongDirect(song) {
|
||||||
|
|
@ -539,201 +499,6 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 加载换装数据
|
// 加载换装数据
|
||||||
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) {
|
getSingGenerateTask(task_id) {
|
||||||
const that = this;
|
const that = this;
|
||||||
|
|
@ -1005,6 +770,121 @@
|
||||||
url: '/pages/index/dynamics'
|
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 = '';
|
||||||
|
|
||||||
|
// 添加用户消息到列表
|
||||||
|
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' });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 滚动聊天到底部
|
||||||
|
scrollChatToBottom() {
|
||||||
|
this.chatScrollTop = 999999;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1696,7 +1576,6 @@
|
||||||
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
|
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
|
||||||
height: 4rpx;
|
height: 4rpx;
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
|
||||||
/* 聊天预览容器样式 */
|
/* 聊天预览容器样式 */
|
||||||
.chat-preview-container {
|
.chat-preview-container {
|
||||||
|
|
@ -1949,3 +1828,100 @@
|
||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
box-shadow: 0 4rpx 12rpx rgba(138, 124, 255, 0.3);
|
box-shadow: 0 4rpx 12rpx rgba(138, 124, 255, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========== 聊天 Swiper 页面样式 ========== */
|
||||||
|
.chat-swiper-page {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message-scroll {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20rpx;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-right {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-avatar {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message-bubble {
|
||||||
|
max-width: 500rpx;
|
||||||
|
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: 20rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-top: 1rpx solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
background: linear-gradient(135deg, #9F47FF 0%, #0053FA 100%);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,7 @@ import {
|
||||||
} from '@/utils/api.js'
|
} from '@/utils/api.js'
|
||||||
import notHave from '@/components/not-have.vue';
|
import notHave from '@/components/not-have.vue';
|
||||||
import topSafety from '@/components/top-safety.vue';
|
import topSafety from '@/components/top-safety.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
notHave,
|
notHave,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<?php
|
<?php
|
||||||
//000000000000
|
//000000000000
|
||||||
exit();?>
|
exit();?>
|
||||||
i:1769854292;
|
i:1769940875;
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,6 @@
|
||||||
# 主要功能
|
# 主要功能
|
||||||
1. 将密码校验删除,因为无法生成模型,用最简单的方法来尝试这些内容。
|
1. 将密码校验删除,因为无法生成模型,用最简单的方法来尝试这些内容。
|
||||||
- [ ] 增加tab栏但是还没有加上对应的功能
|
- [x] 增加tab栏但是还没有加上对应的功能
|
||||||
3. 增加聊天背景选择功能,会员可以自定义背景
|
3. 增加聊天背景选择功能,会员可以自定义背景
|
||||||
4. 增加恋人消息编辑功能,更新**数据库**,加上一些编辑消息、编辑时间相关字段。用户编辑消息之后恋人不回答,只会更新记忆和摘要的数据库,下一次回答的时候会重新引用这个更新后的记忆。
|
4. 增加恋人消息编辑功能,更新**数据库**,加上一些编辑消息、编辑时间相关字段。用户编辑消息之后恋人不回答,只会更新记忆和摘要的数据库,下一次回答的时候会重新引用这个更新后的记忆。
|
||||||
- [ ] 礼物、换装、音色样式更改,但是还未更新数据库
|
- [ ] 礼物、换装、音色样式更改,但是还未更新数据库
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
- [x] 克隆音色API填写然后给你测试
|
- [x] 克隆音色API填写然后给你测试
|
||||||
7. 二维码推广功能:创建邀请码邀请新用户,但是没有二维码生成API
|
7. 二维码推广功能:创建邀请码邀请新用户,但是没有二维码生成API
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 次要接口对齐和UI完善
|
# 次要接口对齐和UI完善
|
||||||
> 1. Tab栏中各内容对齐
|
> 1. Tab栏中各内容对齐
|
||||||
> 2. 短剧、商城、音乐库对接
|
> 2. 短剧、商城、音乐库对接
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user