feat: sync local changes

This commit is contained in:
xin 2026-02-04 15:48:45 +08:00
parent ff4c7b7e09
commit 88ffbbc0a3
40 changed files with 821 additions and 160 deletions

View File

@ -16,6 +16,7 @@
"pages/chat/chat",
"pages/chat-detail/chat-detail",
"pages/profile/profile",
"pages/membership-benefits/membership-benefits",
"pages/login/login",
"pages/recharge/recharge",
"pages/character-detail/character-detail",

47
auto_sync.ps1 Normal file
View File

@ -0,0 +1,47 @@
$ErrorActionPreference = "Stop"
$gitPath = ""
$commonPaths = @(
"C:\Program Files\Git\bin\git.exe",
"C:\Program Files (x86)\Git\bin\git.exe",
"$env:LocalAppData\Programs\Git\bin\git.exe",
"D:\Git\bin\git.exe",
"D:\Program Files\Git\bin\git.exe"
)
foreach ($path in $commonPaths) {
if (Test-Path $path) {
$gitPath = $path
break
}
}
if (-not $gitPath) {
try {
$gitPath = Get-Command git -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source
} catch {}
}
if (-not $gitPath) {
Write-Host "Error: Git not found. Please run this in your local terminal where Git is installed." -ForegroundColor Red
exit 1
}
Write-Host "Found Git: $gitPath" -ForegroundColor Green
try {
Write-Host "--- Syncing started ---" -ForegroundColor Yellow
& $gitPath add -A
$date = Get-Date -Format "yyyy-MM-dd HH:mm"
$message = "feat: UI and logic updates ($date)"
& $gitPath commit -m $message | Out-Null
Write-Host "Pushing to remote..." -ForegroundColor Yellow
& $gitPath push
Write-Host "--- Sync successful! ---" -ForegroundColor Green
} catch {
Write-Host "Error during sync: $($_.Exception.Message)" -ForegroundColor Red
}

View File

