20260325稳定版
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,579 @@
|
||||
<template>
|
||||
<view class="select-page">
|
||||
<view class="select-header" :style="{ paddingTop: statusBarPadding }">
|
||||
<u-icon name="arrow-left" size="32" color="#1f2048" @click="goBack"></u-icon>
|
||||
<text class="header-title">我是玄衡</text>
|
||||
</view>
|
||||
|
||||
<view class="intro-card">
|
||||
<image class="intro-avatar" src="/static/logo.jpg"
|
||||
mode="aspectFill"></image>
|
||||
<view class="flex flex-col justify-start items-start">
|
||||
<text class="intro-name">我是玄衡</text>
|
||||
<text class="intro-desc">知五行 破内卷 助你关键决策</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="phrase-list">
|
||||
<view class="phrase" :style="{color: selectedPhrase === item ? '#1b2a75' : '#9a9fc3'}"
|
||||
v-for="(item, index) in phrases" :key="index" @click="selectPhrase(item)">{{
|
||||
item
|
||||
}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- <view class="mentor-choose">-->
|
||||
<!-- <view class="mentor-item" :class="{active: currentMentor === mentor.roleName}" v-for="mentor in mentors"-->
|
||||
<!-- :key="mentor.id" @click="currentMentor = mentor.roleName">-->
|
||||
<!-- <image class="mentor-img" :src="mentor.avatar" mode="aspectFit"></image>-->
|
||||
<!-- <text class="mentor-text">{{ mentor.roleName }}</text>-->
|
||||
<!-- </view>-->
|
||||
<!-- </view>-->
|
||||
|
||||
<!-- <button class="go-btn" @click="goChat">去聊天</button>-->
|
||||
<Login ref="Login" @done="getMessageList"/>
|
||||
<view class="chat-input" :style="{bottom: `${inputBottom}px`}">
|
||||
<view class="m-40 flex justify-between items-center w-100p">
|
||||
<view class="ml-15">
|
||||
<u-icon :name="voiceMode ? '/static/icon/键盘.png' : '/static/icon/话筒.png'" size="60rpx"
|
||||
@click="toggleVoiceMode"/>
|
||||
</view>
|
||||
<view class="input-wrap" v-if="!voiceMode">
|
||||
<textarea
|
||||
:adjust-position="false"
|
||||
class="textarea"
|
||||
v-model="content"
|
||||
placeholder="迷茫困惑的话和我聊聊吧"
|
||||
maxlength="-1"
|
||||
placeholder-style="text-align:center;"
|
||||
auto-height
|
||||
fixed
|
||||
:show-confirm-bar="false"
|
||||
/>
|
||||
</view>
|
||||
<view class="record-wrap" v-else>
|
||||
<view
|
||||
class="record-btn"
|
||||
@longpress="startRecord"
|
||||
@touchend="stopRecord"
|
||||
@touchcancel="stopRecord"
|
||||
>
|
||||
{{ recording ? '录音中...' : recordBtnText }}
|
||||
</view>
|
||||
</view>
|
||||
<button class="send-btn" @click="goChat">发送</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {addChatListReq, chatListReq, listChatRoleReq, makeTransReq, voiceGetResReq} from "@/api/chat";
|
||||
import Login from "@/components/Login.vue";
|
||||
import {getUserInfo} from "@/util/user";
|
||||
import {dictDataReq} from "@/api/common";
|
||||
import {MAIN_API} from "@/config";
|
||||
import {coinAddReq} from "@/api/coin";
|
||||
|
||||
export default {
|
||||
components: {Login},
|
||||
data() {
|
||||
return {
|
||||
statusBarPadding: '40rpx',
|
||||
phrases: ['你好', '今天天气怎么样', '聊聊山海经', '聊聊论语'],
|
||||
mentors: [],
|
||||
currentMentor: '',
|
||||
selectedPhrase: '你好',
|
||||
messageList: [],
|
||||
content: '',
|
||||
inputBottom: 0,
|
||||
toChat: false,
|
||||
voiceMode: false,
|
||||
recording: false,
|
||||
recordBtnText: '按住说话',
|
||||
recorder: null,
|
||||
voicePollingTimer: null,
|
||||
maxVoicePolling: 15,
|
||||
voicePlayingIndex: null,
|
||||
audioCtx: null,
|
||||
voiceType: 'default',
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
// this.inputBottom = uni.getSystemInfoSync().safeAreaInsets.bottom
|
||||
const info = uni.getSystemInfoSync()
|
||||
this.statusBarPadding = `${(info.statusBarHeight || 20) + 10}px`
|
||||
this.getWelcomeWords()
|
||||
// if (!getUserInfo().token) this.$refs.Login.open()
|
||||
// else this.getMessageList()
|
||||
if (getUserInfo().token) this.getMessageList()
|
||||
uni.removeStorageSync('forceNoJump')
|
||||
uni.onKeyboardHeightChange(res => {
|
||||
setTimeout(() => {
|
||||
console.log('onKeyboardHeightChange', res.height)
|
||||
this.inputBottom = res.height
|
||||
}, 100)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
onKeyboardChange(e) {
|
||||
this.inputBottom = e.detail.height
|
||||
},
|
||||
async getWelcomeWords() {
|
||||
const {data} = await dictDataReq({
|
||||
dictId: 167
|
||||
})
|
||||
this.phrases = data.map(item => item.dictDataCode)
|
||||
this.selectedPhrase = this.phrases[0]
|
||||
this.content = this.phrases[0]
|
||||
},
|
||||
goBack() {
|
||||
uni.setStorageSync('forceNoJump', 1)
|
||||
uni.switchTab({url: '/pages/index/index'})
|
||||
},
|
||||
async getRoleList() {
|
||||
const {data} = await listChatRoleReq()
|
||||
this.mentors = data
|
||||
this.currentMentor = data[0].roleName
|
||||
},
|
||||
toggleVoiceMode() {
|
||||
uni.authorize({
|
||||
scope: 'scope.record',
|
||||
success() {
|
||||
console.log('授权成功')
|
||||
},
|
||||
fail() {
|
||||
console.log('授权失败')
|
||||
},
|
||||
complete(e) {
|
||||
console.log('授权完成', e)
|
||||
}
|
||||
})
|
||||
if (this.voiceMode) {
|
||||
if (this.recorder && this.recording) {
|
||||
this.recorder.stop()
|
||||
}
|
||||
this.recording = false
|
||||
this.voiceMode = false
|
||||
this.recordBtnText = '按住说话'
|
||||
return
|
||||
}
|
||||
this.voiceMode = true
|
||||
this.recordBtnText = '按住说话'
|
||||
typeof uni.hideKeyboard === 'function' && uni.hideKeyboard()
|
||||
this.initRecorder()
|
||||
},
|
||||
async goChat() {
|
||||
if (!getUserInfo().token) return this.$refs.Login.open()
|
||||
const mentor = this.mentors.find(item => item.name === this.currentMentor)
|
||||
let url = `/pages/user/setting?word=${this.content}`
|
||||
if (mentor) {
|
||||
url += `&roleName=${mentor.name}&roleDesc=${mentor.desc}&avatar=${mentor.avatar}`
|
||||
const {data} = await addChatListReq({
|
||||
roleName: mentor.name,
|
||||
roleDesc: mentor.desc,
|
||||
roleId: mentor.id
|
||||
})
|
||||
url += `&chatId=${data.id}`
|
||||
}
|
||||
uni.navigateTo({url})
|
||||
},
|
||||
async getMessageList() {
|
||||
const res = await chatListReq()
|
||||
this.messageList = res.data
|
||||
if (this.messageList.length > 0)
|
||||
await this.getRoleList()
|
||||
},
|
||||
selectPhrase(item) {
|
||||
this.selectedPhrase = item
|
||||
this.content = item
|
||||
},
|
||||
initRecorder() {
|
||||
if (this.recorder) return
|
||||
this.recorder = uni.getRecorderManager()
|
||||
this.recorder.onStart(() => {
|
||||
this.recording = true
|
||||
this.recordBtnText = '录音中...'
|
||||
})
|
||||
this.recorder.onStop((res) => {
|
||||
this.recording = false
|
||||
const path = res && res.tempFilePath
|
||||
if (path) this.processVoiceFile(path)
|
||||
else this.recordBtnText = '按住说话'
|
||||
})
|
||||
this.recorder.onError(() => {
|
||||
this.recording = false
|
||||
this.recordBtnText = '按住说话'
|
||||
uni.showToast({title: '录音失败', icon: 'none'})
|
||||
})
|
||||
},
|
||||
startRecord() {
|
||||
if (!this.voiceMode) return
|
||||
this.initRecorder()
|
||||
if (this.recording) return
|
||||
this.recording = true
|
||||
this.recordBtnText = '录音中...'
|
||||
try {
|
||||
this.recorder.start({
|
||||
duration: 60000,
|
||||
format: 'mp3',
|
||||
sampleRate: 16000,
|
||||
numberOfChannels: 1,
|
||||
encodeBitRate: 48000
|
||||
})
|
||||
} catch (e) {
|
||||
this.recording = false
|
||||
this.recordBtnText = '按住说话'
|
||||
uni.showToast({title: '无法开始录音', icon: 'none'})
|
||||
}
|
||||
},
|
||||
stopRecord() {
|
||||
if (!this.voiceMode || !this.recording || !this.recorder) return
|
||||
this.recording = false
|
||||
this.recorder.stop()
|
||||
},
|
||||
async processVoiceFile(filePath) {
|
||||
this.recordBtnText = '处理中...'
|
||||
uni.showLoading({title: '识别中...'})
|
||||
try {
|
||||
const {url} = await this.uploadVoiceFile(filePath)
|
||||
if (!url) throw new Error('no file')
|
||||
// const file = 'https://guoxue.gxwebsoft.com/api/file/20251202/97e5e082becb44e88ece0e9cbd050dca.mp3'
|
||||
// const {data: taskId} = await makeTransReq({file})
|
||||
const {data: taskId} = await makeTransReq({file: url})
|
||||
if (taskId) {
|
||||
this.startVoicePolling(taskId)
|
||||
} else {
|
||||
throw new Error('no task')
|
||||
}
|
||||
} catch (err) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({title: '语音识别失败', icon: 'none'})
|
||||
this.recordBtnText = '按住说话'
|
||||
}
|
||||
},
|
||||
uploadVoiceFile(filePath) {
|
||||
const {token} = getUserInfo()
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: `${MAIN_API}/api/file/upload`,
|
||||
name: 'file',
|
||||
filePath,
|
||||
header: {
|
||||
Authorization: token,
|
||||
TenantId: 10543
|
||||
},
|
||||
success: (res) => {
|
||||
try {
|
||||
const result = JSON.parse(res.data)
|
||||
const url = result?.data?.url || result?.data
|
||||
resolve({url})
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
},
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
},
|
||||
startVoicePolling(taskId) {
|
||||
this.clearVoicePolling()
|
||||
let attempt = 0
|
||||
this.voicePollingTimer = setInterval(async () => {
|
||||
attempt += 1
|
||||
try {
|
||||
const {data} = await voiceGetResReq(taskId)
|
||||
const result = typeof data === 'string' ? data : (data?.text || '')
|
||||
if (result && result !== '__NULL__') {
|
||||
this.clearVoicePolling()
|
||||
uni.hideLoading()
|
||||
this.recordBtnText = '按住说话'
|
||||
const wordResult = JSON.parse(data)
|
||||
// console.log('wordResult', wordResult)
|
||||
if (wordResult.Sentences && wordResult.Sentences.length)
|
||||
// this.send(wordResult.Sentences[0].Text)
|
||||
this.content = wordResult.Sentences[0].Text
|
||||
else uni.showToast({title: '未识别到内容', icon: 'none'})
|
||||
} else if (result === '__NULL__') {
|
||||
this.clearVoicePolling()
|
||||
uni.hideLoading()
|
||||
this.recordBtnText = '按住说话'
|
||||
uni.showToast({title: '未识别到内容', icon: 'none'})
|
||||
} else if (attempt >= this.maxVoicePolling) {
|
||||
this.clearVoicePolling()
|
||||
uni.hideLoading()
|
||||
this.recordBtnText = '按住说话'
|
||||
uni.showToast({title: '识别超时', icon: 'none'})
|
||||
}
|
||||
} catch (err) {
|
||||
this.clearVoicePolling()
|
||||
uni.hideLoading()
|
||||
this.recordBtnText = '按住说话'
|
||||
uni.showToast({title: '识别失败', icon: 'none'})
|
||||
}
|
||||
}, 2000)
|
||||
},
|
||||
clearVoicePolling() {
|
||||
if (this.voicePollingTimer) {
|
||||
clearInterval(this.voicePollingTimer)
|
||||
this.voicePollingTimer = null
|
||||
}
|
||||
},
|
||||
},
|
||||
onUnload() {
|
||||
this.clearVoicePolling()
|
||||
this.stopAudio()
|
||||
this.recordBtnText = '按住说话'
|
||||
uni.hideLoading()
|
||||
uni.offKeyboardHeightChange()
|
||||
},
|
||||
onHide() {
|
||||
this.clearVoicePolling()
|
||||
this.stopAudio()
|
||||
this.recordBtnText = '按住说话'
|
||||
uni.hideLoading()
|
||||
uni.offKeyboardHeightChange()
|
||||
},
|
||||
onShareTimeline() {
|
||||
if (getUserInfo().token) {
|
||||
coinAddReq({
|
||||
type: '+',
|
||||
desc: '分享获得',
|
||||
num: 1
|
||||
})
|
||||
}
|
||||
return {
|
||||
title: '简幸',
|
||||
path: '/pages/index/index'
|
||||
}
|
||||
},
|
||||
onShareAppMessage() {
|
||||
if (getUserInfo().token) {
|
||||
coinAddReq({
|
||||
type: '+',
|
||||
desc: '分享获得',
|
||||
num: 1
|
||||
})
|
||||
}
|
||||
return {
|
||||
title: '简幸',
|
||||
path: '/pages/index/index'
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.select-page {
|
||||
min-height: 100vh;
|
||||
//background: linear-gradient(180deg, #f5f7ff 0%, #ffffff 100%);
|
||||
padding: 40rpx 30rpx 80rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.select-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
color: #14164e;
|
||||
}
|
||||
|
||||
.pill {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
padding: 10rpx 28rpx;
|
||||
border-radius: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.intro-card {
|
||||
margin-top: 200rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 30rpx;
|
||||
padding: 20rpx;
|
||||
//box-shadow: 0 10rpx 20rpx rgba(123, 137, 255, 0.15);
|
||||
}
|
||||
|
||||
.intro-avatar {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 20rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.intro-name {
|
||||
font-size: 32rpx;
|
||||
color: #14164e;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.intro-desc {
|
||||
font-size: 26rpx;
|
||||
color: #9a9fc3;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.phrase-list {
|
||||
margin-top: 30rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.phrase {
|
||||
background: rgba(160, 175, 255, 0.2);
|
||||
border-radius: 40rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
color: #26306d;
|
||||
font-size: 28rpx;
|
||||
display: inline-flex;
|
||||
width: auto;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.mentor-choose {
|
||||
margin-top: 40rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.mentor-item {
|
||||
width: 48%;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 30rpx;
|
||||
text-align: center;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 12rpx 20rpx rgba(120, 134, 255, 0.15);
|
||||
}
|
||||
|
||||
.mentor-item.active {
|
||||
border: 2rpx solid #f0c730;
|
||||
}
|
||||
|
||||
.mentor-img {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
}
|
||||
|
||||
.mentor-text {
|
||||
margin-top: 12rpx;
|
||||
font-size: 28rpx;
|
||||
color: #424577;
|
||||
}
|
||||
|
||||
.go-btn {
|
||||
margin-top: 60rpx;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
border-radius: 45rpx;
|
||||
background: #1b2a75;
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
|
||||
.chat-input {
|
||||
width: 100%;
|
||||
padding: 40rpx 0;
|
||||
//padding: 20rpx 30rpx 40rpx;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
background: #eee;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.chat-input .input-wrap {
|
||||
flex: 1;
|
||||
border-radius: 10rpx;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
padding: 15rpx 30rpx;
|
||||
margin: 0 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
//height: 100rpx;
|
||||
min-height: 40rpx;
|
||||
}
|
||||
|
||||
.chat-input .textarea {
|
||||
width: 100%;
|
||||
//height: 100%;
|
||||
background: none !important;
|
||||
color: #000 !important;
|
||||
text-align: left;
|
||||
line-height: 40rpx;
|
||||
padding: 0;
|
||||
min-height: 35rpx;
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
width: 120rpx;
|
||||
height: 70rpx;
|
||||
border-radius: 20rpx;
|
||||
//background: linear-gradient(120deg, #fbc889, #f37a5b);
|
||||
background-color: white;
|
||||
border: 1px solid white;
|
||||
color: #000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 15rpx 0 0;
|
||||
font-size: 25rpx;
|
||||
}
|
||||
|
||||
.send-btn:after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
.record-wrap {
|
||||
flex: 1;
|
||||
border-radius: 10rpx;
|
||||
//background: linear-gradient(120deg, #fbc889, #f37a5b);
|
||||
background: #fff;
|
||||
color: #000;
|
||||
padding: 15rpx 30rpx;
|
||||
margin: 0 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 40rpx;
|
||||
}
|
||||
|
||||
.record-btn {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.voice-toggle {
|
||||
width: 120rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #dfe1f2;
|
||||
color: #1f2654;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 10rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.voice-toggle.active {
|
||||
background: #21284f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user