wangfukang 5 days ago
parent
commit
6f73f40e78
  1. 412
      package1/ieBrowser/chat.vue
  2. 389
      package1/ieBrowser/dailyQuestion.vue
  3. 2704
      package1/ieBrowser/index.vue
  4. 12
      package1/ieBrowser/match.vue
  5. 56
      package1/ieBrowser/messages.vue
  6. 55
      package1/ieBrowser/universe.vue
  7. 2
      package1/planet/index.vue

412
package1/ieBrowser/chat.vue

@ -12,6 +12,9 @@
<view class="room-name">{{ companion.name }}</view> <view class="room-name">{{ companion.name }}</view>
<view class="room-meta">{{ roomModeText }} · 可以随时回来继续聊</view> <view class="room-meta">{{ roomModeText }} · 可以随时回来继续聊</view>
</view> </view>
<view class="streak-pill" v-if="streak.streakDays > 0" :class="{ expiring: streak.expiring }" @tap="showStreakTip">
🔥x{{ streak.streakDays }}
</view>
<view class="more" @tap="showSafety = true">···</view> <view class="more" @tap="showSafety = true">···</view>
</view> </view>
@ -32,8 +35,8 @@
<view class="timer-card"> <view class="timer-card">
<view> <view>
<view class="timer-label">长期陪伴频道</view> <view class="timer-label">请勿发送涉黄涉暴反动侮辱等内容</view>
<view class="timer-title">{{ companion.prompt }}</view> <view class="timer-title">发送图片音视频消息涉及内容审核可能略有延迟</view>
</view> </view>
<view class="timer-pill">已连接</view> <view class="timer-pill">已连接</view>
</view> </view>
@ -74,11 +77,13 @@
<text>{{ item.mediaDuration || 1 }}''</text> <text>{{ item.mediaDuration || 1 }}''</text>
</view> </view>
<view class="bubble" v-else :class="{ muted: item.pending, blocked: item.blocked }">{{ item.content }}</view> <view class="bubble" v-else :class="{ muted: item.pending, blocked: item.blocked }">{{ item.content }}</view>
<view class="audit-tip blocked-tip" v-if="item.mine && item.blocked">内容未通过审核请换一个更合适的内容</view>
</view> </view>
<view class="hesitate-line right" v-if="typingHint">{{ typingHint }}</view> <view class="hesitate-line right" v-if="typingHint">{{ typingHint }}</view>
</scroll-view> </scroll-view>
<view class="presence-row" :style="{ bottom: presenceBottom }"> <view class="presence-row" :style="{ bottom: presenceBottom }">
<view class="presence-chip quiz-chip" @tap="openQuiz">🎯 默契测试</view>
<view class="presence-chip" v-for="item in presenceActions" :key="item" @tap="sendPresence(item)"> <view class="presence-chip" v-for="item in presenceActions" :key="item" @tap="sendPresence(item)">
{{ item }} {{ item }}
</view> </view>
@ -126,6 +131,42 @@
</view> </view>
</view> </view>
<view class="safety-mask" v-if="showQuiz" @tap="closeQuizPanel">
<view class="quiz-panel" @tap.stop>
<view class="quiz-title">🎯 默契小测试</view>
<view class="quiz-sub" v-if="!quiz.exists">正在出题...</view>
<template v-else>
<view class="quiz-result" v-if="quiz.bothDone">
<view class="quiz-rate">{{ quiz.matchRate }}%</view>
<view class="quiz-rate-label">{{ quizRateText }}</view>
</view>
<view class="quiz-sub" v-else-if="quiz.myDone">已提交 TA 答完就揭晓 </view>
<view class="quiz-sub" v-else>5 道二选一凭直觉选看看你们多同频</view>
<scroll-view scroll-y class="quiz-scroll">
<view class="quiz-q" v-for="(q, qi) in quiz.questions" :key="qi">
<view class="quiz-q-text">{{ qi + 1 }}. {{ q.text }}</view>
<view class="quiz-options">
<view class="quiz-option"
:class="{ picked: !quiz.bothDone && quizPicks[qi] === 'A', revealed: quiz.bothDone, same: quiz.bothDone && q.same && q.mine === 'A' }"
@tap="pickQuizOption(qi, 'A')">
{{ q.optionA }}
<text class="quiz-marks" v-if="quiz.bothDone">{{ q.mine === 'A' ? ' ' : '' }}{{ q.other === 'A' ? ' TA' : '' }}</text>
</view>
<view class="quiz-option"
:class="{ picked: !quiz.bothDone && quizPicks[qi] === 'B', revealed: quiz.bothDone, same: quiz.bothDone && q.same && q.mine === 'B' }"
@tap="pickQuizOption(qi, 'B')">
{{ q.optionB }}
<text class="quiz-marks" v-if="quiz.bothDone">{{ q.mine === 'B' ? ' ' : '' }}{{ q.other === 'B' ? ' TA' : '' }}</text>
</view>
</view>
</view>
</scroll-view>
<view class="quiz-submit" v-if="!quiz.myDone" :class="{ disabled: !quizComplete }" @tap="submitQuiz">提交答案</view>
<view class="quiz-submit ghost" v-else @tap="closeQuizPanel">{{ quiz.bothDone ? '完成' : '先去聊天揭晓了叫我' }}</view>
</template>
</view>
</view>
<view class="profile-mask" v-if="showTargetProfile" @tap="showTargetProfile = false"> <view class="profile-mask" v-if="showTargetProfile" @tap="showTargetProfile = false">
<view class="profile-panel" @tap.stop> <view class="profile-panel" @tap.stop>
<image class="profile-avatar-img" v-if="targetProfile.avatarUrl" :src="targetProfile.avatarUrl" mode="aspectFill"></image> <image class="profile-avatar-img" v-if="targetProfile.avatarUrl" :src="targetProfile.avatarUrl" mode="aspectFill"></image>
@ -160,7 +201,7 @@
<script> <script>
import ieSocket from '@/common/ieSocket.js' import ieSocket from '@/common/ieSocket.js'
import { finishIeRoom, reportIeRoom, blockIeRoomTarget, unblockIeRoomTarget, getIeRoomBlockStatus, pageIeMessages, sendIeMessage, getIeRoomTargetProfile } from '@/common/ieApi.js' import { finishIeRoom, reportIeRoom, blockIeRoomTarget, unblockIeRoomTarget, getIeRoomBlockStatus, pageIeMessages, sendIeMessage, getIeRoomTargetProfile, getIeRoomStreak, startIeQuiz, getIeQuizCurrent, answerIeQuiz } from '@/common/ieApi.js'
import tui from '@/common/httpRequest.js' import tui from '@/common/httpRequest.js'
export default { export default {
@ -199,6 +240,12 @@
voiceSwitching: false, voiceSwitching: false,
keyboardHeight: 0, keyboardHeight: 0,
keyboardListener: null, keyboardListener: null,
streak: { streakDays: 0, bothToday: false, expiring: false },
lastStreakLoad: 0,
showQuiz: false,
quiz: { exists: false, questions: [] },
quizPicks: ['', '', '', '', ''],
quizPolling: null,
messages: [], messages: [],
messagePage: 1, messagePage: 1,
messagePageSize: 20, messagePageSize: 20,
@ -271,6 +318,18 @@
return { return {
paddingBottom: this.keyboardHeight > 0 ? (this.keyboardHeight + 140) + 'px' : '24rpx' paddingBottom: this.keyboardHeight > 0 ? (this.keyboardHeight + 140) + 'px' : '24rpx'
} }
},
quizComplete() {
return this.quizPicks.every(pick => pick === 'A' || pick === 'B')
},
quizRateText() {
const rate = this.quiz.matchRate
if (rate === null || rate === undefined) return ''
if (rate >= 100) return '灵魂同频!这默契没谁了'
if (rate >= 80) return '默契惊人,你们很合拍'
if (rate >= 60) return '相当同频,可以处'
if (rate >= 40) return '互补型搭子,刚刚好'
return '来自两个星球,但这才有趣'
} }
}, },
onLoad(options) { onLoad(options) {
@ -300,11 +359,29 @@
this.initSocket() this.initSocket()
this.startMessagePolling() this.startMessagePolling()
this.bindKeyboardListener() this.bindKeyboardListener()
this.loadStreak()
},
onShow() {
if (this.roomId) {
this.startMessagePolling()
this.pullNewMessages()
}
},
onHide() {
// markRead
this.stopMessagePolling()
this.stopQuizPolling()
}, },
onUnload() { onUnload() {
this.clearTimer() this.clearTimer()
this.stopMessagePolling() this.stopMessagePolling()
this.stopQuizPolling()
this.unbindKeyboardListener() this.unbindKeyboardListener()
if (this.roomId) {
ieSocket.unsubscribeRoom(this.roomId)
}
ieSocket.resetHandlers()
ieSocket.close()
if (this.innerAudioContext) { if (this.innerAudioContext) {
this.innerAudioContext.destroy() this.innerAudioContext.destroy()
this.innerAudioContext = null this.innerAudioContext = null
@ -376,7 +453,8 @@
poster: item.poster || item.thumbTempFilePath || '', poster: item.poster || item.thumbTempFilePath || '',
mine, mine,
pending: false, pending: false,
blocked: item.isBlocked === 1 blocked: item.isBlocked === 1,
mediaAuditStatus: item.mediaAuditStatus || 0
} }
}, },
startMessagePolling() { startMessagePolling() {
@ -392,6 +470,88 @@
this.pollTimer = null this.pollTimer = null
} }
}, },
loadStreak(throttled) {
if (!this.roomId) return
const now = Date.now()
if (throttled && now - this.lastStreakLoad < 30000) return
this.lastStreakLoad = now
getIeRoomStreak(this.roomId).then(res => {
if (res) this.streak = res
}).catch(() => {})
},
showStreakTip() {
const tip = this.streak.expiring
? `火花 x${this.streak.streakDays},今天还没续上,互发一句就不会熄灭`
: `连续 ${this.streak.streakDays} 天互相陪伴,每满 ${this.streak.rewardInterval || 7} 天双方各得一次匹配机会`
uni.showToast({ title: tip, icon: 'none', duration: 2600 })
},
openQuiz() {
this.showQuiz = true
const handle = (res) => {
if (!res) return
this.quiz = res
this.quizPicks = ['', '', '', '', '']
if (res.myDone && !res.bothDone) {
this.startQuizPolling()
}
}
if (this.quiz.exists && !this.quiz.bothDone) {
getIeQuizCurrent(this.roomId).then(handle).catch(() => {})
return
}
startIeQuiz(this.roomId).then(handle).catch(() => {})
},
closeQuizPanel() {
this.showQuiz = false
// toast
if (!this.quiz.myDone || this.quiz.bothDone) {
this.stopQuizPolling()
}
},
pickQuizOption(index, option) {
if (this.quiz.myDone) return
this.$set(this.quizPicks, index, option)
},
submitQuiz() {
if (!this.quizComplete) {
uni.showToast({ title: '还有题没选哦', icon: 'none' })
return
}
answerIeQuiz(this.roomId, this.quizPicks.join('')).then(res => {
if (!res) return
this.quiz = res
if (!res.bothDone) {
this.startQuizPolling()
}
}).catch(() => {})
},
startQuizPolling() {
this.stopQuizPolling()
let ticks = 0
this.quizPolling = setInterval(() => {
if (++ticks > 100) {
this.stopQuizPolling()
return
}
getIeQuizCurrent(this.roomId).then(res => {
if (res && res.exists) {
this.quiz = res
if (res.bothDone) {
this.stopQuizPolling()
if (!this.showQuiz) {
uni.showToast({ title: `默契测试揭晓:${res.matchRate}%`, icon: 'none', duration: 2600 })
}
}
}
}).catch(() => {})
}, 3000)
},
stopQuizPolling() {
if (this.quizPolling) {
clearInterval(this.quizPolling)
this.quizPolling = null
}
},
async pullNewMessages() { async pullNewMessages() {
if (!this.roomId || this.loadingMessages) return if (!this.roomId || this.loadingMessages) return
const page = await pageIeMessages(this.roomId, 1, this.messagePageSize) const page = await pageIeMessages(this.roomId, 1, this.messagePageSize)
@ -399,10 +559,26 @@
if (!latest.length) return if (!latest.length) return
const exists = new Set(this.messages.map(item => item.messageId || item.clientMsgId)) const exists = new Set(this.messages.map(item => item.messageId || item.clientMsgId))
const appended = latest.filter(item => !exists.has(item.messageId || item.clientMsgId)) const appended = latest.filter(item => !exists.has(item.messageId || item.clientMsgId))
this.mergeMessageStatus(latest)
if (!appended.length) return if (!appended.length) return
this.messages = this.messages.concat(appended) this.messages = this.messages.concat(appended)
this.scrollToBottom() this.scrollToBottom()
}, },
mergeMessageStatus(latest) {
latest.forEach(remote => {
const local = this.messages.find(item => (remote.messageId && String(item.messageId || '') === String(remote.messageId)) ||
(remote.clientMsgId && item.clientMsgId === remote.clientMsgId))
if (!local) return
const wasBlocked = !!local.blocked
local.pending = false
local.blocked = !!remote.blocked
local.mediaAuditStatus = remote.mediaAuditStatus || local.mediaAuditStatus || 0
local.messageId = remote.messageId || local.messageId
if (!wasBlocked && local.blocked && local.mine) {
uni.showToast({ title: '内容未通过审核,请换一个更合适的内容', icon: 'none' })
}
})
},
normalizePage(page) { normalizePage(page) {
if (!page) return [] if (!page) return []
const records = Array.isArray(page) ? page : (page.records || []) const records = Array.isArray(page) ? page : (page.records || [])
@ -504,7 +680,9 @@
uni.showToast({ title: '先等等对方回复吧,首次破冰最多发送 3 条', icon: 'none' }) uni.showToast({ title: '先等等对方回复吧,首次破冰最多发送 3 条', icon: 'none' })
return return
} }
const clientMsgId = 'm-' + Date.now() // clientMsgId ACK
this.msgSeq = (this.msgSeq || 0) + 1
const clientMsgId = 'm-' + Date.now() + '-' + this.msgSeq
this.messages.push({ this.messages.push({
domId: 'msg-' + clientMsgId, domId: 'msg-' + clientMsgId,
clientMsgId, clientMsgId,
@ -515,7 +693,8 @@
mediaFormat: media.mediaFormat, mediaFormat: media.mediaFormat,
poster: media.poster || '', poster: media.poster || '',
mine: true, mine: true,
pending: true pending: !this.isAuditMediaMessage(messageType),
mediaAuditStatus: media.mediaAuditStatus || 0
}) })
this.scrollToBottom() this.scrollToBottom()
this.persistMessage({ this.persistMessage({
@ -525,7 +704,9 @@
content, content,
mediaDuration: media.mediaDuration, mediaDuration: media.mediaDuration,
mediaSize: media.mediaSize, mediaSize: media.mediaSize,
mediaFormat: media.mediaFormat mediaFormat: media.mediaFormat,
mediaCheckUrl: media.mediaCheckUrl || '',
mediaCheckType: media.mediaCheckType
}) })
}, },
needWaitReply() { needWaitReply() {
@ -533,6 +714,9 @@
if (delivered.some(item => !item.mine)) return false if (delivered.some(item => !item.mine)) return false
return delivered.filter(item => item.mine).length >= 3 return delivered.filter(item => item.mine).length >= 3
}, },
isAuditMediaMessage(messageType) {
return messageType === 2 || messageType === 4 || messageType === 5
},
async persistMessage(payload) { async persistMessage(payload) {
try { try {
const ack = await sendIeMessage(this.roomId, payload) const ack = await sendIeMessage(this.roomId, payload)
@ -558,16 +742,27 @@
} }
}, },
applyAck(ack) { applyAck(ack) {
const msg = this.messages.find(item => item.clientMsgId === ack.clientMsgId) const msg = this.messages.find(item => (ack.clientMsgId && item.clientMsgId === ack.clientMsgId) ||
(ack.messageId && String(item.messageId || '') === String(ack.messageId)))
if (!msg) return if (!msg) return
const wasBlocked = !!msg.blocked
msg.pending = false msg.pending = false
msg.messageId = ack.messageId msg.messageId = ack.messageId
msg.messageType = ack.messageType || msg.messageType || 1 msg.messageType = ack.messageType || msg.messageType || 1
msg.mediaDuration = ack.mediaDuration || msg.mediaDuration msg.mediaDuration = ack.mediaDuration || msg.mediaDuration
msg.mediaSize = ack.mediaSize || msg.mediaSize msg.mediaSize = ack.mediaSize || msg.mediaSize
msg.mediaFormat = ack.mediaFormat || msg.mediaFormat msg.mediaFormat = ack.mediaFormat || msg.mediaFormat
msg.mediaAuditStatus = ack.mediaAuditStatus || msg.mediaAuditStatus || 0
msg.blocked = ack.isBlocked === 1 msg.blocked = ack.isBlocked === 1
msg.content = msg.blocked ? '这句话没有送出,请换一种更温柔的说法。' : (ack.content || msg.content) if (msg.blocked) {
msg.content = msg.messageType === 1 ? '这句话没有送出,请换一种更温柔的说法。' : msg.content
if (!wasBlocked) {
uni.showToast({ title: '内容未通过审核,请换一个更合适的内容', icon: 'none' })
}
} else {
msg.content = ack.content || msg.content
this.loadStreak(true)
}
}, },
chooseMedia() { chooseMedia() {
if (this.isBlocked) { if (this.isBlocked) {
@ -619,7 +814,10 @@
this.showEmoji = false this.showEmoji = false
this.sendChatContent(imageUrl, 2, { this.sendChatContent(imageUrl, 2, {
mediaSize: fileSize, mediaSize: fileSize,
mediaFormat: 'image' mediaFormat: 'image',
mediaCheckUrl: imageUrl,
mediaCheckType: 2,
mediaAuditStatus: 1
}) })
} finally { } finally {
this.uploadingImage = false this.uploadingImage = false
@ -634,12 +832,24 @@
uni.showToast({ title: '视频上传失败', icon: 'none' }) uni.showToast({ title: '视频上传失败', icon: 'none' })
return return
} }
let posterUrl = ''
if (file.thumbTempFilePath) {
try {
const posterResult = await tui.uploadFile('/upload/file', file.thumbTempFilePath)
posterUrl = typeof posterResult === 'string' ? posterResult : (posterResult.url || posterResult.fileUrl || posterResult.path || posterResult.fullPath || posterResult)
} catch (e) {
posterUrl = ''
}
}
this.showEmoji = false this.showEmoji = false
this.sendChatContent(videoUrl, 5, { this.sendChatContent(videoUrl, 5, {
mediaDuration: file.duration ? Math.round(file.duration) : undefined, mediaDuration: file.duration ? Math.round(file.duration) : undefined,
mediaSize: file.size, mediaSize: file.size,
mediaFormat: 'video', mediaFormat: 'video',
poster: file.thumbTempFilePath || '' poster: posterUrl || file.thumbTempFilePath || '',
mediaCheckUrl: posterUrl,
mediaCheckType: posterUrl ? 2 : undefined,
mediaAuditStatus: 1
}) })
} finally { } finally {
this.uploadingImage = false this.uploadingImage = false
@ -743,7 +953,10 @@
this.sendChatContent(voiceUrl, 4, { this.sendChatContent(voiceUrl, 4, {
mediaDuration: Math.min(duration, 60), mediaDuration: Math.min(duration, 60),
mediaSize: fileSize, mediaSize: fileSize,
mediaFormat: 'mp3' mediaFormat: 'mp3',
mediaCheckUrl: voiceUrl,
mediaCheckType: 1,
mediaAuditStatus: 1
}) })
} finally { } finally {
this.uploadingVoice = false this.uploadingVoice = false
@ -932,6 +1145,7 @@
mediaDuration: msg.mediaDuration, mediaDuration: msg.mediaDuration,
mediaSize: msg.mediaSize, mediaSize: msg.mediaSize,
mediaFormat: msg.mediaFormat, mediaFormat: msg.mediaFormat,
mediaAuditStatus: msg.mediaAuditStatus || 0,
content: msg.content, content: msg.content,
mine: String(msg.senderId || '') === String(uni.getStorageSync('id') || '') mine: String(msg.senderId || '') === String(uni.getStorageSync('id') || '')
}) })
@ -1354,6 +1568,28 @@
.bubble.muted { .bubble.muted {
background: rgba(223, 254, 244, 0.88); background: rgba(223, 254, 244, 0.88);
opacity: .62;
}
.bubble.blocked {
opacity: .5;
filter: grayscale(1);
}
.audit-tip {
align-self: flex-end;
margin: 0 12rpx 0 0;
padding: 6rpx 14rpx;
border-radius: 999rpx;
color: rgba(22, 27, 46, .42);
background: rgba(255, 255, 255, .58);
font-size: 19rpx;
line-height: 28rpx;
}
.blocked-tip {
color: #e85d75;
background: rgba(255, 228, 236, .82);
} }
.image-bubble { .image-bubble {
@ -1490,6 +1726,158 @@
font-size: 23rpx; font-size: 23rpx;
} }
.quiz-chip {
color: #7a4dff;
font-weight: 800;
background: linear-gradient(135deg, rgba(167, 139, 250, 0.22), rgba(255, 184, 209, 0.26));
border: 1rpx solid rgba(122, 77, 255, 0.22);
}
.streak-pill {
flex-shrink: 0;
margin-right: 8rpx;
padding: 6rpx 16rpx;
border-radius: 999rpx;
font-size: 23rpx;
font-weight: 800;
color: #ff6a3c;
background: rgba(255, 138, 84, 0.14);
border: 1rpx solid rgba(255, 138, 84, 0.3);
}
.streak-pill.expiring {
animation: streakBlink 1.2s ease-in-out infinite;
}
@keyframes streakBlink {
0%, 100% { opacity: 1; }
50% { opacity: 0.45; }
}
.quiz-panel {
width: 100%;
max-height: 76vh;
padding: 32rpx 30rpx 30rpx;
border-radius: 38rpx;
background: linear-gradient(180deg, #ffffff 0%, #f6f8ff 100%);
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.quiz-title {
font-size: 32rpx;
font-weight: 800;
text-align: center;
}
.quiz-sub {
margin-top: 10rpx;
text-align: center;
font-size: 24rpx;
color: rgba(22, 27, 46, 0.55);
}
.quiz-result {
margin-top: 12rpx;
text-align: center;
}
.quiz-rate {
font-size: 72rpx;
font-weight: 900;
background: linear-gradient(135deg, #7a4dff, #ff7eb3);
-webkit-background-clip: text;
color: transparent;
}
.quiz-rate-label {
margin-top: 4rpx;
font-size: 26rpx;
font-weight: 700;
color: rgba(22, 27, 46, 0.72);
}
.quiz-scroll {
margin-top: 20rpx;
max-height: 46vh;
}
.quiz-q {
margin-bottom: 24rpx;
}
.quiz-q-text {
font-size: 27rpx;
font-weight: 700;
margin-bottom: 12rpx;
}
.quiz-options {
display: flex;
gap: 14rpx;
}
.quiz-option {
flex: 1;
padding: 18rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
text-align: center;
color: rgba(22, 27, 46, 0.66);
background: rgba(255, 255, 255, 0.9);
border: 2rpx solid rgba(122, 77, 255, 0.12);
}
.quiz-option.picked {
color: #7a4dff;
font-weight: 800;
background: rgba(122, 77, 255, 0.1);
border-color: rgba(122, 77, 255, 0.5);
}
.quiz-option.revealed {
border-color: rgba(22, 27, 46, 0.08);
}
.quiz-option.same {
color: #ff6a3c;
font-weight: 800;
background: rgba(255, 138, 84, 0.12);
border-color: rgba(255, 138, 84, 0.45);
}
.quiz-marks {
margin-left: 8rpx;
font-size: 20rpx;
color: #7a4dff;
font-weight: 800;
}
.quiz-submit {
margin-top: 18rpx;
height: 86rpx;
line-height: 86rpx;
text-align: center;
border-radius: 999rpx;
font-size: 29rpx;
font-weight: 800;
color: #fff;
background: linear-gradient(135deg, #7a4dff, #ff7eb3);
box-shadow: 0 16rpx 34rpx rgba(122, 77, 255, 0.3);
}
.quiz-submit.disabled {
opacity: 0.5;
box-shadow: none;
}
.quiz-submit.ghost {
color: rgba(22, 27, 46, 0.62);
background: rgba(22, 27, 46, 0.06);
box-shadow: none;
}
.input-bar { .input-bar {
position: fixed; position: fixed;
left: 24rpx; left: 24rpx;

389
package1/ieBrowser/dailyQuestion.vue

@ -0,0 +1,389 @@
<template>
<view class="daily-page">
<view class="top-safe" :style="{ height: menuButtonInfo.top + 'px' }"></view>
<view class="nav">
<view class="back" @tap="back"></view>
<view class="title">每日同频一题</view>
<view class="ghost"></view>
</view>
<view class="question-card" v-if="question.exists">
<view class="q-deco"> 今日全校同题 </view>
<view class="q-text">{{ question.content }}</view>
<view class="q-count">已有 {{ question.answerCount }} 人回答</view>
</view>
<view class="question-card" v-else>
<view class="q-text">{{ loading ? '正在取今天的题目...' : '今天还没有题目,明天再来吧' }}</view>
</view>
<view class="answer-box" v-if="question.exists && !question.answered">
<textarea class="answer-input" v-model="draft" maxlength="200"
placeholder="写下你的回答,答完就能看到大家的答案(200字以内)"></textarea>
<view class="answer-submit" :class="{ disabled: !draft.trim() || submitting }" @tap="submit">
提交回答解锁大家的答案
</view>
</view>
<view class="my-answer" v-if="question.exists && question.answered">
<view class="my-answer-label">我的回答</view>
<view class="my-answer-text">{{ question.myAnswer }}</view>
</view>
<view class="feed" v-if="question.answered">
<view class="feed-title">大家的回答</view>
<view class="answer-card" v-for="item in answers" :key="item.id">
<view class="card-head">
<image class="card-avatar-img" v-if="item.avatarUrl" :src="item.avatarUrl" mode="aspectFill"></image>
<view class="card-avatar" v-else>{{ item.avatarText || '' }}</view>
<view class="card-user">
<view class="card-name">
{{ item.anonymousName || '半匿名漂流者' }}
<text class="school-badge" v-if="item.sameSchool">同校</text>
<text class="mine-badge" v-if="item.mine"></text>
</view>
<view class="card-mode">{{ item.currentMode === 'e' ? 'e 人 · 轻轻热闹' : 'i 人 · 安静陪伴' }}</view>
</view>
<view class="card-chat" v-if="!item.mine" @tap="chatWith(item)">和TA聊聊</view>
</view>
<view class="card-content">{{ item.content }}</view>
</view>
<view class="feed-status">{{ loadingAnswers ? '正在加载...' : (hasMore ? '上滑加载更多' : (answers.length ? '今天先到这里' : '还没有人回答,抢个沙发')) }}</view>
</view>
<view class="locked-tip" v-else-if="question.exists">
<view class="locked-icon">🔒</view>
<view class="locked-text">回答之后才能看到大家的答案</view>
</view>
</view>
</template>
<script>
import { getIeDailyQuestion, answerIeDailyQuestion, pageIeDailyAnswers, matchIeByAnswer } from '@/common/ieApi.js'
export default {
data() {
return {
menuButtonInfo: { top: 44 },
loading: true,
question: { exists: false },
draft: '',
submitting: false,
answers: [],
pageNumber: 1,
hasMore: true,
loadingAnswers: false,
matchingAnswerId: null
}
},
onLoad() {
if (uni.getMenuButtonBoundingClientRect) {
this.menuButtonInfo = uni.getMenuButtonBoundingClientRect()
}
this.loadQuestion()
},
onReachBottom() {
if (this.question.answered && this.hasMore && !this.loadingAnswers) {
this.loadAnswers()
}
},
methods: {
back() {
uni.navigateBack({ fail: () => uni.redirectTo({ url: '/package1/ieBrowser/index' }) })
},
async loadQuestion() {
this.loading = true
const res = await getIeDailyQuestion().catch(() => null)
this.loading = false
if (!res) return
this.question = res
if (res.answered) {
this.pageNumber = 1
this.answers = []
this.hasMore = true
this.loadAnswers()
}
},
async submit() {
const content = this.draft.trim()
if (!content || this.submitting) return
this.submitting = true
try {
const res = await answerIeDailyQuestion(content)
if (!res) return
this.draft = ''
uni.showToast({ title: '已提交,去看看大家的回答', icon: 'none' })
this.loadQuestion()
} finally {
this.submitting = false
}
},
async loadAnswers() {
if (this.loadingAnswers) return
this.loadingAnswers = true
try {
const res = await pageIeDailyAnswers(this.pageNumber, 10)
if (!res) return
const records = res.records || []
this.answers = this.pageNumber === 1 ? records : this.answers.concat(records)
this.hasMore = this.answers.length < (res.total || 0)
this.pageNumber += 1
} finally {
this.loadingAnswers = false
}
},
async chatWith(item) {
if (this.matchingAnswerId) return
this.matchingAnswerId = item.id
try {
const match = await matchIeByAnswer(item.id)
if (!match || !match.roomId) return
uni.navigateTo({
url: '/package1/ieBrowser/chat?mode=' + (match.mode || 'i') +
'&roomId=' + match.roomId +
'&targetUserId=' + match.targetUserId +
'&name=' + encodeURIComponent(match.anonymousName || item.anonymousName || '') +
'&avatar=' + encodeURIComponent(match.avatarText || item.avatarText || '') +
'&avatarUrl=' + encodeURIComponent(match.avatarUrl || item.avatarUrl || '') +
'&quote=' + encodeURIComponent(match.quoteText || '')
})
} finally {
this.matchingAnswerId = null
}
}
}
}
</script>
<style scoped>
.daily-page {
min-height: 100vh;
padding: 0 32rpx 60rpx;
box-sizing: border-box;
color: #161b2e;
background: radial-gradient(circle at 18% 10%, rgba(167, 139, 250, .26), transparent 320rpx),
radial-gradient(circle at 86% 24%, rgba(255, 184, 209, .26), transparent 320rpx),
linear-gradient(180deg, #fbfdff, #eef4ff 62%, #fff4e8);
}
.nav {
display: flex;
align-items: center;
height: 88rpx;
}
.back {
width: 62rpx;
font-size: 52rpx;
color: rgba(22, 27, 46, .62);
}
.title {
flex: 1;
text-align: center;
font-size: 31rpx;
font-weight: 800;
}
.ghost {
width: 62rpx;
}
.question-card {
margin-top: 18rpx;
padding: 36rpx 30rpx;
border-radius: 34rpx;
background: rgba(255, 255, 255, .8);
border: 1rpx solid rgba(255, 255, 255, .9);
box-shadow: 0 18rpx 44rpx rgba(96, 112, 160, .14);
text-align: center;
}
.q-deco {
font-size: 22rpx;
font-weight: 800;
color: #7a4dff;
letter-spacing: 4rpx;
}
.q-text {
margin-top: 16rpx;
font-size: 36rpx;
font-weight: 800;
line-height: 1.5;
}
.q-count {
margin-top: 14rpx;
font-size: 23rpx;
color: rgba(22, 27, 46, .5);
}
.answer-box {
margin-top: 24rpx;
}
.answer-input {
width: 100%;
height: 220rpx;
padding: 24rpx;
box-sizing: border-box;
border-radius: 28rpx;
background: rgba(255, 255, 255, .85);
border: 1rpx solid rgba(255, 255, 255, .9);
font-size: 26rpx;
}
.answer-submit {
margin-top: 20rpx;
height: 92rpx;
line-height: 92rpx;
text-align: center;
border-radius: 999rpx;
font-size: 28rpx;
font-weight: 800;
color: #fff;
background: linear-gradient(135deg, #7a4dff, #ff7eb3);
box-shadow: 0 16rpx 36rpx rgba(122, 77, 255, .3);
}
.answer-submit.disabled {
opacity: .5;
box-shadow: none;
}
.my-answer {
margin-top: 24rpx;
padding: 24rpx 26rpx;
border-radius: 26rpx;
background: rgba(122, 77, 255, .08);
border: 1rpx solid rgba(122, 77, 255, .16);
}
.my-answer-label {
font-size: 22rpx;
font-weight: 800;
color: #7a4dff;
}
.my-answer-text {
margin-top: 8rpx;
font-size: 26rpx;
line-height: 1.5;
}
.feed {
margin-top: 28rpx;
}
.feed-title {
font-size: 28rpx;
font-weight: 800;
margin-bottom: 16rpx;
}
.answer-card {
margin-bottom: 18rpx;
padding: 24rpx;
border-radius: 28rpx;
background: rgba(255, 255, 255, .82);
border: 1rpx solid rgba(255, 255, 255, .9);
box-shadow: 0 12rpx 30rpx rgba(96, 112, 160, .1);
}
.card-head {
display: flex;
align-items: center;
}
.card-avatar,
.card-avatar-img {
width: 76rpx;
height: 76rpx;
border-radius: 50%;
flex-shrink: 0;
}
.card-avatar {
line-height: 76rpx;
text-align: center;
font-size: 30rpx;
font-weight: 800;
color: #5a4632;
background: linear-gradient(135deg, #ffe9c9, #ffd6a5);
}
.card-user {
flex: 1;
margin-left: 16rpx;
min-width: 0;
}
.card-name {
font-size: 27rpx;
font-weight: 800;
}
.school-badge,
.mine-badge {
margin-left: 10rpx;
padding: 2rpx 12rpx;
border-radius: 999rpx;
font-size: 18rpx;
font-weight: 800;
vertical-align: 4rpx;
}
.school-badge {
color: #1d7a4f;
background: rgba(61, 220, 151, .16);
}
.mine-badge {
color: #7a4dff;
background: rgba(122, 77, 255, .12);
}
.card-mode {
margin-top: 4rpx;
font-size: 21rpx;
color: rgba(22, 27, 46, .48);
}
.card-chat {
flex-shrink: 0;
padding: 12rpx 24rpx;
border-radius: 999rpx;
font-size: 22rpx;
font-weight: 800;
color: #fff;
background: linear-gradient(135deg, #7a4dff, #ff7eb3);
}
.card-content {
margin-top: 16rpx;
font-size: 26rpx;
line-height: 1.55;
color: rgba(22, 27, 46, .82);
}
.feed-status {
padding: 24rpx 0 10rpx;
text-align: center;
font-size: 23rpx;
color: rgba(22, 27, 46, .42);
}
.locked-tip {
margin-top: 60rpx;
text-align: center;
}
.locked-icon {
font-size: 64rpx;
}
.locked-text {
margin-top: 14rpx;
font-size: 25rpx;
color: rgba(22, 27, 46, .5);
}
</style>

2704
package1/ieBrowser/index.vue

File diff suppressed because it is too large

12
package1/ieBrowser/match.vue

@ -44,8 +44,10 @@
targetMode: 'any', targetMode: 'any',
targetGender: 'any', targetGender: 'any',
matchScope: 'school', matchScope: 'school',
intent: '',
dailyQuota: 3, dailyQuota: 3,
chancesLeft: 3, chancesLeft: 3,
matching: false,
menuButtonInfo: { top: 44 } menuButtonInfo: { top: 44 }
} }
}, },
@ -63,6 +65,7 @@
this.targetMode = options.targetMode || 'any' this.targetMode = options.targetMode || 'any'
this.targetGender = options.targetGender || 'any' this.targetGender = options.targetGender || 'any'
this.matchScope = options.matchScope || 'school' this.matchScope = options.matchScope || 'school'
this.intent = options.intent || ''
this.loadQuota() this.loadQuota()
}, },
methods: { methods: {
@ -84,6 +87,7 @@
this.chancesLeft = Math.max(this.dailyQuota - (home.usedQuota || 0), 0) this.chancesLeft = Math.max(this.dailyQuota - (home.usedQuota || 0), 0)
}, },
async start() { async start() {
if (this.matching) return
if (this.chancesLeft <= 0) { if (this.chancesLeft <= 0) {
uni.showToast({ title: '今天先到这里', icon: 'none' }) uni.showToast({ title: '今天先到这里', icon: 'none' })
return return
@ -93,7 +97,13 @@
uni.showToast({ title: '请先选择校区', icon: 'none' }) uni.showToast({ title: '请先选择校区', icon: 'none' })
return return
} }
const match = await startIeMatch({ mode: this.mode, mood: this.mood, targetMode: this.targetMode, targetGender: this.targetGender, matchScope: this.matchScope, ...areaInfo }) this.matching = true
let match = null
try {
match = await startIeMatch({ mode: this.mode, mood: this.mood, targetMode: this.targetMode, targetGender: this.targetGender, matchScope: this.matchScope, intent: this.intent, ...areaInfo })
} finally {
this.matching = false
}
if (!match || !match.roomId) { if (!match || !match.roomId) {
uni.showToast({ title: (match && match.failReason) || '暂时没有同频的人', icon: 'none' }) uni.showToast({ title: (match && match.failReason) || '暂时没有同频的人', icon: 'none' })
return return

56
package1/ieBrowser/messages.vue

@ -1,10 +1,11 @@
<template> <template>
<view class="messages-page"> <view class="messages-page">
<view class="top-safe" :style="{ height: menuButtonInfo.top + 'px' }"></view> <view class="top-safe" :style="{ height: menuButtonInfo.top + 'px' }"></view>
<view class="nav"> <view class="float-bar">
<view class="back" @tap="back"></view> <view class="home-back" @tap="backHome">
<view class="nav-title">消息</view> <text class="home-back-icon"></text>
<view class="ghost"></view> <text>返回首页</text>
</view>
</view> </view>
<view class="head"> <view class="head">
<view class="title">消息</view> <view class="title">消息</view>
@ -22,6 +23,8 @@
<view class="empty" v-if="!loading && !requests.length">没有未完成的关系也是一种轻松</view> <view class="empty" v-if="!loading && !requests.length">没有未完成的关系也是一种轻松</view>
<view class="empty" v-else>{{ loading ? '正在加载...' : (hasMore ? '上滑加载更多' : '没有更多消息了') }}</view> <view class="empty" v-else>{{ loading ? '正在加载...' : (hasMore ? '上滑加载更多' : '没有更多消息了') }}</view>
<ie-bottom-tab active="messages" :unread-count="unreadCount"></ie-bottom-tab>
<view class="delete-mask" v-if="deleteDialogVisible" @tap="closeDeleteDialog"> <view class="delete-mask" v-if="deleteDialogVisible" @tap="closeDeleteDialog">
<view class="delete-dialog" @tap.stop> <view class="delete-dialog" @tap.stop>
<view class="delete-icon">!</view> <view class="delete-icon">!</view>
@ -40,12 +43,16 @@
</template> </template>
<script> <script>
import { pageIeRecords, deleteIeRecord } from '@/common/ieApi.js' import { getIeUnreadCount, pageIeRecords, deleteIeRecord } from '@/common/ieApi.js'
import IeBottomTab from '@/components/ie-bottom-tab/ie-bottom-tab.vue'
export default { export default {
components: { IeBottomTab },
data() { data() {
return { return {
menuButtonInfo: { top: 44 }, menuButtonInfo: { top: 44 },
unreadCount: 0,
unreadTimer: null,
requests: [], requests: [],
pageNumber: 1, pageNumber: 1,
pageSize: 10, pageSize: 10,
@ -56,14 +63,40 @@
deleting: false deleting: false
} }
}, },
onLoad() { onLoad(options = {}) {
if (uni.getMenuButtonBoundingClientRect) this.menuButtonInfo = uni.getMenuButtonBoundingClientRect() if (uni.getMenuButtonBoundingClientRect) this.menuButtonInfo = uni.getMenuButtonBoundingClientRect()
this.unreadCount = Number(options.unreadCount) || 0
this.loadRecords() this.loadRecords()
}, },
onReachBottom() { onReachBottom() {
this.loadRecords() this.loadRecords()
}, },
onShow() {
this.startUnreadTimer()
},
onHide() {
this.stopUnreadTimer()
},
onUnload() {
this.stopUnreadTimer()
},
methods: { methods: {
startUnreadTimer() {
if (this.unreadTimer) return
this.unreadTimer = setInterval(() => {
this.refreshUnreadCount()
}, 15000)
},
stopUnreadTimer() {
if (!this.unreadTimer) return
clearInterval(this.unreadTimer)
this.unreadTimer = null
},
async refreshUnreadCount() {
const count = await getIeUnreadCount()
if (count === null || count === undefined) return
this.unreadCount = Number(count) || 0
},
normalizeRecord(item) { normalizeRecord(item) {
const remeet = item.remeetAvailable === 1 const remeet = item.remeetAvailable === 1
return { return {
@ -92,7 +125,7 @@
this.loading = false this.loading = false
} }
}, },
back() { uni.navigateBack() }, backHome() { uni.redirectTo({ url: '/pages/index/index' }) },
confirmDelete(item) { confirmDelete(item) {
if (!item || !item.id) return if (!item || !item.id) return
this.deleteTarget = item this.deleteTarget = item
@ -135,10 +168,11 @@
<style lang="scss" scoped> <style lang="scss" scoped>
page { background: #f7f9ff; } page { background: #f7f9ff; }
.messages-page { min-height: 100vh; padding: 0 30rpx 60rpx; box-sizing: border-box; color: #151a2d; background: radial-gradient(circle at 18% 8%, rgba(169,255,231,.42), transparent 280rpx), linear-gradient(180deg, #fbfdff, #eef4ff); } .messages-page { min-height: 100vh; padding: 0 30rpx 230rpx; box-sizing: border-box; color: #151a2d; background: radial-gradient(circle at 18% 8%, rgba(169,255,231,.42), transparent 280rpx), linear-gradient(180deg, #fbfdff, #eef4ff); }
.nav { height: 90rpx; display: flex; align-items: center; } .float-bar { position: relative; z-index: 5; display: flex; align-items: center; justify-content: space-between; margin-top: 14rpx; }
.back, .ghost { width: 70rpx; font-size: 56rpx; color: rgba(21,26,45,.55); } .home-back { display: flex; align-items: center; height: 58rpx; padding: 0 22rpx 0 12rpx; border: 1rpx solid rgba(255,255,255,.88); border-radius: 999rpx; color: rgba(22,27,46,.66); background: rgba(255,255,255,.66); backdrop-filter: blur(20rpx); box-shadow: 0 14rpx 36rpx rgba(96,112,160,.12), inset 0 1rpx 0 rgba(255,255,255,.95); font-size: 23rpx; font-weight: 800; }
.nav-title { flex: 1; text-align: center; font-size: 30rpx; font-weight: 800; } .home-back:active { transform: scale(.95); background: rgba(169,255,231,.7); }
.home-back-icon { margin-right: 6rpx; font-size: 40rpx; line-height: 50rpx; font-weight: 400; }
.head { padding-top: 10rpx; } .head { padding-top: 10rpx; }
.title { font-size: 52rpx; font-weight: 800; } .title { font-size: 52rpx; font-weight: 800; }
.sub { margin-top: 12rpx; color: rgba(21,26,45,.5); font-size: 25rpx; } .sub { margin-top: 12rpx; color: rgba(21,26,45,.5); font-size: 25rpx; }

55
package1/ieBrowser/universe.vue

@ -1,10 +1,11 @@
<template> <template>
<view class="universe-page"> <view class="universe-page">
<view class="top-safe" :style="{ height: menuButtonInfo.top + 'px' }"></view> <view class="top-safe" :style="{ height: menuButtonInfo.top + 'px' }"></view>
<view class="nav"> <view class="float-bar">
<view class="back" @tap="back"></view> <view class="home-back" @tap="backHome">
<view class="nav-title">我的宇宙</view> <text class="home-back-icon"></text>
<view class="ghost"></view> <text>返回首页</text>
</view>
</view> </view>
<view class="profile"> <view class="profile">
<image class="orb-img" v-if="profile.avatarUrl" :src="profile.avatarUrl" mode="aspectFill"></image> <image class="orb-img" v-if="profile.avatarUrl" :src="profile.avatarUrl" mode="aspectFill"></image>
@ -43,16 +44,21 @@
<view class="arrow"></view> <view class="arrow"></view>
</view> </view>
</view> </view>
<ie-bottom-tab active="universe" :unread-count="unreadCount"></ie-bottom-tab>
</view> </view>
</template> </template>
<script> <script>
import { getIeProfile } from '@/common/ieApi.js' import { getIeProfile, getIeUnreadCount } from '@/common/ieApi.js'
import IeBottomTab from '@/components/ie-bottom-tab/ie-bottom-tab.vue'
export default { export default {
components: { IeBottomTab },
data() { data() {
return { return {
menuButtonInfo: { top: 44 }, menuButtonInfo: { top: 44 },
unreadCount: 0,
unreadTimer: null,
profile: {}, profile: {},
orbit: [ orbit: [
{ day: '今天', value: 72, mood: '轻了一点' }, { day: '今天', value: 72, mood: '轻了一点' },
@ -61,16 +67,42 @@
] ]
} }
}, },
onLoad() { onLoad(options = {}) {
if (uni.getMenuButtonBoundingClientRect) this.menuButtonInfo = uni.getMenuButtonBoundingClientRect() if (uni.getMenuButtonBoundingClientRect) this.menuButtonInfo = uni.getMenuButtonBoundingClientRect()
this.unreadCount = Number(options.unreadCount) || 0
this.loadProfile() this.loadProfile()
}, },
onShow() {
this.startUnreadTimer()
},
onHide() {
this.stopUnreadTimer()
},
onUnload() {
this.stopUnreadTimer()
},
methods: { methods: {
startUnreadTimer() {
if (this.unreadTimer) return
this.unreadTimer = setInterval(() => {
this.refreshUnreadCount()
}, 15000)
},
stopUnreadTimer() {
if (!this.unreadTimer) return
clearInterval(this.unreadTimer)
this.unreadTimer = null
},
async refreshUnreadCount() {
const count = await getIeUnreadCount()
if (count === null || count === undefined) return
this.unreadCount = Number(count) || 0
},
async loadProfile() { async loadProfile() {
const profile = await getIeProfile() const profile = await getIeProfile()
if (profile) this.profile = profile if (profile) this.profile = profile
}, },
back() { uni.navigateBack() }, backHome() { uni.redirectTo({ url: '/pages/index/index' }) },
editProfile() { uni.navigateTo({ url: '/package1/ieBrowser/profileSetup?edit=1' }) }, editProfile() { uni.navigateTo({ url: '/package1/ieBrowser/profileSetup?edit=1' }) },
previewPersona(index) { previewPersona(index) {
uni.previewImage({ urls: this.profile.personaImages || [], current: this.profile.personaImages[index] }) uni.previewImage({ urls: this.profile.personaImages || [], current: this.profile.personaImages[index] })
@ -82,10 +114,11 @@
<style lang="scss" scoped> <style lang="scss" scoped>
page { background: #f7f9ff; } page { background: #f7f9ff; }
.universe-page { min-height: 100vh; padding: 0 32rpx 60rpx; box-sizing: border-box; color: #161b2e; background: radial-gradient(circle at 50% 12%, rgba(169,255,231,.42), transparent 330rpx), radial-gradient(circle at 86% 28%, rgba(255,184,209,.24), transparent 320rpx), linear-gradient(180deg, #fbfdff, #eef4ff 62%, #fff4e8); } .universe-page { min-height: 100vh; padding: 0 32rpx 230rpx; box-sizing: border-box; color: #161b2e; background: radial-gradient(circle at 50% 12%, rgba(169,255,231,.42), transparent 330rpx), radial-gradient(circle at 86% 28%, rgba(255,184,209,.24), transparent 320rpx), linear-gradient(180deg, #fbfdff, #eef4ff 62%, #fff4e8); }
.nav { height: 90rpx; display: flex; align-items: center; } .float-bar { position: relative; z-index: 5; display: flex; align-items: center; justify-content: space-between; margin-top: 14rpx; }
.back, .ghost { width: 70rpx; font-size: 56rpx; color: rgba(22,27,46,.55); } .home-back { display: flex; align-items: center; height: 58rpx; padding: 0 22rpx 0 12rpx; border: 1rpx solid rgba(255,255,255,.88); border-radius: 999rpx; color: rgba(22,27,46,.66); background: rgba(255,255,255,.66); backdrop-filter: blur(20rpx); box-shadow: 0 14rpx 36rpx rgba(96,112,160,.12), inset 0 1rpx 0 rgba(255,255,255,.95); font-size: 23rpx; font-weight: 800; }
.nav-title { flex: 1; text-align: center; font-size: 30rpx; font-weight: 800; } .home-back:active { transform: scale(.95); background: rgba(169,255,231,.7); }
.home-back-icon { margin-right: 6rpx; font-size: 40rpx; line-height: 50rpx; font-weight: 400; }
.profile { padding-top: 18rpx; text-align: center; } .profile { padding-top: 18rpx; text-align: center; }
.orb, .orb,
.orb-img { width: 148rpx; height: 148rpx; margin: 0 auto; border-radius: 50%; box-shadow: 0 0 80rpx rgba(169,255,231,.18); } .orb-img { width: 148rpx; height: 148rpx; margin: 0 auto; border-radius: 50%; box-shadow: 0 0 80rpx rgba(169,255,231,.18); }

2
package1/planet/index.vue

@ -175,7 +175,7 @@
</view> </view>
<view class="guide-item"> <view class="guide-item">
<text>4</text> <text>4</text>
<view>补给地图农场券树防御塔和校园下单给券任务适合每天顺手经营</view> <view>补给地图券树培育防御塔升级下单给券适合每天顺手经营</view>
</view> </view>
</view> </view>
<view class="guide-btn" @tap="guideVisible=false">知道怎么玩了</view> <view class="guide-btn" @tap="guideVisible=false">知道怎么玩了</view>

Loading…
Cancel
Save