Compare commits

..

2 Commits

Author SHA1 Message Date
zhiyun
7b6bb822fa chore: normalize eol and cleanup 2026-02-04 19:10:34 +08:00
zhiyun
1ae572a083 新版本01 2026-02-04 18:12:02 +08:00
28 changed files with 938 additions and 375 deletions

23
.gitattributes vendored Normal file
View File

@ -0,0 +1,23 @@
* text=auto
*.js text eol=lf
*.json text eol=lf
*.wxml text eol=lf
*.wxss text eol=lf
*.wxs text eol=lf
*.md text eol=lf
*.sh text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.xml text eol=lf
*.svg text eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.webp binary
*.ico binary

29
app.js
View File

@ -437,20 +437,37 @@ App({
return this.globalData.isLoggedIn
},
/**
* 加载审核状态
*/
async loadAuditStatus() {
const localAppVersion = config.APP_VERSION || ''
try {
const res = await api.common.getAuditStatus()
if (res.code === 0 && res.data) {
this.globalData.auditStatus = Number(res.data.auditStatus || 0)
console.log('获取审核状态成功:', this.globalData.auditStatus)
const serverAppVersion = res.data.app_version || res.data.appVersion || ''
if (localAppVersion !== serverAppVersion) {
this.globalData.auditStatus = 0
wx.setStorageSync('auditStatus', 0)
return
}
const auditStatus = Number(res.data.auditStatus || 0)
this.globalData.auditStatus = auditStatus
wx.setStorageSync('auditStatus', auditStatus)
return
}
} catch (err) {
console.error('获取审核状态失败', err)
// 失败时默认为 0正式环境或根据需要设为 1保守方案
}
const cachedStatus = wx.getStorageSync('auditStatus')
if (cachedStatus !== undefined) {
this.globalData.auditStatus = cachedStatus
return
}
this.globalData.auditStatus = 0
wx.setStorageSync('auditStatus', 0)
},
/**

View File

@ -32,10 +32,14 @@ const ENV = {
// development: 本地开发 staging: 测试环境 production: 正式环境
const CURRENT_ENV = 'production'
// 硬编码版本号 - 用于审核开关对比
const APP_VERSION = '1.0'
// 导出配置
const config = {
...ENV[CURRENT_ENV],
ENV: CURRENT_ENV,
APP_VERSION: APP_VERSION,
// 存储键名
STORAGE_KEYS: {

174
config/version.js Normal file
View File

@ -0,0 +1,174 @@
/**
* 小程序版本配置文件
* 用于控制不同版本显示不同内容
*
* 版本号格式主版本.次版本.修订版本
* 示例'1.0.0', '2.0.0'
*
* 使用方式
* - 修改 CURRENT_VERSION 来切换当前开发版本
* - pages 中使用 getVersion() 获取当前版本
* - 使用 getContent(version) 获取对应版本的内容配置
*/
const CURRENT_VERSION = '1.0.0'
const VERSION_CONFIG = {
'1.0.0': {
name: 'V1基础版',
description: '初始版本',
categoryList: [
{
id: 1,
name: '兴趣搭子',
icon: 'https://ai-c.maimanji.com/images/icon-interest.png',
url: '/pages/interest-partner/interest-partner'
},
{
id: 2,
name: '同城活动',
icon: 'https://ai-c.maimanji.com/images/icon-city.png',
url: '/pages/city-activities/city-activities'
},
{
id: 3,
name: '户外郊游',
icon: 'https://ai-c.maimanji.com/images/icon-outdoor.png',
url: '/pages/outdoor-activities/outdoor-activities'
},
{
id: 4,
name: '高端定制',
icon: 'https://ai-c.maimanji.com/images/icon-travel.png',
url: '/pages/theme-travel/theme-travel'
},
{
id: 5,
name: '快乐学堂',
icon: 'https://ai-c.maimanji.com/images/icon-checkin.png',
url: '/pages/happy-school/happy-school'
},
{
id: 6,
name: '单身聚会',
icon: 'https://ai-c.maimanji.com/images/icon-love.png',
url: '/pages/singles-party/singles-party'
}
],
features: {
showNewFeature: false,
showBetaFeature: false,
useNewUI: false,
useOldUI: true
}
},
'2.0.0': {
name: 'V2升级版',
description: '全新UI设计新增功能入口',
categoryList: [
{
id: 1,
name: '兴趣搭子',
icon: 'https://ai-c.maimanji.com/images/v2/icon-interest.png',
url: '/pages/interest-partner/interest-partner'
},
{
id: 2,
name: '同城活动',
icon: 'https://ai-c.maimanji.com/images/v2/icon-city.png',
url: '/pages/city-activities/city-activities'
},
{
id: 3,
name: '户外郊游',
icon: 'https://ai-c.maimanji.com/images/v2/icon-outdoor.png',
url: '/pages/outdoor-activities/outdoor-activities'
},
{
id: 4,
name: '主题旅行',
icon: 'https://ai-c.maimanji.com/images/v2/icon-travel.png',
url: '/pages/theme-travel/theme-travel'
},
{
id: 5,
name: '快乐学堂',
icon: 'https://ai-c.maimanji.com/images/v2/icon-checkin.png',
url: '/pages/happy-school/happy-school'
},
{
id: 6,
name: '爱心传递',
icon: 'https://ai-c.maimanji.com/images/v2/icon-love.png',
url: '/pages/love-transmission/love-transmission'
}
],
features: {
showNewFeature: true,
showBetaFeature: true,
useNewUI: true,
useOldUI: false
}
}
}
function getVersion() {
const debugVersion = wx.getStorageSync('debugVersion')
if (debugVersion && VERSION_CONFIG[debugVersion]) {
return debugVersion
}
return CURRENT_VERSION
}
function getVersionInfo(version = CURRENT_VERSION) {
return VERSION_CONFIG[version] || VERSION_CONFIG['1.0.0']
}
function getCategoryList(version = CURRENT_VERSION) {
const config = getVersionInfo(version)
return config ? config.categoryList : []
}
function getFeatureFlag(featureName, version = CURRENT_VERSION) {
const config = getVersionInfo(version)
return config ? config.features[featureName] : false
}
function compareVersion(v1, v2) {
const v1s = v1.split('.')
const v2s = v2.split('.')
const len = Math.max(v1s.length, v2s.length)
for (let i = 0; i < len; i++) {
const n1 = parseInt(v1s[i]) || 0
const n2 = parseInt(v2s[i]) || 0
if (n1 > n2) return 1
if (n1 < n2) return -1
}
return 0
}
function isVersionOrAbove(targetVersion, currentVersion = CURRENT_VERSION) {
return compareVersion(currentVersion, targetVersion) >= 0
}
function isVersionBelow(targetVersion, currentVersion = CURRENT_VERSION) {
return compareVersion(currentVersion, targetVersion) < 0
}
function getAllVersions() {
return Object.keys(VERSION_CONFIG)
}
module.exports = {
CURRENT_VERSION,
VERSION_CONFIG,
getVersion,
getVersionInfo,
getCategoryList,
getFeatureFlag,
compareVersion,
isVersionOrAbove,
isVersionBelow,
getAllVersions
}

View File

@ -2,6 +2,8 @@
* 协议页面
* 显示用户服务协议或隐私协议
*/
const config = require('../../config/index')
Page({
data: {
statusBarHeight: 20,
@ -25,7 +27,8 @@ Page({
wx.showLoading({ title: '加载中...' });
// 优先读取本地缓存
const cached = wx.getStorageSync(`agreement_${code}`);
const cacheKey = code === 'privacy_policy' ? 'privacy_policy' : `agreement_${code}`;
const cached = wx.getStorageSync(cacheKey);
const CACHE_DURATION = 60 * 60 * 1000; // 1小时
const now = Date.now();
@ -38,9 +41,12 @@ Page({
return;
}
// 从配置文件获取API域名
const baseUrl = String(config.API_BASE_URL || '').replace(/\/api$/, '')
// 网络请求
wx.request({
url: `https://ai-c.maimanji.com/api/agreements?code=${code}`,
url: `${baseUrl}/api/agreements?code=${code}`,
method: 'GET',
success: (res) => {
wx.hideLoading();
@ -52,7 +58,7 @@ Page({
});
// 写入缓存
wx.setStorageSync(`agreement_${code}`, {
wx.setStorageSync(cacheKey, {
data: data,
timestamp: now
});

View File

@ -22,7 +22,10 @@ Page({
data: {
statusBarHeight: 44,
navHeight: 96,
// 审核状态
auditStatus: 0,
// 角色信息
characterId: '',
conversationId: '',
@ -128,25 +131,28 @@ Page({
},
onLoad(options) {
// 从本地存储读取审核状态
const auditStatus = wx.getStorageSync('auditStatus') || 0
const { statusBarHeight, navHeight } = app.globalData
// 初始化消息处理相关变量
this.pendingMessages = []
this.messageTimer = null
this.isProcessing = false
// 获取参数
const characterId = options.id || ''
const conversationId = options.conversationId || ''
const characterName = decodeURIComponent(options.name || '')
// 设置用户头像
const userInfo = app.globalData.userInfo
const myAvatar = imageUrl.getAvatarUrl(userInfo?.avatar)
this.setData({
statusBarHeight,
navHeight,
auditStatus,
characterId,
conversationId,
myAvatar,
@ -163,6 +169,10 @@ Page({
},
onShow() {
// 从本地存储读取审核状态
const auditStatus = wx.getStorageSync('auditStatus') || 0
this.setData({ auditStatus })
// 每次显示页面时,刷新一次配额状态,确保免费畅聊时间等状态是最新的
if (!this.data.loading) {
this.loadQuotaStatus()
@ -1927,127 +1937,123 @@ Page({
onVoiceTouchEnd() {
clearInterval(this.recordingTimer)
const { voiceCancelHint, recordingDuration, characterId, character, isUnlocked, remainingCount } = this.data
this.setData({ isRecording: false })
if (this.recorderManager) {
this.recorderManager.stop()
if (voiceCancelHint) {
// 取消发送
util.showToast('已取消')
return
}
if (recordingDuration < 1) {
util.showError('录音时间太短')
return
}
this.recorderManager.onStop(async (res) => {
console.log('[chat-detail] 录音完成:', res.tempFilePath, '时长:', recordingDuration)
// 先显示语音消息(带识别中状态)
const newId = util.generateId()
const voiceMessage = {
id: newId,
type: 'voice',
audioUrl: res.tempFilePath,
duration: recordingDuration,
isMe: true,
time: util.formatTime(new Date(), 'HH:mm'),
recognizing: true, // 识别中状态
recognizedText: '' // 识别出的文字
}
this.setData({
messages: [...this.data.messages, voiceMessage]
}, () => {
this.scrollToBottom()
})
// 进行语音识别
try {
wx.showLoading({ title: '语音识别中...' })
// 读取音频文件并转换为base64
const fs = wx.getFileSystemManager()
const audioData = fs.readFileSync(res.tempFilePath)
const audioBase64 = wx.arrayBufferToBase64(audioData)
// 调用语音识别API
const recognizeRes = await api.speech.recognize({
audio: audioBase64,
format: 'mp3'
})
wx.hideLoading()
let recognizedText = ''
if (recognizeRes.success && recognizeRes.data && recognizeRes.data.text) {
recognizedText = recognizeRes.data.text
console.log('[chat-detail] 语音识别结果:', recognizedText)
} else {
// 识别失败,使用默认文字
recognizedText = '[语音消息]'
console.log('[chat-detail] 语音识别失败,使用默认文字')
}
// 更新语音消息的识别状态
const messages = this.data.messages.map(msg => {
if (msg.id === newId) {
return { ...msg, recognizing: false, recognizedText }
}
return msg
})
this.setData({ messages })
// 如果识别出了有效文字发送给AI
if (recognizedText && recognizedText !== '[语音消息]') {
// 检查聊天权限
const canChatByFreeTime = !!(this.data.freeTime && this.data.freeTime.isActive)
const canChatByVip = !!this.data.isVip
if (!isUnlocked && !canChatByVip && !canChatByFreeTime) {
console.log('[chat-detail] 语音消息无聊天权限', { isUnlocked, isVip, canChatByFreeTime })
this.setData({ showUnlockPopup: true })
return
}
// 将识别出的文字加入待处理队列
this.pendingMessages.push(recognizedText)
// 如果没有正在等待的定时器,启动延迟处理
if (!this.messageTimer) {
this.startMessageTimer(characterId, this.data.conversationId, character, isUnlocked, remainingCount)
}
}
} catch (err) {
wx.hideLoading()
console.error('[chat-detail] 语音识别失败:', err)
// 更新消息状态
const messages = this.data.messages.map(msg => {
if (msg.id === newId) {
return { ...msg, recognizing: false, recognizedText: '[语音消息]' }
}
return msg
})
this.setData({ messages })
util.showError('语音识别失败')
}
})
}
// 取消发送
if (voiceCancelHint) {
util.showToast('已取消')
return
}
// 录音时间太短
if (recordingDuration < 1) {
util.showError('录音时间太短')
return
}
// 等待录音停止后再处理
this.recorderManager.onStop(async (res) => {
console.log('[chat-detail] 录音完成:', res.tempFilePath, '时长:', recordingDuration)
// 先显示语音消息(带识别中状态)
const newId = util.generateId()
const voiceMessage = {
id: newId,
type: 'voice',
audioUrl: res.tempFilePath,
duration: recordingDuration,
isMe: true,
time: util.formatTime(newId, 'HH:mm'),
recognizing: true,
recognizedText: ''
}
this.setData({
messages: [...this.data.messages, voiceMessage]
}, () => {
this.scrollToBottom()
})
// 进行语音识别
try {
wx.showLoading({ title: '语音识别中...' })
const fs = wx.getFileSystemManager()
const audioData = fs.readFileSync(res.tempFilePath)
const audioBase64 = wx.arrayBufferToBase64(audioData)
console.log('[chat-detail] 音频文件大小:', audioData.byteLength, 'bytes')
console.log('[chat-detail] Base64长度:', audioBase64.length, 'chars')
console.log('[chat-detail] 开始调用语音识别API...')
const recognizeRes = await api.speech.recognize({
audio: audioBase64,
format: 'mp3'
})
console.log('[chat-detail] 语音识别响应:', JSON.stringify(recognizeRes).substring(0, 200))
wx.hideLoading()
let recognizedText = ''
if (recognizeRes.success && recognizeRes.data && recognizeRes.data.text) {
recognizedText = recognizeRes.data.text
console.log('[chat-detail] 语音识别结果:', recognizedText)
} else {
recognizedText = '[语音消息]'
console.log('[chat-detail] 语音识别失败,使用默认文字')
}
const messages = this.data.messages.map(msg => {
if (msg.id === newId) {
return { ...msg, recognizing: false, recognizedText }
}
return msg
})
this.setData({ messages })
if (recognizedText && recognizedText !== '[语音消息]') {
const canChatByFreeTime = !!(this.data.freeTime && this.data.freeTime.isActive)
const canChatByVip = !!this.data.isVip
if (!isUnlocked && !canChatByVip && !canChatByFreeTime) {
console.log('[chat-detail] 语音消息无聊天权限', { isUnlocked, isVip, canChatByFreeTime })
this.setData({ showUnlockPopup: true })
return
}
this.pendingMessages.push(recognizedText)
if (!this.messageTimer) {
this.startMessageTimer(characterId, this.data.conversationId, character, isUnlocked, remainingCount)
}
}
} catch (err) {
wx.hideLoading()
console.error('[chat-detail] 语音识别失败:', err)
const messages = this.data.messages.map(msg => {
if (msg.id === newId) {
return { ...msg, recognizing: false, recognizedText: '[语音消息]' }
}
return msg
})
this.setData({ messages })
util.showError('语音识别失败')
}
})
},
onVoiceTouchCancel() {
clearInterval(this.recordingTimer)
this.setData({ isRecording: false })
if (this.recorderManager) {
this.recorderManager.stop()
}
@ -2058,7 +2064,7 @@ Page({
*/
onMessageLongPress(e) {
const item = e.currentTarget.dataset.item
wx.showActionSheet({
itemList: ['复制', '删除'],
success: (res) => {

View File

@ -57,12 +57,12 @@
</view>
<!-- 加密对话提示 -->
<view class="encrypt-hint">
<view class="encrypt-hint" wx:if="{{auditStatus === 0}}">
<text>与 {{character.name}} 的加密对话</text>
</view>
<!-- 聊天消息列表 -->
<view class="chat-list">
<view class="chat-list" wx:if="{{auditStatus === 0}}">
<view
class="chat-item {{item.isMe ? 'me' : 'other'}}"
wx:for="{{messages}}"
@ -150,7 +150,7 @@
</view>
<!-- 正在输入提示 -->
<view class="chat-item other" wx:if="{{isTyping}}">
<view class="chat-item other" wx:if="{{isTyping && auditStatus === 0}}">
<view class="avatar-wrap">
<image wx:if="{{character.avatar}}" class="chat-avatar" src="{{character.avatar}}" mode="aspectFill"></image>
<view wx:else class="avatar-placeholder">
@ -177,8 +177,14 @@
<!-- 面板打开时的透明遮罩层 - 点击关闭面板 -->
<view class="panel-overlay" wx:if="{{showEmoji || showMorePanel}}" bindtap="onClosePanels"></view>
<!-- 底部输入区域 - Figma设计样式 -->
<view class="bottom-input-area {{showEmoji || showMorePanel ? 'panel-open' : ''}}">
<!-- 审核模式提示 -->
<view class="audit-notice" wx:if="{{auditStatus === 1}}">
<image src="/images/icon-bell.png" class="notice-icon" mode="aspectFit"></image>
<text class="notice-text">当前处于审核模式</text>
</view>
<!-- 底部输入区域 - Figma设计样式 - 审核时隐藏 -->
<view class="bottom-input-area {{showEmoji || showMorePanel ? 'panel-open' : ''}}" wx:if="{{auditStatus === 0}}">
<view class="input-container figma-input-container">
<!-- 语音/键盘切换按钮 - Figma样式 -->
<view class="figma-voice-btn" bindtap="onVoiceMode">

View File

@ -1657,3 +1657,34 @@
color: #99A1AF;
letter-spacing: 0.5rpx;
}
/* ==================== 审核模式样式 ==================== */
/* 审核提示 */
.audit-notice {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 24rpx;
padding: 48rpx;
background: rgba(255, 255, 255, 0.95);
border-radius: 24rpx;
box-shadow: 0 20rpx 40rpx rgba(0, 0, 0, 0.1);
z-index: 100;
}
.notice-icon {
width: 80rpx;
height: 80rpx;
opacity: 0.6;
}
.notice-text {
font-size: 32rpx;
color: #6B7280;
font-weight: 500;
}

View File

@ -37,31 +37,34 @@ Page({
// 总未读消息数
totalUnread: 0,
// 加载状态
loading: true,
error: null,
auditStatus: 0,
// 免费畅聊相关
freeTime: null,
countdownText: ''
},
onLoad() {
// 从本地存储读取审核状态
const auditStatus = wx.getStorageSync('auditStatus') || 0
this.setData({ auditStatus })
this.loadConversations()
},
onShow() {
wx.hideTabBar({ animation: false })
const app = getApp()
this.setData({
auditStatus: app.globalData.auditStatus
})
// 从本地存储读取审核状态
const auditStatus = wx.getStorageSync('auditStatus') || 0
this.setData({ auditStatus })
// 检查免费畅聊时间
this.checkFreeTime()
// 每次显示时刷新列表
// 增加延迟确保标记已读API有时间完成从聊天详情页返回时
if (!this.data.loading) {

View File

@ -34,8 +34,8 @@
</view>
</view>
<!-- AI会话列表支持滑动删除 -->
<view class="swipe-container" wx:for="{{conversations}}" wx:key="id">
<!-- AI会话列表支持滑动删除,审核时隐藏 -->
<view class="swipe-container" wx:for="{{conversations}}" wx:key="id" wx:if="{{auditStatus === 0}}">
<view
class="swipe-content {{item.swiped ? 'swiped' : ''}}"
bindtouchstart="onTouchStart"

View File

@ -2,6 +2,8 @@
// 根据Figma设计实现
const api = require('../../utils/api')
const config = require('../../config/index')
const versionConfig = require('../../config/version')
const app = getApp()
Page({
@ -17,39 +19,8 @@ Page({
swiperHeight: 400,
currentBannerIndex: 0,
// 功能入口 - 使用正式环境图片URL
categoryList: [
{
id: 1,
name: '兴趣搭子',
icon: '/images/icon-interest.png'
},
{
id: 2,
name: '同城活动',
icon: '/images/icon-city.png'
},
{
id: 3,
name: '户外郊游',
icon: '/images/icon-outdoor.png'
},
{
id: 4,
name: '高端定制',
icon: '/images/icon-travel.png'
},
{
id: 5,
name: '快乐学堂',
icon: '/images/icon-checkin.png'
},
{
id: 6,
name: '单身聚会',
icon: '/images/icon-love.png'
}
],
// 功能入口 - 使用版本化配置
categoryList: versionConfig.getCategoryList(),
// 滚动公告
noticeList: [],
@ -112,7 +83,7 @@ Page({
* 处理图片URL如果是相对路径则拼接域名并设置清晰度为85
*/
processImageUrl(url) {
if (!url) return ''
if (!url || typeof url !== 'string') return ''
let fullUrl = url
if (!url.startsWith('http://') && !url.startsWith('https://')) {
const baseUrl = 'https://ai-c.maimanji.com'
@ -545,58 +516,17 @@ Page({
* 分类点击
*/
onCategoryTap(e) {
const { id, name } = e.currentTarget.dataset
// 兴趣搭子跳转到专门页面
if (id === 1) {
wx.navigateTo({
url: '/pages/interest-partner/interest-partner'
})
return
const { id, name, url } = e.currentTarget.dataset
// 从版本配置中获取分类信息
const categoryList = versionConfig.getCategoryList()
const category = categoryList.find(item => item.id === id)
if (category && category.url) {
wx.navigateTo({ url: category.url })
} else {
wx.showToast({ title: `${name}功能开发中`, icon: 'none' })
}
// 同城活动跳转到专门页面
if (id === 2) {
wx.navigateTo({
url: '/pages/city-activities/city-activities'
})
return
}
// 户外郊游跳转到专门页面
if (id === 3) {
wx.navigateTo({
url: '/pages/outdoor-activities/outdoor-activities'
})
return
}
// 定制主题跳转到专门页面
if (id === 4) {
wx.navigateTo({
url: '/pages/theme-travel/theme-travel'
})
return
}
// 快乐学堂跳转到专门页面
if (id === 5) {
wx.navigateTo({
url: '/pages/happy-school/happy-school'
})
return
}
// 单身聚会跳转到专门页面
if (id === 6) {
wx.navigateTo({
url: '/pages/singles-party/singles-party'
})
return
}
wx.showToast({ title: `${name}功能开发中`, icon: 'none' })
// TODO: 跳转到对应分类页面
},
/**

View File

@ -37,11 +37,12 @@
<!-- 功能入口 - 与服务页面统一的圆形图标风格 -->
<view class="category-section">
<view class="category-item"
wx:for="{{categoryList}}"
<view class="category-item"
wx:for="{{categoryList}}"
wx:key="id"
data-id="{{item.id}}"
data-name="{{item.name}}"
data-url="{{item.url}}"
bindtap="onCategoryTap">
<!-- 图标容器:圆形图片 -->
<view class="category-icon-container">

View File

@ -1,5 +1,4 @@
const api = require('../../utils/api')
const auth = require('../../utils/auth')
const config = require('../../config/index')
Page({
@ -8,6 +7,7 @@ Page({
navBarHeight: 44,
totalNavHeight: 88,
lovePoints: 0,
isLoggedIn: false,
gifts: [],
giftsLoading: false,
giftsError: '',
@ -32,28 +32,31 @@ Page({
const menuButton = wx.getMenuButtonBoundingClientRect()
const navBarHeight = menuButton.height + (menuButton.top - statusBarHeight) * 2
const totalNavHeight = statusBarHeight + navBarHeight
this.setData({
statusBarHeight,
navBarHeight,
totalNavHeight
})
// 礼物商城无需登录即可访问
this.loadGifts()
const isValid = await auth.ensureLogin({
pageName: 'gift-shop',
redirectUrl: '/pages/gift-shop/gift-shop'
})
// 检查登录状态并加载爱心值
const app = getApp()
const isLoggedIn = app.globalData.isLoggedIn
this.setData({ isLoggedIn })
if (!isValid) return
await new Promise(resolve => setTimeout(resolve, 50))
this.loadLovePoints()
if (isLoggedIn) {
this.loadLovePoints()
}
},
onShow() {
const app = getApp()
this.setData({ isLoggedIn: app.globalData.isLoggedIn })
// 仅在登录状态下刷新爱心值
if (app.globalData.isLoggedIn) {
this.loadLovePoints()
}
@ -121,12 +124,21 @@ Page({
},
// 点击礼品卡片
onGiftTap(e) {
onGiftTap() {
wx.showModal({
title: '提示',
content: '请下载app端进行兑换',
showCancel: false,
confirmText: '知道了'
title: '联系客服',
content: '请添加客服mmj20259999',
confirmText: '复制',
success: (res) => {
if (res.confirm) {
wx.setClipboardData({
data: 'mmj20259999',
success: () => {
wx.showToast({ title: '已复制', icon: 'success' })
}
})
}
}
})
},

View File

@ -13,7 +13,8 @@
<!-- 内容区域 -->
<scroll-view scroll-y class="content-scroll" style="padding-top: {{totalNavHeight}}px;" enhanced show-scrollbar="{{false}}">
<view class="content-wrap">
<view class="love-card">
<!-- 爱心值卡片 - 仅登录后显示 -->
<view class="love-card" wx:if="{{isLoggedIn}}">
<view class="love-card-bg"></view>
<view class="love-card-blur"></view>
<view class="love-card-bottom-shade"></view>

View File

@ -85,31 +85,21 @@ Page({
},
async onLoad() {
// 获取系统信息
const { statusBarHeight, navHeight, auditStatus } = app.globalData
this.setData({ statusBarHeight, navHeight, auditStatus })
// 如果是审核状态,重定向到文娱页面
if (auditStatus === 1) {
const localAuditStatus = wx.getStorageSync('auditStatus') || 0
const { statusBarHeight, navHeight } = app.globalData
this.setData({ statusBarHeight, navHeight, auditStatus: localAuditStatus })
if (localAuditStatus === 1 || app.globalData.auditStatus === 1) {
wx.switchTab({
url: '/pages/entertainment/entertainment'
})
return
}
// 加载首页素材 (Banner等)
this.loadHomeAssets()
// 加载分享配置
this.loadShareConfig()
// 加载角色列表
this.loadCharacters()
// 加载用户爱心余额
this.loadHeartBalance()
// 加载解锁配置
this.loadUnlockConfig()
},
@ -208,13 +198,15 @@ Page({
// 隐藏默认tabbar
wx.hideTabBar({ animation: false })
// 从本地存储读取审核状态
const localAuditStatus = wx.getStorageSync('auditStatus') || 0
const app = getApp()
this.setData({
auditStatus: app.globalData.auditStatus
auditStatus: localAuditStatus
})
// 如果是审核状态,重定向到文娱页面
if (app.globalData.auditStatus === 1) {
if (localAuditStatus === 1 || app.globalData.auditStatus === 1) {
wx.switchTab({
url: '/pages/entertainment/entertainment'
})
@ -230,13 +222,30 @@ Page({
// 检查AI角色主动推送消息
this.checkProactiveMessages()
// 检查注册奖励
this.checkRegistrationReward()
// 检查 GF100 弹窗
this.checkGf100Popup()
},
onHide() {
this.stopAudio()
},
onUnload() {
this.stopAudio()
},
stopAudio() {
if (this.audioContext) {
try {
this.audioContext.stop()
this.audioContext.destroy()
} catch (e) {}
this.audioContext = null
}
wx.hideToast()
},
onPullDownRefresh() {
Promise.all([
this.loadCharacters(),
@ -534,16 +543,8 @@ Page({
const nextIndex = currentIndex + 1
// 切换卡片时,如果正在播放语音,则停止播放
if (isVoicePlaying && this.audioContext) {
try {
console.log('[index] 切换卡片,停止上一个角色的语音')
this.audioContext.stop()
} catch (e) {
console.error('[index] 停止语音失败:', e)
}
}
if (isVoicePlaying) this.stopAudio()
// 如果快到末尾,加载更多
if (nextIndex >= profiles.length - 2) {
this.loadMoreCharacters()
}
@ -552,7 +553,8 @@ Page({
currentIndex: nextIndex,
offsetX: 0,
rotation: 0,
swipeDirection: ''
swipeDirection: '',
isVoicePlaying: false
})
},

View File

@ -1,5 +1,5 @@
<!-- 首页 - 遇见页面 -->
<view class="page-container">
<view class="page-container" wx:if="{{auditStatus === 0}}">
<!-- 顶部导航栏 -->
<view class="unified-header">
<view class="unified-header-left"></view>

View File

@ -16,7 +16,7 @@
<view class="tab-item {{currentTab === 'completed' ? 'active' : ''}}" bindtap="switchTab" data-tab="completed">已完成</view>
</view>
<view class="wrap" style="padding-top: {{totalNavHeight + 80}}px; padding-bottom: 40rpx;">
<view class="wrap" style="padding-top: {{totalNavHeight + 60}}px; padding-bottom: 40rpx;">
<view class="card">
<view class="list">
<view wx:if="{{loading}}" class="loading">加载中...</view>

View File

@ -56,7 +56,7 @@
<view class="user-info">
<view class="user-main">
<text class="user-name">{{item.userName}}</text>
<text class="user-level" wx:if="{{item.level !== '普通用户'}}">{{item.level}}</text>
<text class="user-level">{{item.level}}</text>
</view>
<view class="user-sub">加入时间: {{item.joinedAt}}</view>
</view>

View File

@ -63,15 +63,14 @@
}
.stats-value {
font-size: 60rpx;
font-size: 48rpx;
font-weight: 800;
margin-bottom: 12rpx;
margin-bottom: 8rpx;
}
.stats-label {
font-size: 30rpx;
opacity: 0.95;
font-weight: 500;
font-size: 24rpx;
opacity: 0.9;
}
/* Amount Card */
@ -158,116 +157,55 @@
}
.purple-dot {
width: 10rpx;
height: 38rpx;
width: 8rpx;
height: 32rpx;
background: #B06AB3;
border-radius: 6rpx;
border-radius: 4rpx;
}
.panel-title {
font-size: 38rpx;
font-size: 32rpx;
font-weight: 800;
color: #111827;
}
.referral-count {
font-size: 38rpx;
font-size: 32rpx;
font-weight: 800;
color: #B06AB3;
}
.see-all {
font-size: 32rpx;
color: #B06AB3;
font-weight: 700;
.user-grid {
display: flex;
flex-wrap: wrap;
gap: 32rpx; /* Space between items */
}
.user-list {
.user-item {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.user-row {
display: flex;
align-items: center;
padding: 32rpx 0;
border-bottom: 2rpx solid #F3F4F6;
}
.user-row:last-child {
border-bottom: none;
width: 120rpx;
}
.user-avatar {
width: 110rpx;
height: 110rpx;
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: #F3F4F6;
margin-right: 24rpx;
flex-shrink: 0;
}
.user-info {
flex: 1;
min-width: 0;
}
.user-main {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 8rpx;
margin-bottom: 12rpx;
}
.user-name {
font-size: 36rpx;
font-weight: 700;
color: #1F2937;
max-width: 240rpx;
font-size: 24rpx;
color: #4B5563;
width: 100%;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.user-level {
font-size: 24rpx;
color: #B06AB3;
background: rgba(176, 106, 179, 0.1);
padding: 4rpx 16rpx;
border-radius: 20rpx;
font-weight: 600;
}
.user-sub {
font-size: 28rpx;
color: #4B5563;
}
.user-stats {
display: flex;
gap: 24rpx;
text-align: right;
}
.stat-item {
display: flex;
flex-direction: column;
min-width: 100rpx;
}
.stat-label {
font-size: 24rpx;
color: #9CA3AF;
margin-bottom: 6rpx;
}
.stat-value {
font-size: 30rpx;
font-weight: 700;
color: #1F2937;
}
.empty-state {
width: 100%;
text-align: center;
@ -286,7 +224,7 @@
.menu-item {
background: #FFFFFF;
border-radius: 24rpx;
padding: 40rpx 32rpx;
padding: 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
@ -296,12 +234,12 @@
.menu-left {
display: flex;
align-items: center;
gap: 32rpx;
gap: 24rpx;
}
.icon-circle {
width: 88rpx;
height: 88rpx;
width: 72rpx;
height: 72rpx;
border-radius: 50%;
display: flex;
align-items: center;
@ -313,15 +251,15 @@
.icon-circle.orange { background: rgba(245, 158, 11, 0.1); }
.menu-text {
font-size: 34rpx;
font-weight: 700;
font-size: 30rpx;
font-weight: 600;
color: #1F2937;
}
.btn-reset {
background: none;
border: none;
padding: 40rpx 32rpx; /* Match menu-item padding */
padding: 32rpx; /* Match menu-item padding */
margin: 0;
line-height: normal;
border-radius: 24rpx; /* Match menu-item border-radius */

View File

@ -61,11 +61,11 @@
<view class="list-container" wx:if="{{list.length > 0}}">
<view class="list-item" wx:for="{{list}}" wx:key="userId" bindtap="viewOrders" data-item="{{item}}">
<view class="user-info">
<image src="{{item.userAvatar || defaultAvatar}}" class="user-avatar" mode="aspectFill"></image>
<image src="{{item.userAvatar}}" class="user-avatar" mode="aspectFill"></image>
<view class="user-details">
<view class="user-name-row">
<text class="user-name">{{item.userName}}</text>
<text class="user-level" wx:if="{{item.levelText !== '普通用户'}}">{{item.levelText}}</text>
<text class="user-level">{{item.levelText}}</text>
</view>
<view class="user-time">绑定时间: {{formatTime(item.boundAt)}}</view>
<view class="user-meta">

View File

@ -137,7 +137,7 @@
}
.user-name {
font-size: 36rpx;
font-size: 32rpx;
font-weight: 600;
color: #1F2937;
}
@ -194,13 +194,13 @@
}
.user-time {
font-size: 28rpx;
font-size: 24rpx;
color: #9CA3AF;
}
/* 列表项详情扩展 */
.user-meta {
font-size: 26rpx;
font-size: 22rpx;
color: #6B7280;
margin-top: 8rpx;
display: flex;
@ -208,10 +208,10 @@
}
.user-level {
font-size: 24rpx;
font-size: 20rpx;
color: #B06AB3;
background: rgba(176, 106, 179, 0.1);
padding: 4rpx 16rpx;
padding: 2rpx 12rpx;
border-radius: 20rpx;
font-weight: 600;
}

View File

@ -35,7 +35,7 @@
<!-- 商家列表 -->
<view class="provider-list">
<view class="provider-card" wx:for="{{listData}}" wx:key="id" data-id="{{item.id}}" bindtap="onItemTap">
<view class="provider-card" wx:for="{{listData}}" wx:key="id">
<view class="provider-left">
<view class="avatar-container">
<view class="avatar-wrapper">

View File

@ -0,0 +1,108 @@
const versionUtils = require('../../utils/version')
const versionConfig = require('../../config/version')
Page({
data: {
currentVersion: '',
versionName: '',
versionList: [],
categoryList: [],
isV2OrAbove: false,
useNewUI: false
},
onLoad() {
this.loadVersionInfo()
},
onShow() {
this.loadVersionInfo()
},
loadVersionInfo() {
const currentVersion = versionUtils.getCurrentVersion()
const versionInfo = versionUtils.getVersionInfo()
const allVersions = versionConfig.getAllVersions()
const categoryList = versionUtils.getCategoryList()
const isV2OrAbove = versionUtils.isV2OrAbove()
const useNewUI = versionUtils.getFeatureFlag('useNewUI')
const versionList = allVersions.map(v => {
const info = versionConfig.getVersionInfo(v)
return {
version: v,
name: info ? info.name : v,
description: info ? info.description : ''
}
})
this.setData({
currentVersion,
versionName: versionInfo ? versionInfo.name : '',
versionList,
categoryList,
isV2OrAbove,
useNewUI
})
versionUtils.logVersionInfo()
},
switchVersion(e) {
const { version } = e.currentTarget.dataset
if (version === this.data.currentVersion) {
wx.showToast({ title: '已是该版本', icon: 'none' })
return
}
wx.showModal({
title: '切换版本',
content: `确定要切换到 ${version} 版本吗?切换后页面将重新加载。`,
confirmColor: '#b06ab3',
success: (res) => {
if (res.confirm) {
this.doSwitchVersion(version)
}
}
})
},
doSwitchVersion(version) {
wx.showLoading({ title: '切换中...' })
try {
const allVersions = versionConfig.getAllVersions()
if (allVersions.includes(version)) {
wx.setStorageSync('debugVersion', version)
console.log(`[VersionDebug] 切换到版本: ${version}`)
setTimeout(() => {
wx.hideLoading()
wx.showToast({ title: '切换成功', icon: 'success' })
setTimeout(() => {
wx.reLaunch({
url: '/pages/version-debug/version-debug'
})
}, 1000)
}, 500)
} else {
wx.hideLoading()
wx.showToast({ title: '版本不存在', icon: 'none' })
}
} catch (err) {
wx.hideLoading()
console.error('切换版本失败', err)
wx.showToast({ title: '切换失败', icon: 'none' })
}
},
onShareAppMessage() {
return {
title: '版本调试工具',
path: '/pages/version-debug/version-debug'
}
}
})

View File

@ -0,0 +1,4 @@
{
"usingComponents": {},
"navigationBarTitleText": "版本调试"
}

View File

@ -0,0 +1,55 @@
<view class="debug-container">
<view class="debug-header">版本调试工具</view>
<view class="version-info">
<text class="label">当前版本:</text>
<text class="value">{{currentVersion}}</text>
</view>
<view class="version-info">
<text class="label">版本名称:</text>
<text class="value">{{versionName}}</text>
</view>
<view class="section-title">切换版本</view>
<view class="version-list">
<view
class="version-item {{item.version === currentVersion ? 'active' : ''}}"
wx:for="{{versionList}}"
wx:key="version"
data-version="{{item.version}}"
bindtap="switchVersion">
<view class="version-name">{{item.name}}</view>
<view class="version-desc">{{item.description}}</view>
<view class="version-current" wx:if="{{item.version === currentVersion}}">当前版本</view>
</view>
</view>
<view class="section-title">版本特性对比</view>
<view class="feature-comparison">
<view class="feature-row">
<text class="feature-name">新功能入口</text>
<text class="feature-value {{isV2OrAbove ? 'enabled' : 'disabled'}}">
{{isV2OrAbove ? '显示' : '隐藏'}}
</text>
</view>
<view class="feature-row">
<text class="feature-name">新UI设计</text>
<text class="feature-value {{useNewUI ? 'enabled' : 'disabled'}}">
{{useNewUI ? '启用' : '停用'}}
</text>
</view>
</view>
<view class="section-title">分类图标预览</view>
<view class="icon-preview">
<view class="icon-item" wx:for="{{categoryList}}" wx:key="id">
<image class="icon-image" src="{{item.icon}}" mode="aspectFit"></image>
<text class="icon-name">{{item.name}}</text>
</view>
</view>
<view class="tips">
<text>提示:切换版本后会重新加载页面</text>
</view>
</view>

View File

@ -0,0 +1,181 @@
.debug-container {
min-height: 100vh;
background: #f5f5f5;
padding: 30rpx;
}
.debug-header {
font-size: 40rpx;
font-weight: 700;
color: #1D293D;
text-align: center;
padding: 30rpx 0;
margin-bottom: 30rpx;
}
.version-info {
display: flex;
justify-content: space-between;
align-items: center;
background: #fff;
padding: 24rpx 30rpx;
border-radius: 16rpx;
margin-bottom: 20rpx;
}
.version-info .label {
font-size: 28rpx;
color: #62748E;
}
.version-info .value {
font-size: 28rpx;
font-weight: 600;
color: #b06ab3;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #1D293D;
margin: 40rpx 0 20rpx;
padding-left: 10rpx;
border-left: 8rpx solid #b06ab3;
}
.version-list {
background: #fff;
border-radius: 16rpx;
overflow: hidden;
}
.version-item {
display: flex;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
position: relative;
transition: background 0.2s;
}
.version-item:last-child {
border-bottom: none;
}
.version-item.active {
background: #fdf2f8;
}
.version-item.active::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 8rpx;
background: #b06ab3;
}
.version-name {
font-size: 30rpx;
font-weight: 600;
color: #1D293D;
margin-right: 20rpx;
}
.version-desc {
font-size: 24rpx;
color: #62748E;
flex: 1;
}
.version-current {
font-size: 22rpx;
color: #b06ab3;
background: #fdf2f8;
padding: 8rpx 16rpx;
border-radius: 20rpx;
}
.feature-comparison {
background: #fff;
border-radius: 16rpx;
overflow: hidden;
}
.feature-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.feature-row:last-child {
border-bottom: none;
}
.feature-name {
font-size: 28rpx;
color: #1D293D;
}
.feature-value {
font-size: 26rpx;
font-weight: 600;
padding: 8rpx 20rpx;
border-radius: 20rpx;
}
.feature-value.enabled {
color: #10b981;
background: #ecfdf5;
}
.feature-value.disabled {
color: #6b7280;
background: #f3f4f6;
}
.icon-preview {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
background: #fff;
border-radius: 16rpx;
padding: 20rpx;
}
.icon-item {
display: flex;
flex-direction: column;
align-items: center;
width: 180rpx;
padding: 20rpx;
background: #fafafa;
border-radius: 12rpx;
}
.icon-image {
width: 128rpx;
height: 128rpx;
border-radius: 50%;
margin-bottom: 16rpx;
}
.icon-name {
font-size: 24rpx;
color: #1D293D;
text-align: center;
}
.tips {
text-align: center;
margin-top: 40rpx;
padding: 20rpx;
}
.tips text {
font-size: 24rpx;
color: #62748E;
}

View File

@ -1389,6 +1389,12 @@ const common = {
*/
getAuditStatus: () => request('/common/audit-status'),
/**
* 获取系统配置
* 返回 app_version 等配置信息用于版本号对比
*/
getSystemConfig: () => request('/admin/system-config'),
/**
* 获取品牌配置
* 返回品牌故事介绍等内容

55
utils/version.js Normal file
View File

@ -0,0 +1,55 @@
/**
* 版本控制工具类
* 用于在小程序中获取和管理版本相关信息
*/
const versionConfig = require('../config/version')
function getCurrentVersion() {
return versionConfig.getVersion()
}
function getVersionInfo() {
return versionConfig.getVersionInfo()
}
function isV2OrAbove() {
return versionConfig.isVersionOrAbove('2.0.0')
}
function isV1() {
const v = getCurrentVersion()
return v.startsWith('1.')
}
function getCategoryList() {
return versionConfig.getCategoryList()
}
function getFeatureFlag(featureName) {
return versionConfig.getFeatureFlag(featureName)
}
function logVersionInfo() {
const version = getCurrentVersion()
const info = getVersionInfo()
const features = info ? info.features : {}
console.log('========== 小程序版本信息 ==========')
console.log('当前版本:', version)
console.log('版本名称:', info ? info.name : '未知')
console.log('版本描述:', info ? info.description : '未知')
console.log('分类数量:', getCategoryList().length)
console.log('功能开关:', features)
console.log('====================================')
}
module.exports = {
getCurrentVersion,
getVersionInfo,
isV2OrAbove,
isV1,
getCategoryList,
getFeatureFlag,
logVersionInfo
}