407 lines
17 KiB
Plaintext
407 lines
17 KiB
Plaintext
<!-- AI智能体聊天详情页面 - 基于Figma设计 -->
|
||
<view class="page-container">
|
||
<!-- 状态栏 -->
|
||
<view class="status-bar-area">
|
||
<view class="status-bar" style="height: {{statusBarHeight}}px;"></view>
|
||
</view>
|
||
|
||
<!-- 顶部导航栏 -->
|
||
<view class="nav-header" style="top: {{statusBarHeight}}px;">
|
||
<view class="nav-content">
|
||
<!-- 返回按钮 -->
|
||
<view class="nav-back" bindtap="onBack">
|
||
<image src="/images/icon-back-arrow.png" class="back-icon" mode="aspectFit"></image>
|
||
<text class="back-text">返回</text>
|
||
</view>
|
||
|
||
<!-- 中间角色信息 - 点击头像跳转到角色详情 -->
|
||
<view class="nav-center" bindtap="onGoToCharacterDetail">
|
||
<view class="nav-avatar-wrap">
|
||
<image wx:if="{{character.avatar}}" class="nav-avatar" src="{{character.avatar}}" mode="aspectFill"></image>
|
||
<view wx:else class="nav-avatar-placeholder">
|
||
<text>{{character.name[0] || 'AI'}}</text>
|
||
</view>
|
||
</view>
|
||
<text class="nav-name">{{character.name}}</text>
|
||
<view class="online-dot" wx:if="{{character.isOnline}}"></view>
|
||
</view>
|
||
|
||
<!-- 右侧占位,避免中间内容偏移 -->
|
||
<view class="nav-right-placeholder"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 聊天内容区域 -->
|
||
<view class="chat-area-wrapper" style="top: {{navHeight + statusBarHeight}}px;" bindtap="onTapChatArea">
|
||
<scroll-view
|
||
scroll-y
|
||
class="chat-scroll"
|
||
scroll-into-view="{{scrollIntoView}}"
|
||
scroll-with-animation="{{true}}"
|
||
enhanced="{{true}}"
|
||
show-scrollbar="{{false}}"
|
||
bindscroll="onScroll"
|
||
scroll-top="{{scrollTop}}"
|
||
>
|
||
<!-- 加载更多提示 -->
|
||
<view class="load-more-hint" wx:if="{{hasMore && !isFirstLoad}}">
|
||
<view class="load-more-content" wx:if="{{loadingMore}}">
|
||
<view class="loading-spinner"></view>
|
||
<text>加载中...</text>
|
||
</view>
|
||
<text wx:else class="load-more-text">向上滑动加载更多</text>
|
||
</view>
|
||
|
||
<view class="no-more-hint" wx:if="{{!hasMore && messages.length > 20}}">
|
||
<text>没有更多消息了</text>
|
||
</view>
|
||
|
||
<!-- 加密对话提示 -->
|
||
<view class="encrypt-hint">
|
||
<text>与 {{character.name}} 的加密对话</text>
|
||
</view>
|
||
|
||
<!-- 聊天消息列表 -->
|
||
<view class="chat-list">
|
||
<view
|
||
class="chat-item {{item.isMe ? 'me' : 'other'}}"
|
||
wx:for="{{messages}}"
|
||
wx:key="id"
|
||
id="msg-{{index}}"
|
||
>
|
||
<!-- AI消息(左侧) -->
|
||
<block wx:if="{{!item.isMe}}">
|
||
<view class="avatar-wrap">
|
||
<image wx:if="{{character.avatar}}" class="chat-avatar" src="{{character.avatar}}" mode="aspectFill"></image>
|
||
<view wx:else class="avatar-placeholder">
|
||
<text class="avatar-text">{{character.name[0] || 'AI'}}</text>
|
||
</view>
|
||
</view>
|
||
<view class="message-content">
|
||
<!-- 文字消息 -->
|
||
<view class="chat-bubble other" wx:if="{{!item.type || item.type === 'text'}}" bindlongpress="onMessageLongPress" data-item="{{item}}">
|
||
<text class="chat-text" decode="{{true}}">{{item.text}}</text>
|
||
</view>
|
||
<!-- 图片消息 -->
|
||
<view class="chat-bubble-image other" wx:elif="{{item.type === 'image'}}">
|
||
<image class="message-image" src="{{item.imageUrl}}" mode="widthFix" bindtap="onPreviewImage" data-url="{{item.imageUrl}}"></image>
|
||
</view>
|
||
<!-- 语音消息 -->
|
||
<view class="chat-bubble voice other {{playingVoiceId === item.id ? 'playing' : ''}}" wx:elif="{{item.type === 'voice'}}" bindtap="onPlayVoice" data-id="{{item.id}}" data-url="{{item.audioUrl}}">
|
||
<view class="voice-waves">
|
||
<view class="voice-wave-bar"></view>
|
||
<view class="voice-wave-bar"></view>
|
||
<view class="voice-wave-bar"></view>
|
||
</view>
|
||
<text class="voice-duration">{{item.duration || 1}}″</text>
|
||
</view>
|
||
<view class="message-actions" wx:if="{{!item.type || item.type === 'text'}}">
|
||
<text class="message-time">{{item.time}}</text>
|
||
<!-- AI语音播放按钮 -->
|
||
<view class="play-voice-btn" wx:if="{{character.voiceId}}" bindtap="onPlayAIVoice" data-id="{{item.id}}" data-text="{{item.text}}">
|
||
<image src="/images/icon-voice.png" class="play-voice-icon" mode="aspectFit"></image>
|
||
</view>
|
||
</view>
|
||
<text class="message-time" wx:else>{{item.time}}</text>
|
||
</view>
|
||
</block>
|
||
|
||
<!-- 用户消息(右侧) -->
|
||
<block wx:else>
|
||
<view class="message-content me">
|
||
<!-- 文字消息 -->
|
||
<view class="chat-bubble me" wx:if="{{!item.type || item.type === 'text'}}" bindlongpress="onMessageLongPress" data-item="{{item}}">
|
||
<text class="chat-text" decode="{{true}}">{{item.text}}</text>
|
||
</view>
|
||
<!-- 图片消息 -->
|
||
<view class="chat-bubble-image me" wx:elif="{{item.type === 'image'}}">
|
||
<image class="message-image" src="{{item.imageUrl}}" mode="widthFix" bindtap="onPreviewImage" data-url="{{item.imageUrl}}"></image>
|
||
</view>
|
||
<!-- 语音消息 -->
|
||
<view class="chat-bubble voice me {{playingVoiceId === item.id ? 'playing' : ''}}" wx:elif="{{item.type === 'voice'}}" bindtap="onPlayVoice" data-id="{{item.id}}" data-url="{{item.audioUrl}}">
|
||
<text class="voice-duration">{{item.duration || 1}}″</text>
|
||
<view class="voice-waves">
|
||
<view class="voice-wave-bar"></view>
|
||
<view class="voice-wave-bar"></view>
|
||
<view class="voice-wave-bar"></view>
|
||
</view>
|
||
</view>
|
||
<!-- 语音识别文字 -->
|
||
<view class="voice-recognized-text" wx:if="{{item.type === 'voice' && (item.recognizing || item.recognizedText)}}">
|
||
<text wx:if="{{item.recognizing}}" class="recognizing-hint">识别中...</text>
|
||
<text wx:else class="recognized-text">{{item.recognizedText}}</text>
|
||
</view>
|
||
<!-- 礼物消息 -->
|
||
<view class="gift-message me" wx:elif="{{item.type === 'gift'}}">
|
||
<view class="gift-message-content">
|
||
<image class="gift-message-image" src="{{item.giftInfo.image}}" mode="aspectFit"></image>
|
||
<text class="gift-message-text">{{item.text}}</text>
|
||
</view>
|
||
</view>
|
||
<text class="message-time">{{item.time}}</text>
|
||
</view>
|
||
<view class="avatar-wrap user-avatar">
|
||
<image wx:if="{{myAvatar}}" class="chat-avatar" src="{{myAvatar}}" mode="aspectFill" binderror="onAvatarError"></image>
|
||
<view wx:else class="avatar-placeholder user">
|
||
<text class="avatar-text">我</text>
|
||
</view>
|
||
</view>
|
||
</block>
|
||
</view>
|
||
|
||
<!-- 正在输入提示 -->
|
||
<view class="chat-item other" wx:if="{{isTyping}}">
|
||
<view class="avatar-wrap">
|
||
<image wx:if="{{character.avatar}}" class="chat-avatar" src="{{character.avatar}}" mode="aspectFill"></image>
|
||
<view wx:else class="avatar-placeholder">
|
||
<text class="avatar-text">{{character.name[0] || 'AI'}}</text>
|
||
</view>
|
||
</view>
|
||
<view class="message-content">
|
||
<view class="chat-bubble other typing">
|
||
<view class="typing-dot"></view>
|
||
<view class="typing-dot"></view>
|
||
<view class="typing-dot"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部占位 -->
|
||
<view class="chat-bottom-space"></view>
|
||
<!-- 底部锚点 - 用于滚动定位 -->
|
||
<view id="chat-bottom-anchor"></view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 面板打开时的透明遮罩层 - 点击关闭面板 -->
|
||
<view class="panel-overlay" wx:if="{{showEmoji || showMorePanel}}" bindtap="onClosePanels"></view>
|
||
|
||
<!-- 底部输入区域 - Figma设计样式 -->
|
||
<view class="bottom-input-area {{showEmoji || showMorePanel ? 'panel-open' : ''}}">
|
||
<view class="input-container figma-input-container">
|
||
<!-- 语音/键盘切换按钮 - Figma样式 -->
|
||
<view class="figma-voice-btn" bindtap="onVoiceMode">
|
||
<image src="{{isVoiceMode ? '/images/icon-keyboard.png' : '/images/chat-input-voice.png'}}" class="figma-btn-icon" mode="aspectFit"></image>
|
||
</view>
|
||
|
||
<!-- 语音模式:按住说话按钮 -->
|
||
<view
|
||
wx:if="{{isVoiceMode}}"
|
||
class="voice-record-btn {{isRecording ? 'recording' : ''}}"
|
||
bindtouchstart="onVoiceTouchStart"
|
||
bindtouchmove="onVoiceTouchMove"
|
||
bindtouchend="onVoiceTouchEnd"
|
||
bindtouchcancel="onVoiceTouchCancel"
|
||
>
|
||
<text>{{isRecording ? (voiceCancelHint ? '松开 取消' : '松开 发送') : '按住 说话'}}</text>
|
||
</view>
|
||
|
||
<!-- 文字模式:输入框 - Figma样式 -->
|
||
<view wx:else class="figma-input-wrap">
|
||
<input
|
||
class="figma-text-input"
|
||
placeholder="{{isUnlocked ? '发消息...' : (freeTime.isActive ? '限时免费畅聊中...' : '发消息...')}}"
|
||
placeholder-class="figma-input-placeholder"
|
||
value="{{inputText}}"
|
||
bindinput="onInput"
|
||
confirm-type="send"
|
||
bindconfirm="onSend"
|
||
focus="{{inputFocus}}"
|
||
adjust-position="{{!showEmoji}}"
|
||
hold-keyboard="{{true}}"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 表情按钮 - Figma样式 -->
|
||
<view class="figma-emoji-btn {{showEmoji ? 'active' : ''}}" bindtap="onEmojiToggle">
|
||
<image src="{{showEmoji ? '/images/icon-keyboard.png' : '/images/chat-input-emoji.png'}}" class="figma-btn-icon" mode="aspectFit"></image>
|
||
</view>
|
||
|
||
<!-- 发送/更多按钮 - Figma样式 -->
|
||
<view class="figma-send-btn" wx:if="{{inputText.length > 0 && !isVoiceMode}}" bindtap="onSend">
|
||
<image src="/images/icon-send.png" class="figma-btn-icon" mode="aspectFit"></image>
|
||
</view>
|
||
<view class="figma-add-btn {{showMorePanel ? 'active' : ''}}" wx:else bindtap="onAddMore">
|
||
<image src="/images/chat-input-plus.png" class="figma-btn-icon" mode="aspectFit"></image>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 表情面板 -->
|
||
<view class="emoji-panel" wx:if="{{showEmoji}}">
|
||
<scroll-view scroll-y class="emoji-scroll">
|
||
<view class="emoji-grid">
|
||
<view
|
||
class="emoji-item"
|
||
wx:for="{{emojis}}"
|
||
wx:key="*this"
|
||
data-emoji="{{item}}"
|
||
bindtap="onEmojiSelect"
|
||
>
|
||
<text class="emoji-text">{{item}}</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 更多功能面板 - AI角色聊天只显示基础功能(Figma设计样式) -->
|
||
<view class="more-panel" wx:if="{{showMorePanel}}">
|
||
<view class="more-panel-content">
|
||
<!-- AI角色聊天:只显示照片、拍摄、礼物(按Figma顺序) -->
|
||
<view class="more-grid ai-chat-grid">
|
||
<!-- 照片(相册) -->
|
||
<view class="more-item" bindtap="onChooseImage">
|
||
<view class="more-icon-wrap figma-style">
|
||
<image class="figma-action-icon" src="/images/chat-action-photo.png" mode="aspectFit"></image>
|
||
</view>
|
||
<text class="more-text">照片</text>
|
||
</view>
|
||
<!-- 拍摄(拍照) -->
|
||
<view class="more-item" bindtap="onTakePhoto">
|
||
<view class="more-icon-wrap figma-style">
|
||
<image class="figma-action-icon" src="/images/chat-action-camera.png" mode="aspectFit"></image>
|
||
</view>
|
||
<text class="more-text">拍摄</text>
|
||
</view>
|
||
<!-- 礼物 -->
|
||
<view class="more-item" bindtap="onSendGift">
|
||
<view class="more-icon-wrap figma-style">
|
||
<image class="figma-action-icon" src="/images/chat-action-gift.png" mode="aspectFit"></image>
|
||
</view>
|
||
<text class="more-text">礼物</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- 底部安全区域 -->
|
||
<view class="more-panel-safe"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 语音录制提示浮层 -->
|
||
<view class="voice-recording-mask" wx:if="{{isRecording}}">
|
||
<view class="voice-recording-popup {{voiceCancelHint ? 'cancel' : ''}}">
|
||
<view class="voice-wave" wx:if="{{!voiceCancelHint}}">
|
||
<view class="wave-bar"></view>
|
||
<view class="wave-bar"></view>
|
||
<view class="wave-bar"></view>
|
||
<view class="wave-bar"></view>
|
||
<view class="wave-bar"></view>
|
||
</view>
|
||
<image wx:else class="cancel-icon" src="/images/icon-close.png" mode="aspectFit"></image>
|
||
<text class="voice-tip">{{voiceCancelHint ? '松开手指,取消发送' : '手指上划,取消发送'}}</text>
|
||
<text class="voice-duration-tip" wx:if="{{!voiceCancelHint}}">{{recordingDuration}}″</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 礼物选择弹窗 -->
|
||
<view class="gift-popup-mask" wx:if="{{showGiftPopup}}" bindtap="onCloseGiftPopup">
|
||
<view class="gift-popup" catchtap="preventMove">
|
||
<view class="gift-popup-header">
|
||
<text class="gift-popup-title">选择礼物</text>
|
||
<view class="gift-popup-close" bindtap="onCloseGiftPopup">
|
||
<image src="/images/icon-close.png" class="close-icon" mode="aspectFit"></image>
|
||
</view>
|
||
</view>
|
||
<scroll-view scroll-y class="gift-list-scroll">
|
||
<view class="gift-grid">
|
||
<view
|
||
class="gift-item {{selectedGift && selectedGift.id === item.id ? 'selected' : ''}}"
|
||
wx:for="{{giftList}}"
|
||
wx:key="id"
|
||
bindtap="onSelectGift"
|
||
data-gift="{{item}}"
|
||
>
|
||
<image class="gift-image" src="{{item.image}}" mode="aspectFit"></image>
|
||
<text class="gift-name">{{item.name}}</text>
|
||
<view class="gift-price">
|
||
<image src="/images/icon-heart-filled.png" class="price-icon" mode="aspectFit"></image>
|
||
<text>{{item.price}}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
<view class="gift-popup-footer">
|
||
<view class="gift-balance">
|
||
<text class="balance-label">我的花朵:</text>
|
||
<image src="/images/icon-heart-filled.png" class="balance-icon" mode="aspectFit"></image>
|
||
<text class="balance-value">{{userFlowers || 0}}</text>
|
||
</view>
|
||
<view class="gift-send-btn {{selectedGift ? 'active' : ''}}" bindtap="onConfirmSendGift">
|
||
<text>赠送</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 解锁角色弹窗 - 与首页样式一致 -->
|
||
<view class="unlock-popup-mask" wx:if="{{showUnlockPopup}}" bindtap="closeUnlockPopup" catchtouchmove="preventTouchMove">
|
||
<view class="unlock-popup" catchtap="preventBubble">
|
||
<!-- 关闭按钮 -->
|
||
<view class="unlock-popup-close" bindtap="closeUnlockPopup">
|
||
<text>×</text>
|
||
</view>
|
||
|
||
<!-- 顶部白色区域 -->
|
||
<view class="unlock-popup-header">
|
||
<!-- 角色头像 -->
|
||
<view class="unlock-avatar-container">
|
||
<view class="unlock-avatar-wrap">
|
||
<image class="unlock-avatar" src="{{character.avatar}}" mode="aspectFill"></image>
|
||
</view>
|
||
<!-- 锁图标 -->
|
||
<view class="unlock-lock-icon">
|
||
<image src="/images/icon-lock.png" mode="aspectFit"></image>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 标题 -->
|
||
<view class="unlock-title">
|
||
<text>解锁与 </text>
|
||
<text class="highlight">{{character.name}}</text>
|
||
<text> 的专属聊天</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 解锁选项列表 -->
|
||
<view class="unlock-options">
|
||
<!-- 100爱心解锁 -->
|
||
<view class="unlock-option-item hearts-option" bindtap="onExchangeHearts">
|
||
<view class="option-left">
|
||
<view class="option-icon hearts-icon">
|
||
<image src="/images/icon-heart-filled.png" mode="aspectFit"></image>
|
||
</view>
|
||
<view class="option-info">
|
||
<text class="option-title">{{unlockHeartsCost}} 爱心</text>
|
||
<text class="option-desc">{{heartCount >= unlockHeartsCost ? '爱心值充足 立即兑换' : '爱心值不足 去充值'}}</text>
|
||
</view>
|
||
</view>
|
||
<view class="option-btn hearts-btn">
|
||
<text>兑换</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 现金购买选项 -->
|
||
<view class="unlock-option-item money-option" bindtap="onPurchaseDirect">
|
||
<view class="option-left">
|
||
<view class="option-icon money-icon">
|
||
<text class="money-symbol">¥</text>
|
||
</view>
|
||
<view class="option-info light">
|
||
<text class="option-title">9.9元</text>
|
||
<text class="option-desc">限时特惠 立即购买</text>
|
||
</view>
|
||
</view>
|
||
<view class="option-btn money-btn">
|
||
<text>购买</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 暂不需要 -->
|
||
<view class="unlock-cancel" bindtap="closeUnlockPopup">
|
||
<text>暂不需要</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|