@ -30,6 +30,7 @@ const ICONS = {
'scan': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="CURRENT" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7V5a2 2 0 0 1 2-2h2"/><path d="M16 3h2a2 2 0 0 1 2 2v2"/><path d="M20 17v2a2 2 0 0 1-2 2h-2"/><path d="M8 21H6a2 2 0 0 1-2-2v-2"/><path d="M7 12h10"/></svg>',
'camera': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="CURRENT" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 4h-5L8 6H5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-3l-1.5-2z"/><circle cx="12" cy="13" r="3"/></svg>',
'clipboard': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="CURRENT" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/></svg>',
'credit-card': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="CURRENT" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect><line x1="1" y1="10" x2="23" y2="10"></line></svg>',
'trending-up': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="CURRENT" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline><polyline points="17 6 23 6 23 12"></polyline></svg>',
'map-pin': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="CURRENT" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 1 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>',
'heart': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="CURRENT" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>',

View File

@ -7,25 +7,19 @@ echo.
cd /d "%~dp0"
set /p msg=请输入提交信息(例如: feat: 更新小程序代码):
if "%msg%"=="" set msg=chore: sync
echo [1/5] 添加所有更改...
git add -A
echo.
echo [2/5] 提交更改...
git commit -m "feat: 更新小程序代码 - 2026-02-02"
git commit -m "%msg%"
echo.
echo [3/5] 添加 tag...
git tag -a v1.0.0 -m "Version 1.0.0 - 2026-02-02"
echo.
echo [4/5] 推送到远程仓库...
echo 请输入密码: zy12345678
git push https://zhiyun:zy12345678@git.maimanji.com/adminzy/ai-c.git master --force
echo.
echo [5/5] 推送 tag...
git push https://zhiyun:zy12345678@git.maimanji.com/adminzy/ai-c.git v1.0.0
echo [3/4] 推送到远程仓库...
git push
echo.
echo ========================

View File

@ -1,6 +1,4 @@
#!/bin/bash
# Git 提交脚本
cd "$(dirname "$0")"
echo "========================"
@ -8,24 +6,21 @@ echo "Git 提交脚本"
echo "========================"
echo ""
echo "[1/5] 添加所有更改..."
read -r -p "请输入提交信息(例如: feat: 更新小程序代码): " msg
if [ -z "$msg" ]; then
msg="chore: sync"
fi
echo "[1/3] 添加所有更改..."
git add -A
echo ""
echo "[2/5] 提交更改..."
git commit -m "feat: 更新小程序代码 - 2026-02-02"
echo "[2/3] 提交更改..."
git commit -m "$msg"
echo ""
echo "[3/5] 添加 tag..."
git tag -a v1.0.0 -m "Version 1.0.0 - 2026-02-02"
echo ""
echo "[4/5] 推送到远程仓库..."
git push https://zhiyun:zy12345678@git.maimanji.com/adminzy/ai-c.git master --force
echo ""
echo "[5/5] 推送 tag..."
git push https://zhiyun:zy12345678@git.maimanji.com/adminzy/ai-c.git v1.0.0
echo "[3/3] 推送到远程仓库..."
git push
echo ""
echo "========================"

BIN
images/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View File

@ -165,11 +165,11 @@
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="bar-left">
<!-- 收藏按钮 -->
<view class="action-btn" bindtap="onToggleFavorite">
<!-- 收藏按钮 - 根据需求隐藏 -->
<!-- <view class="action-btn" bindtap="onToggleFavorite">
<app-icon name="{{activity.is_favorited ? 'heart-filled' : 'heart'}}" size="56" color="{{activity.is_favorited ? '#FF5252' : '#4A5565'}}" />
<text class="action-text">{{activity.is_favorited ? '已收藏' : '收藏'}}</text>
</view>
</view> -->
<!-- 分享按钮 -->
<button class="action-btn share-btn" open-type="share">

View File

@ -501,7 +501,8 @@ page {
.bar-left {
display: flex;
align-items: center;
gap: 12rpx;
justify-content: flex-start;
margin-right: 40rpx;
}
.action-btn {
@ -512,7 +513,7 @@ page {
background: transparent;
border: none;
padding: 0;
width: 80rpx;
min-width: 80rpx;
}
.share-btn::after {
@ -676,16 +677,20 @@ page {
position: absolute;
top: 30rpx;
right: 30rpx;
width: 60rpx;
height: 60rpx;
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
background: #F8FAFC;
border-radius: 50%;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
}
.qrcode-modal .close-icon {
width: 32rpx;
height: 32rpx;
width: 48rpx;
height: 48rpx;
opacity: 0.6;
}
.qrcode-modal .modal-title {

View File

@ -76,11 +76,17 @@
<!-- 底部操作按钮 -->
<view class="action-bar">
<view class="action-btn dislike-btn" bindtap="onDislike">
<image src="/images/icon-close.png" class="action-btn-icon" mode="aspectFit"></image>
<view class="action-group" bindtap="onDislike">
<view class="action-btn dislike-btn">
<image src="/images/icon-close.png" class="action-btn-icon" mode="aspectFit"></image>
</view>
<text class="action-label">返回</text>
</view>
<view class="action-btn chat-btn" bindtap="onChat">
<image src="/images/icon-comment.png" class="action-btn-icon" mode="aspectFit"></image>
<view class="action-group" bindtap="onChat">
<view class="action-btn chat-btn">
<image src="/images/icon-comment.png" class="action-btn-icon" mode="aspectFit"></image>
</view>
<text class="action-label highlight">去聊天</text>
</view>
</view>
@ -127,7 +133,7 @@
</view>
<view class="heart-option-info">
<text class="heart-option-title">{{unlockHeartsCost}}爱心</text>
<text class="heart-option-desc">{{userLovePoints >= unlockHeartsCost ? '余额充足 立即兑换' : '爱心值不足 去充值'}}</text>
<text class="heart-option-desc">{{userLovePoints >= unlockHeartsCost ? '余额充足 立即兑换' : '爱心不足 立即充值'}}</text>
</view>
</view>
<view class="heart-option-btn exchange-btn">兑换</view>

View File

@ -350,13 +350,32 @@
bottom: 0;
left: 0;
right: 0;
height: 220rpx;
background: linear-gradient(to top, #fff 60%, transparent);
height: 280rpx;
background: linear-gradient(to top, #fff 80%, transparent);
display: flex;
align-items: center;
justify-content: center;
gap: 80rpx;
gap: 120rpx;
padding-bottom: env(safe-area-inset-bottom);
z-index: 100;
}
.action-group {
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
}
.action-label {
font-size: 26rpx;
font-weight: 600;
color: #6a7282;
letter-spacing: 2rpx;
}
.action-label.highlight {
color: #07C160;
}
.action-btn {
@ -365,38 +384,38 @@
justify-content: center;
border-radius: 50%;
box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.12);
transition: transform 0.2s ease;
transition: all 0.2s ease;
}
.action-btn:active {
transform: scale(0.95);
.action-group:active .action-btn {
transform: scale(0.92);
}
/* X按钮 - 不喜欢 */
.action-btn.dislike-btn {
width: 140rpx;
height: 140rpx;
width: 130rpx;
height: 130rpx;
background: #fff;
border: 3rpx solid #f3f4f6;
}
.action-btn.dislike-btn .action-btn-icon {
width: 64rpx;
height: 64rpx;
width: 56rpx;
height: 56rpx;
opacity: 0.6;
}
/* 对话按钮 - 微信绿色系 */
.action-btn.chat-btn {
width: 140rpx;
height: 140rpx;
width: 130rpx;
height: 130rpx;
background: #07C160;
box-shadow: 0 0 0 6rpx rgba(7, 193, 96, 0.15), 0 12rpx 32rpx rgba(7, 193, 96, 0.35);
}
.action-btn.chat-btn .action-btn-icon {
width: 64rpx;
height: 64rpx;
width: 56rpx;
height: 56rpx;
filter: brightness(0) invert(1);
}

View File

@ -201,7 +201,7 @@
<view wx:else class="figma-input-wrap">
<input
class="figma-text-input"
placeholder="{{isUnlocked ? '发消息...' : (freeTime.isActive ? '限时免费畅聊中...' : '发消息...')}}"
placeholder="限时免费陪伴中"
placeholder-class="figma-input-placeholder"
value="{{inputText}}"
bindinput="onInput"
@ -372,7 +372,7 @@
</view>
<view class="option-info">
<text class="option-title">{{unlockHeartsCost}} 爱心</text>
<text class="option-desc">{{heartCount >= unlockHeartsCost ? '爱心值充足 立即兑换' : '爱心值不足 去充值'}}</text>
<text class="option-desc">{{heartCount >= unlockHeartsCost ? '爱心值充足 立即兑换' : '爱心不足 立即充值'}}</text>
</view>
</view>
<view class="option-btn hearts-btn">

View File

@ -9,8 +9,8 @@
<!-- 消息列表 -->
<scroll-view scroll-y class="message-list">
<!-- 免费畅聊时间提醒 (显示在推广收益上方) -->
<view class="free-chat-banner" wx:if="{{freeTime && freeTime.isActive && countdownText}}" bindtap="onFreeChatTap">
<!-- 免费畅聊时间提醒 (显示在推广收益上方) - 已根据需求隐藏 -->
<view class="free-chat-banner" wx:if="{{false && freeTime && freeTime.isActive && countdownText}}" bindtap="onFreeChatTap">
<view class="free-chat-banner-content">
<image src="/images/icon-clock-red.png" class="banner-clock-icon" mode="aspectFit"></image>
<text class="banner-text">剩余 {{countdownText}} 分 可以免费畅聊</text>

View File

@ -338,20 +338,25 @@ page {
/* 空状态 */
.empty-state {
padding: 120rpx 32rpx;
text-align: center;
padding: 160rpx 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-icon {
width: 200rpx;
height: 200rpx;
margin: 0 auto 32rpx;
opacity: 0.5;
width: 320rpx;
height: 320rpx;
margin-bottom: 32rpx;
opacity: 0.8;
}
.empty-text {
font-size: 28rpx;
color: #B39DDB;
font-size: 30rpx;
color: #94A3B8;
font-weight: 500;
letter-spacing: 2rpx;
}
/* 二维码弹窗 */

View File

@ -287,6 +287,11 @@ Page({
wx.navigateTo({
url: '/pages/promote/promote'
})
} else if (res.cancel) {
// 用户点击暂不需要,退出当前页回到上一页
wx.navigateBack({
delta: 1
})
}
}
})

View File

@ -524,8 +524,7 @@ swiper-item {
/* 空状态 */
.empty-state {
padding: 200rpx 0;
text-align: center;
padding: 160rpx 0;
display: flex;
flex-direction: column;
align-items: center;
@ -533,15 +532,17 @@ swiper-item {
}
.empty-icon {
width: 240rpx;
height: 240rpx;
margin: 0 auto 32rpx;
opacity: 0.5;
width: 320rpx;
height: 320rpx;
margin-bottom: 32rpx;
opacity: 0.8;
}
.empty-text {
font-size: 32rpx;
color: #99A1AF;
font-size: 30rpx;
color: #94A3B8;
font-weight: 500;
letter-spacing: 2rpx;
}
/* 列表底部 */

View File

@ -370,19 +370,25 @@ page {
/* 空状态 */
.empty-state {
padding: 100rpx 0;
text-align: center;
padding: 160rpx 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-icon {
width: 200rpx;
height: 200rpx;
opacity: 0.5;
width: 320rpx;
height: 320rpx;
margin-bottom: 32rpx;
opacity: 0.8;
}
.empty-text {
font-size: 32rpx;
color: #FFBE76;
font-size: 30rpx;
color: #94A3B8;
font-weight: 500;
letter-spacing: 2rpx;
}
/* 二维码弹窗 */

View File

@ -77,6 +77,9 @@ Page({
showGf100Popup: false,
gf100ImageUrl: '',
// 语音播放状态
isVoicePlaying: false,
// 解锁配置
unlockHeartsCost: 500 // 默认解锁爱心成本
},
@ -527,8 +530,18 @@ Page({
* 移动到下一张卡片
*/
moveToNext() {
const { currentIndex, profiles } = this.data
const { currentIndex, profiles, isVoicePlaying } = this.data
const nextIndex = currentIndex + 1
// 切换卡片时,如果正在播放语音,则停止播放
if (isVoicePlaying && this.audioContext) {
try {
console.log('[index] 切换卡片,停止上一个角色的语音')
this.audioContext.stop()
} catch (e) {
console.error('[index] 停止语音失败:', e)
}
}
// 如果快到末尾,加载更多
if (nextIndex >= profiles.length - 2) {
@ -603,9 +616,17 @@ Page({
* 播放语音优先使用预录制的开场白音频
*/
async onPlayVoice() {
const { currentIndex, profiles } = this.data
const { currentIndex, profiles, isVoicePlaying } = this.data
if (currentIndex >= profiles.length) return
// 如果正在播放,则停止
if (isVoicePlaying && this.audioContext) {
try {
this.audioContext.stop()
} catch (e) {}
return
}
const currentProfile = profiles[currentIndex]
@ -695,8 +716,15 @@ Page({
innerAudioContext.onPlay(() => {
console.log('[index] 音频开始播放, volume:', innerAudioContext.volume)
this.setData({ isVoicePlaying: true })
})
innerAudioContext.onStop(() => {
console.log('[index] 音频停止播放')
this.setData({ isVoicePlaying: false })
wx.hideToast()
})
innerAudioContext.onTimeUpdate(() => {
// 每秒打印一次进度
const currentTime = Math.floor(innerAudioContext.currentTime)
@ -708,6 +736,7 @@ Page({
innerAudioContext.onError((err) => {
console.error('[index] 音频播放错误:', JSON.stringify(err))
this.setData({ isVoicePlaying: false })
wx.hideToast()
let errMsg = '播放失败'
if (err.errCode === 10001 || err.errCode === -1) {
@ -722,6 +751,7 @@ Page({
innerAudioContext.onEnded(() => {
console.log('[index] 音频播放结束')
this.setData({ isVoicePlaying: false })
wx.hideToast()
})

View File

@ -3,7 +3,7 @@
<!-- 顶部导航栏 -->
<view class="unified-header">
<view class="unified-header-left"></view>
<text class="unified-header-title">陪伴</text>
<text class="unified-header-title">关怀陪伴</text>
<view class="unified-header-right"></view>
</view>
@ -111,8 +111,17 @@
<image src="/images/icon-heart{{likedProfiles[profiles[currentIndex].id] ? '-filled' : ''}}.png" class="action-icon" mode="aspectFit"></image>
<text class="action-label">喜欢</text>
</view>
<view class="action-btn voice-btn" catchtap="onPlayVoice">
<image src="/images/icon-voice.png" class="action-icon" mode="aspectFit"></image>
<view class="action-btn voice-btn {{isVoicePlaying ? 'playing' : ''}}" catchtap="onPlayVoice">
<view class="action-icon">
<image src="/images/icon-voice.png" class="voice-image" mode="aspectFit"></image>
<!-- 模拟跳动的线条 -->
<view class="voice-waves" wx:if="{{isVoicePlaying}}">
<view class="wave-line"></view>
<view class="wave-line"></view>
<view class="wave-line"></view>
<view class="wave-line"></view>
</view>
</view>
<text class="action-label">声音</text>
</view>
<view class="action-btn select-btn {{unlockedProfiles[profiles[currentIndex].id] ? 'unlocked' : ''}}" catchtap="onSelectCharacter">
@ -142,7 +151,7 @@
</view>
<view class="option-info">
<text class="option-price">{{unlockHeartsCost}} 爱心</text>
<text class="option-desc">{{heartCount >= unlockHeartsCost ? '爱心值充足 立即兑换' : '爱心值不足 去充值'}}</text>
<text class="option-desc">{{heartCount >= unlockHeartsCost ? '爱心值充足 立即兑换' : '爱心不足 立即充值'}}</text>
</view>
<view class="option-btn">兑换</view>
</view>
@ -218,7 +227,7 @@
</view>
<view class="option-info">
<text class="option-title">{{unlockHeartsCost}}爱心</text>
<text class="option-desc">{{heartCount >= unlockHeartsCost ? '余额充足 立即兑换' : '爱心值不足 去充值'}}</text>
<text class="option-desc">{{heartCount >= unlockHeartsCost ? '余额充足 立即兑换' : '爱心不足 立即充值'}}</text>
</view>
</view>
<view class="option-btn hearts-btn">

View File

@ -320,6 +320,64 @@
background: rgba(145, 69, 132, 0.6);
border: 3rpx solid rgba(255,255,255,0.3);
box-shadow: 0 8rpx 24rpx rgba(145, 69, 132, 0.4);
position: relative;
}
.voice-image {
width: 100%;
height: 100%;
}
/* 播放时的波纹/线条跳动效果 */
.voice-waves {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 6rpx;
background: rgba(145, 69, 132, 0.8);
border-radius: 50%;
}
.wave-line {
width: 6rpx;
height: 20rpx;
background: #fff;
border-radius: 4rpx;
animation: waveJump 0.8s ease-in-out infinite;
}
.wave-line:nth-child(2) {
animation-delay: 0.1s;
height: 40rpx;
}
.wave-line:nth-child(3) {
animation-delay: 0.2s;
height: 30rpx;
}
.wave-line:nth-child(4) {
animation-delay: 0.3s;
height: 15rpx;
}
@keyframes waveJump {
0%, 100% {
transform: scaleY(0.5);
opacity: 0.6;
}
50% {
transform: scaleY(1.2);
opacity: 1;
}
}
.voice-btn.playing .action-icon {
background: rgba(145, 69, 132, 0.9);
box-shadow: 0 0 0 6rpx rgba(145, 69, 132, 0.3), 0 12rpx 32rpx rgba(145, 69, 132, 0.5);
transform: scale(1.05);
}
.action-btn.liked .action-icon {

View File

@ -48,8 +48,9 @@
<view class="loading-tip" wx:if="{{loading}}">加载中...</view>
<!-- 空状态 -->
<view class="empty-tip" wx:if="{{!loading && partnerList.length === 0}}">
<text>暂无兴趣搭子数据</text>
<view class="empty-state" wx:if="{{!loading && partnerList.length === 0}}">
<image src="/images/icon-empty.png" class="empty-icon" mode="aspectFit"></image>
<text class="empty-text">暂无兴趣搭子数据</text>
</view>
</view>
@ -83,7 +84,7 @@
<view class="modal-content" catchtap="{{null}}">
<!-- 关闭按钮 -->
<view class="close-btn" catchtap="onCloseQrcodeModal">
<image src="/images/icon-close-gray.png" class="close-icon" mode="aspectFit"></image>
<image src="/images/icon-close.png" class="close-icon" mode="aspectFit"></image>
</view>
<!-- 标题 -->

View File

@ -122,12 +122,27 @@ page {
color: #999;
}
/* 空状态提示 */
.empty-tip {
text-align: center;
/* 空状态 */
.empty-state {
padding: 120rpx 0;
font-size: 28rpx;
color: #999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-icon {
width: 320rpx;
height: 320rpx;
margin-bottom: 32rpx;
opacity: 0.8;
}
.empty-text {
font-size: 30rpx;
color: #94A3B8;
font-weight: 500;
letter-spacing: 2rpx;
}
/* 保留原有的固定分类图标样式作为备用 */
@ -407,24 +422,26 @@ page {
position: absolute;
top: 32rpx;
right: 32rpx;
width: 72rpx;
height: 72rpx;
background: #F1F5F9;
width: 80rpx;
height: 80rpx;
background: #F8FAFC;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
}
.close-btn:active {
transform: scale(0.9);
background: #E2E8F0;
background: #F1F5F9;
}
.close-icon {
width: 40rpx;
height: 40rpx;
width: 48rpx;
height: 48rpx;
opacity: 0.6;
}
/* 标题 */

View File

@ -16,11 +16,11 @@
<view class="logo-section">
<view class="logo-wrapper">
<view class="logo-circle">
<image src="/images/tab-heart.png" class="logo-icon" mode="aspectFit"></image>
<image src="/images/logo.jpg" class="logo-icon" mode="aspectFit"></image>
</view>
</view>
<text class="app-name">心伴</text>
<text class="app-slogan">你的AI情感陪伴</text>
<text class="app-slogan">欢迎您加入心伴俱乐部</text>
</view>
<!-- 登录按钮区域 -->

View File

@ -65,18 +65,14 @@
.logo-circle {
width: 200rpx;
height: 200rpx;
background: linear-gradient(135deg, #914584 0%, #B378FE 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 20rpx 60rpx rgba(145, 69, 132, 0.3);
}
.logo-icon {
width: 100rpx;
height: 100rpx;
filter: brightness(0) invert(1);
width: 200rpx;
height: 200rpx;
}
.app-name {

View File

@ -0,0 +1,103 @@
const api = require('../../utils/api');
Page({
data: {
statusBarHeight: 20,
navBarHeight: 44,
totalNavHeight: 64,
role: '',
level: '',
currentPackage: null,
loading: true
},
onLoad(options) {
const { role, level } = options;
const systemInfo = wx.getSystemInfoSync();
const statusBarHeight = systemInfo.statusBarHeight || 20;
const menuButton = wx.getMenuButtonBoundingClientRect();
const navBarHeight = menuButton.height + (menuButton.top - statusBarHeight) * 2;
this.setData({
statusBarHeight,
navBarHeight,
totalNavHeight: statusBarHeight + navBarHeight,
role: role || '',
level: level || ''
});
this.loadBenefits();
},
async loadBenefits() {
try {
const res = await api.payment.getPackages();
const body = res.data || {};
const list = Array.isArray(body) ? body : body?.data || [];
if (!list.length) {
this.setData({ loading: false });
return;
}
// 映射逻辑与 recharge.js 保持一致
const allPackages = list.map((p) => {
const attrs = p.attributes || {};
const benefits = p.benefits || attrs.benefits || [];
const gradientStart = p.gradientStart || attrs.gradient_start || '#60A5FA';
const gradientEnd = p.gradientEnd || attrs.gradient_end || '#2563EB';
const tagColor = p.tagColor || attrs.tag_color || '#1E3A8A';
let vipType = attrs.type || p.vipType || '';
if (vipType === 'monthly') vipType = 'month';
if (vipType === 'yearly') vipType = 'year';
if (vipType === 'lifetime') vipType = 'svip';
return {
id: p.id,
title: p.title,
subtitle: p.subtitle || attrs.subtitle || (benefits[0] || ''),
benefits,
vipType,
gradient: `background: linear-gradient(180deg, ${gradientStart}, ${gradientEnd});`,
iconGradient: `background: linear-gradient(180deg, ${gradientStart}, ${gradientEnd});`,
checkColor: tagColor || gradientEnd
};
});
// 根据 role 或 level 寻找匹配的套餐
let currentPackage = null;
if (this.data.role) {
// 优先根据 role 匹配 (soulmate, guardian, companion, listener)
currentPackage = allPackages.find(p => p.vipType === this.data.role);
}
if (!currentPackage && this.data.level) {
// 如果 role 没匹配到,尝试根据 levelText 匹配 (SVIP, VIP)
if (this.data.level === 'SVIP') {
currentPackage = allPackages.find(p => p.vipType === 'svip');
} else if (this.data.level === 'VIP') {
currentPackage = allPackages.find(p => p.vipType === 'month' || p.vipType === 'year');
}
}
// 如果还是没找到,默认取第一个(兜底)
if (!currentPackage && allPackages.length > 0) {
currentPackage = allPackages[0];
}
this.setData({
currentPackage,
loading: false
});
} catch (err) {
console.error('Failed to load benefits:', err);
this.setData({ loading: false });
}
},
onBack() {
wx.navigateBack({ delta: 1 });
}
});

View File

@ -0,0 +1,6 @@
{
"usingComponents": {
"app-icon": "../../components/icon/icon"
},
"navigationStyle": "custom"
}

View File

@ -0,0 +1,60 @@
<view class="page safe-bottom">
<!-- 顶部导航栏 -->
<view class="unified-header">
<view class="unified-header-left" bindtap="onBack">
<image src="/images/icon-back.png" class="unified-back-icon" mode="aspectFit"></image>
<text class="unified-back-text">返回</text>
</view>
<text class="unified-header-title">等级权益</text>
<view class="unified-header-right"></view>
</view>
<view class="content" style="padding-top: {{totalNavHeight}}px">
<!-- 加载中 -->
<view class="loading-state" wx:if="{{loading}}">
<text>正在加载权益内容...</text>
</view>
<!-- 权益内容 -->
<view class="benefits-container" wx:elif="{{currentPackage}}">
<view class="package-header" style="{{currentPackage.gradient}}">
<view class="header-main">
<app-icon name="crown" size="80" color="#ffffff" />
<view class="header-info">
<text class="package-title">{{currentPackage.title}}</text>
<text class="package-subtitle">{{currentPackage.subtitle}}</text>
</view>
</view>
<view class="header-decoration"></view>
</view>
<view class="benefits-section">
<view class="section-title">
<view class="title-line"></view>
<text>专属权益内容</text>
<view class="title-line"></view>
</view>
<view class="benefits-list">
<view class="benefit-item" wx:for="{{currentPackage.benefits}}" wx:key="*this">
<view class="check-icon">
<app-icon name="check" size="36" color="{{currentPackage.checkColor}}" />
</view>
<text class="benefit-text">{{item}}</text>
</view>
</view>
</view>
<view class="footer-tip">
<text>如有疑问,请咨询在线客服</text>
</view>
</view>
<!-- 未找到内容 -->
<view class="empty-state" wx:else>
<app-icon name="info" size="100" color="#9CA3AF" />
<text>暂无该等级的权益说明</text>
<button class="btn-reset back-btn" bindtap="onBack">返回我的</button>
</view>
</view>
</view>

View File

@ -0,0 +1,145 @@
.page {
min-height: 100vh;
background: #F9FAFB;
}
.content {
padding: 32rpx;
}
.loading-state, .empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 200rpx;
color: #9CA3AF;
font-size: 28rpx;
}
.empty-state text {
margin-top: 24rpx;
margin-bottom: 48rpx;
}
.back-btn {
background: #B06AB3;
color: #ffffff;
padding: 20rpx 60rpx;
border-radius: 999rpx;
font-size: 28rpx;
}
/* Package Header */
.package-header {
border-radius: 48rpx;
padding: 60rpx 48rpx;
color: #ffffff;
position: relative;
overflow: hidden;
box-shadow: 0 20rpx 40rpx rgba(0, 0, 0, 0.1);
margin-bottom: 48rpx;
}
.header-main {
display: flex;
align-items: center;
gap: 32rpx;
position: relative;
z-index: 2;
}
.header-info {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.package-title {
font-size: 48rpx;
font-weight: 900;
letter-spacing: 2rpx;
}
.package-subtitle {
font-size: 28rpx;
opacity: 0.9;
}
.header-decoration {
position: absolute;
right: -40rpx;
bottom: -40rpx;
width: 240rpx;
height: 240rpx;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
z-index: 1;
}
/* Benefits Section */
.benefits-section {
background: #ffffff;
border-radius: 40rpx;
padding: 48rpx 40rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.02);
}
.section-title {
display: flex;
align-items: center;
justify-content: center;
gap: 24rpx;
margin-bottom: 48rpx;
}
.section-title text {
font-size: 32rpx;
font-weight: 800;
color: #111827;
}
.title-line {
flex: 1;
height: 2rpx;
background: #F3F4F6;
max-width: 80rpx;
}
.benefits-list {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.benefit-item {
display: flex;
align-items: flex-start;
gap: 24rpx;
}
.check-icon {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.03);
border-radius: 50%;
flex-shrink: 0;
margin-top: 4rpx;
}
.benefit-text {
font-size: 30rpx;
color: #374151;
line-height: 1.6;
font-weight: 500;
}
.footer-tip {
text-align: center;
margin-top: 60rpx;
color: #9CA3AF;
font-size: 24rpx;
}

View File

@ -447,20 +447,25 @@ page {
/* 空状态 */
.empty-state {
padding: 120rpx 32rpx;
text-align: center;
padding: 160rpx 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-icon {
width: 200rpx;
height: 200rpx;
margin: 0 auto 32rpx;
opacity: 0.5;
width: 320rpx;
height: 320rpx;
margin-bottom: 32rpx;
opacity: 0.8;
}
.empty-text {
font-size: 28rpx;
color: #81C784;
font-size: 30rpx;
color: #94A3B8;
font-weight: 500;
letter-spacing: 2rpx;
}
/* 列表底部 */

View File

@ -74,6 +74,9 @@ Page({
} else {
this.setData({
me: { nickname: '未登录', avatar: this.data.defaultAvatar },
vip: { levelText: '', levelClass: '' },
isDistributor: false,
distributorRole: '',
balances: { grass: 0, commission: '0.00' },
counts: { orders: 0, team: 0, performance: 0 },
totalUnread: 0
@ -143,10 +146,13 @@ Page({
'partner': { text: '城市合伙人', class: 'vip-partner' }
};
if (user.isDistributor && roleMap[distributorRole]) {
if (roleMap[distributorRole]) {
const info = roleMap[distributorRole];
roleText = info.text;
roleClass = info.class;
} else if (user.isDistributor) {
roleText = '分销商';
roleClass = 'vip-normal';
} else {
// Fallback to VIP
const vipLevel = Number(user.vip_level || 0);
@ -159,7 +165,8 @@ Page({
this.setData({
me: { id, idShort, nickname, avatar, phone },
vip: { levelText: roleText || '', levelClass: roleClass },
isDistributor: !!user.isDistributor
isDistributor: !!user.isDistributor,
distributorRole: distributorRole // 保存角色以供权益页面使用
});
// CRITICAL: Update global data and local storage to ensure avatar consistency across pages
@ -287,6 +294,20 @@ Page({
wx.navigateTo({ url: '/pages/recharge/recharge' });
}
},
showBenefits() {
if (this.requireLogin()) {
const { distributorRole, vip } = this.data;
// 如果没有角色且不是VIP则跳转到充值页面
if (!distributorRole && !vip.levelText) {
this.goRecharge();
return;
}
wx.navigateTo({
url: `/pages/membership-benefits/membership-benefits?role=${distributorRole || ''}&level=${vip.levelText || ''}`
});
}
},
goOrders() {
if (this.requireLogin()) {
wx.navigateTo({ url: '/pages/orders/orders' });

View File

@ -15,7 +15,7 @@
<view class="profile-info">
<view class="name-row">
<text class="nickname">{{me.nickname}}</text>
<view class="vip-badge {{vip.levelClass}}" bindtap="goRecharge" wx:if="{{vip.levelText}}">
<view class="vip-badge {{vip.levelClass}}" bindtap="showBenefits" wx:if="{{vip.levelText}}">
<app-icon name="crown" size="24" color="#FFFFFF" />
<text class="vip-text">{{vip.levelText}}</text>
<app-icon name="chevron-right" size="20" color="#FFFFFF" />
@ -168,7 +168,7 @@
<view class="menu-item" bindtap="goEdit">
<view class="menu-left">
<app-icon name="settings" size="44" color="#9CA3AF" />
<text class="menu-text">修改资料</text>
<text class="menu-text">个人资料</text>
</view>
<app-icon name="chevron-right" size="36" color="#E5E7EB" />
</view>

View File

@ -96,27 +96,32 @@ page {
.name-row {
display: flex;
align-items: center;
gap: 24rpx;
gap: 16rpx;
margin-bottom: 16rpx;
width: 100%;
}
.nickname {
font-size: 40rpx;
font-size: 36rpx;
font-weight: 900;
color: #111827;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 240rpx;
flex-shrink: 1;
}
.vip-badge {
background: linear-gradient(90deg, #B06AB3, #8E44AD); /* Default/fallback */
padding: 4rpx 20rpx;
padding: 4rpx 16rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
gap: 8rpx;
gap: 6rpx;
box-shadow: 0 2rpx 4rpx rgba(0,0,0,0.1);
flex-shrink: 0;
white-space: nowrap;
}
.vip-badge.vip-soulmate {

View File

@ -410,20 +410,25 @@ page {
/* 空状态 */
.empty-state {
padding: 120rpx 32rpx;
text-align: center;
padding: 160rpx 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-icon {
width: 200rpx;
height: 200rpx;
margin: 0 auto 32rpx;
opacity: 0.5;
width: 320rpx;
height: 320rpx;
margin-bottom: 32rpx;
opacity: 0.8;
}
.empty-text {
font-size: 28rpx;
color: #F06292;
font-size: 30rpx;
color: #94A3B8;
font-weight: 500;
letter-spacing: 2rpx;
}
/* 列表底部 */
@ -481,18 +486,20 @@ page {
position: absolute;
top: 32rpx;
right: 32rpx;
width: 72rpx;
height: 72rpx;
background: #F1F5F9;
width: 80rpx;
height: 80rpx;
background: #F8FAFC;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
}
.close-icon {
width: 40rpx;
height: 40rpx;
width: 48rpx;
height: 48rpx;
opacity: 0.6;
}
.modal-title {

View File

@ -117,8 +117,9 @@
/>
</view>
<view class="figma-send-btn" wx:if="{{inputText.length > 0}}" bindtap="onSend">
<view class="figma-send-btn {{inputText.length > 0 ? 'active' : ''}}" bindtap="onSend">
<image src="/images/icon-send.png" class="figma-btn-icon" mode="aspectFit"></image>
<text class="send-text">发送</text>
</view>
</view>
</view>

View File

@ -279,9 +279,9 @@
flex: 1;
background: #F9FAFB;
border: 2rpx solid #F3F4F6;
border-radius: 32rpx;
border-radius: 40rpx;
padding: 0 32rpx;
height: 96rpx;
height: 120rpx;
display: flex;
align-items: center;
}
@ -289,22 +289,43 @@
.figma-text-input {
width: 100%;
height: 100%;
font-size: 34rpx;
font-size: 38rpx;
color: #101828;
}
.figma-send-btn {
width: 88rpx;
width: 180rpx;
height: 88rpx;
background: #914584;
border-radius: 50%;
background: #F3F4F6;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
flex-shrink: 0;
transition: all 0.3s ease;
}
.figma-send-btn.active {
background: #914584;
}
.figma-btn-icon {
width: 44rpx;
height: 44rpx;
width: 36rpx;
height: 36rpx;
filter: grayscale(1) opacity(0.5);
}
.figma-send-btn.active .figma-btn-icon {
filter: brightness(0) invert(1);
}
.send-text {
font-size: 28rpx;
font-weight: 700;
color: #9CA3AF;
}
.figma-send-btn.active .send-text {
color: #FFFFFF;
}

View File

@ -37,16 +37,52 @@ Page({
try {
const statsRes = await request({ url: '/api/commission?action=stats', method: 'GET' });
const statsBody = statsRes.data || {};
let currentRoleText = '守护会员'; // 默认
// 尝试从本地存储获取当前用户信息来判断角色
try {
const userStr = wx.getStorageSync('user');
if (userStr) {
const user = JSON.parse(userStr);
// 优先使用 distributorRole其次 userRole再次 vip_level
const role = user.distributorRole || user.userRole;
const roleMap = {
'soulmate': '心伴会员',
'guardian': '守护会员',
'companion': '陪伴会员',
'listener': '倾听会员',
'partner': '城市合伙人'
};
if (role && roleMap[role]) {
currentRoleText = roleMap[role];
} else if (user.isDistributor) {
// 如果是分销商但没有明确role可能是早期数据根据cardType判断
// 但这里更直接用API返回的
}
}
} catch (e) {
console.error('解析用户信息失败', e);
}
if (statsBody.success) {
const d = statsBody.data || {};
// 如果API返回了明确的等级名称或类型优先使用API的
if (d.cardType || d.level) {
currentRoleText = this.getCardTitle(d.cardType || d.level);
}
this.setData({
stats: {
todayReferrals: Number(d.todayReferrals || d.today_referrals || 0),
totalReferrals: Number(d.totalReferrals || d.total_referrals || 0),
totalContribution: Number(d.totalContribution || d.total_contribution || 0).toFixed(2)
},
cardTitle: this.getCardTitle(d.cardType || d.level || 'guardian_card')
cardTitle: currentRoleText
});
} else {
// API不成功使用本地推断的角色
this.setData({ cardTitle: currentRoleText });
}
const res = await request({ url: '/api/commission?action=referrals&page=1&pageSize=50', method: 'GET' });
@ -137,11 +173,17 @@ Page({
const map = {
'guardian_card': '守护会员',
'companion_card': '陪伴会员',
'soulmate_card': '心伴会员',
'listener_card': '倾听会员',
'guardian': '守护会员',
'companion': '陪伴会员',
'soulmate': '心伴会员',
'listener': '倾听会员',
'identity_card': '身份会员',
'vip': 'VIP会员',
'partner': '城市合伙人'
};
return map[type] || '守护会员';
return map[type] || type || '守护会员';
}
});

View File

@ -453,20 +453,25 @@ page {
/* 空状态 */
.empty-state {
padding: 120rpx 32rpx;
text-align: center;
padding: 160rpx 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-icon {
width: 200rpx;
height: 200rpx;
margin: 0 auto 32rpx;
opacity: 0.5;
width: 320rpx;
height: 320rpx;
margin-bottom: 32rpx;
opacity: 0.8;
}
.empty-text {
font-size: 28rpx;
color: #9575CD;
font-size: 30rpx;
color: #94A3B8;
font-weight: 500;
letter-spacing: 2rpx;
}
/* 二维码弹窗 */

View File

@ -53,6 +53,17 @@
<button class="btn-reset submit-btn" bindtap="submit" disabled="{{submitting}}">
{{submitting ? '处理中...' : '立即提现'}}
</button>
<!-- 提现说明 -->
<view class="instruction-section">
<view class="instruction-title">提现说明:</view>
<view class="instruction-list">
<view class="instruction-item">1. 单笔提现10-2000元支持分多笔申请</view>
<view class="instruction-item">2. 提现扣除一定手续费,实际到账金额实时展示;</view>
<view class="instruction-item">3. T+1工作日到账不含节假日/周末),收款账户需实名一致;</view>
<view class="instruction-item">4. 禁止违规获利,否则平台有权拒绝提现。</view>
</view>
</view>
</view>
<!-- Records Section -->

View File

@ -184,6 +184,39 @@
box-shadow: none;
}
/* 提现说明 */
.instruction-section {
margin-top: 64rpx;
padding: 40rpx;
background: #F9FAFB;
border-radius: 32rpx;
border: 2rpx dashed #E5E7EB;
margin-bottom: 40rpx;
}
.instruction-title {
font-size: 32rpx;
font-weight: 800;
color: #374151;
margin-bottom: 24rpx;
display: block;
}
.instruction-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.instruction-item {
font-size: 28rpx;
color: #4B5563;
line-height: 1.7;
position: relative;
padding-left: 4rpx;
}
/* Records Section */
.records-section {
margin-top: 48rpx;

View File

@ -1,6 +1,6 @@
{
"libVersion": "3.13.1",
"projectname": "%E5%BF%83%E4%BC%B4",
"projectname": "ai-c",
"condition": {},
"setting": {
"urlCheck": false,