From b92e3b8adb5ea18e040bc0726ff7e761113df4d2 Mon Sep 17 00:00:00 2001 From: francis_fh <13935151924@163.com> Date: Thu, 22 Jan 2026 18:58:19 +0800 Subject: [PATCH] =?UTF-8?q?AI=E6=A8=A1=E5=9D=97=E8=81=94=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/globalFunction.js | 6 +- components/CustomTabBar/CustomTabBar.vue | 10 +- components/tabbar/midell-box.vue | 2 +- components/wxAuthLogin/WxAuthLogin.vue | 12 +- hook/useTTSPlayer.js | 614 ++++++++---------- main.js | 16 - .../pages}/complete-info/company-info.vue | 0 .../pages}/complete-info/complete-info.vue | 2 +- .../components/map-location-picker.vue | 0 .../pages}/complete-info/skill-search.vue | 0 .../pages}/job/companySearch.vue | 0 {pages => packageA/pages}/job/publishJob.vue | 0 .../pages}/nearby/components/four.vue | 0 .../pages}/nearby/components/one.vue | 0 .../pages}/nearby/components/three.vue | 0 .../pages}/nearby/components/two.vue | 0 {pages => packageA/pages}/nearby/nearby.vue | 0 packageA/pages/personalInfo/personalInfo.vue | 2 +- packageRc/pages/jobList/jobList.vue | 4 +- pages.json | 98 +-- pages/index/components/index-one.vue | 8 +- pages/mine/mine.vue | 2 +- utils/streamRequest.js | 101 ++- 23 files changed, 424 insertions(+), 453 deletions(-) rename {pages => packageA/pages}/complete-info/company-info.vue (100%) rename {pages => packageA/pages}/complete-info/complete-info.vue (99%) rename {pages => packageA/pages}/complete-info/components/map-location-picker.vue (100%) rename {pages => packageA/pages}/complete-info/skill-search.vue (100%) rename {pages => packageA/pages}/job/companySearch.vue (100%) rename {pages => packageA/pages}/job/publishJob.vue (100%) rename {pages => packageA/pages}/nearby/components/four.vue (100%) rename {pages => packageA/pages}/nearby/components/one.vue (100%) rename {pages => packageA/pages}/nearby/components/three.vue (100%) rename {pages => packageA/pages}/nearby/components/two.vue (100%) rename {pages => packageA/pages}/nearby/nearby.vue (100%) diff --git a/common/globalFunction.js b/common/globalFunction.js index 8a183eb..a523251 100644 --- a/common/globalFunction.js +++ b/common/globalFunction.js @@ -72,18 +72,18 @@ export const navTo = function(url, { const pages = getCurrentPages(); if (pages.length >= 10) { uni.redirectTo({ - url: '/pages/complete-info/complete-info', + url: '/packageA/pages/complete-info/complete-info', fail: (err) => { console.error('页面跳转失败:', err); } }); } else { uni.navigateTo({ - url: '/pages/complete-info/complete-info', + url: '/packageA/pages/complete-info/complete-info', fail: (err) => { console.error('页面跳转失败:', err); uni.redirectTo({ - url: '/pages/complete-info/complete-info', + url: '/packageA/pages/complete-info/complete-info', fail: (err2) => { console.error('redirectTo也失败:', err2); } diff --git a/components/CustomTabBar/CustomTabBar.vue b/components/CustomTabBar/CustomTabBar.vue index 70661ca..444ecd7 100644 --- a/components/CustomTabBar/CustomTabBar.vue +++ b/components/CustomTabBar/CustomTabBar.vue @@ -104,7 +104,7 @@ const generateTabbarList = () => { baseItems.splice(1, 0, { id: 1, text: '发布岗位', - path: '/pages/job/publishJob', + path: '/packageA/pages/job/publishJob', iconPath: '/static/tabbar/post.png', selectedIconPath: '/static/tabbar/posted.png', centerItem: false, @@ -173,7 +173,7 @@ const switchTab = (item, index) => { // 检查是否为需要登录的页面 const loginRequiredPages = [ - '/pages/job/publishJob', + '/packageA/pages/job/publishJob', '/pages/mine/mine', '/pages/mine/company-mine' ]; @@ -190,7 +190,7 @@ const switchTab = (item, index) => { } // 已登录,处理特定页面的逻辑 - if (item.path === '/pages/job/publishJob') { + if (item.path === '/packageA/pages/job/publishJob') { // 检查企业信息是否完整 const cachedUserInfo = uni.getStorageSync('userInfo') || {}; const storeUserInfo = userInfo.value || {}; @@ -200,12 +200,12 @@ const switchTab = (item, index) => { if (!currentUserInfo.company || currentUserInfo.company === null) { // 企业信息为空,跳转到企业信息补全页面 uni.navigateTo({ - url: '/pages/complete-info/company-info', + url: '/packageA/pages/complete-info/company-info', }); } else { // 企业信息完整,跳转到发布岗位页面 uni.navigateTo({ - url: '/pages/job/publishJob', + url: '/packageA/pages/job/publishJob', }); } diff --git a/components/tabbar/midell-box.vue b/components/tabbar/midell-box.vue index 6df2aaa..d3e6e3a 100644 --- a/components/tabbar/midell-box.vue +++ b/components/tabbar/midell-box.vue @@ -110,7 +110,7 @@ const generateTabbarList = () => { baseItems.splice(1, 0, { id: 1, text: '发布岗位', - path: '/pages/job/publishJob', + path: '/packageA/pages/job/publishJob', iconPath: '../../static/tabbar/post.png', selectedIconPath: '../../static/tabbar/posted.png', centerItem: false, diff --git a/components/wxAuthLogin/WxAuthLogin.vue b/components/wxAuthLogin/WxAuthLogin.vue index 0664095..5053a47 100644 --- a/components/wxAuthLogin/WxAuthLogin.vue +++ b/components/wxAuthLogin/WxAuthLogin.vue @@ -188,12 +188,12 @@ const getPhoneNumber = (e) => { if (userType.value === 1 && !resData.idCard) { // 求职者跳转到个人信息补全页面 uni.navigateTo({ - url: '/pages/complete-info/complete-info?step=1' + url: '/packageA/pages/complete-info/complete-info?step=1' }); } else if (userType.value === 0 && !resData.idCard) { // 招聘者跳转到企业信息补全页面 uni.navigateTo({ - url: '/pages/complete-info/company-info' + url: '/packageA/pages/complete-info/company-info' }); } } @@ -271,12 +271,12 @@ const wxLogin = () => { if (userType.value === 1) { // 求职者跳转到个人信息补全页面 uni.navigateTo({ - url: '/pages/complete-info/complete-info?step=1' + url: '/packageA/pages/complete-info/complete-info?step=1' }); } else if (userType.value === 0) { // 招聘者跳转到企业信息补全页面 uni.navigateTo({ - url: '/pages/complete-info/company-info' + url: '/packageA/pages/complete-info/company-info' }); } } @@ -325,12 +325,12 @@ const testLogin = () => { if (userType.value === 1) { // 求职者跳转到个人信息补全页面 uni.navigateTo({ - url: '/pages/complete-info/complete-info?step=1' + url: '/packageA/pages/complete-info/complete-info?step=1' }); } else if (userType.value === 0) { // 招聘者跳转到企业信息补全页面 uni.navigateTo({ - url: '/pages/complete-info/company-info' + url: '/packageA/pages/complete-info/company-info' }); } } diff --git a/hook/useTTSPlayer.js b/hook/useTTSPlayer.js index 24a2689..525318d 100644 --- a/hook/useTTSPlayer.js +++ b/hook/useTTSPlayer.js @@ -1,297 +1,195 @@ -import { - ref, - onUnmounted, - onBeforeUnmount, - onMounted -} from 'vue' -import { - onHide, - onUnload -} from '@dcloudio/uni-app' -import WavDecoder from '@/lib/wav-decoder@1.3.0.js' - -export function useTTSPlayer(wsUrl) { - const isSpeaking = ref(false) - const isPaused = ref(false) - const isComplete = ref(false) - - // #ifdef H5 - const audioContext = typeof window !== 'undefined' && (window.AudioContext || window.webkitAudioContext) - ? new(window.AudioContext || window.webkitAudioContext)() - : null - // #endif - - // #ifdef MP-WEIXIN - const audioContext = null // 微信小程序不支持 AudioContext - // #endif - - let playTime = audioContext ? audioContext.currentTime : 0 - let sourceNodes = [] - let socket = null - let sampleRate = 16000 - let numChannels = 1 - let isHeaderDecoded = false - let pendingText = null - - let currentPlayId = 0 - let activePlayId = 0 - - const speak = (text) => { - if (!audioContext) { - console.warn('⚠️ TTS not supported in current environment'); - return; - } - - console.log('🎤 TTS speak function called'); - console.log('📝 Text to synthesize:', text ? text.substring(0, 100) + '...' : 'No text'); - console.log('🔗 WebSocket URL:', wsUrl); - - currentPlayId++ - const myPlayId = currentPlayId - console.log('🆔 Play ID:', myPlayId); - - reset() - pendingText = text - activePlayId = myPlayId - - console.log('✅ Speak function setup complete'); - } - - const pause = () => { - if (!audioContext) { - console.warn('⚠️ TTS not supported in current environment'); - return; - } - - console.log('⏸️ TTS pause called'); - console.log('🔊 AudioContext state:', audioContext.state); - console.log('🔊 Is speaking before pause:', isSpeaking.value); - console.log('⏸️ Is paused before pause:', isPaused.value); - - if (audioContext.state === 'running') { - audioContext.suspend() - isPaused.value = true - // 不要设置 isSpeaking.value = false,保持当前状态 - console.log('✅ Audio paused successfully'); - } else { - console.log('⚠️ AudioContext is not running, cannot pause'); - } - - console.log('🔊 Is speaking after pause:', isSpeaking.value); - console.log('⏸️ Is paused after pause:', isPaused.value); - } - - const resume = () => { - if (!audioContext) { - console.warn('⚠️ TTS not supported in current environment'); - return; - } - - console.log('▶️ TTS resume called'); - console.log('🔊 AudioContext state:', audioContext.state); - console.log('🔊 Is speaking before resume:', isSpeaking.value); - console.log('⏸️ Is paused before resume:', isPaused.value); - - if (audioContext.state === 'suspended') { - audioContext.resume() - isPaused.value = false - isSpeaking.value = true - console.log('✅ Audio resumed successfully'); - } else { - console.log('⚠️ AudioContext is not suspended, cannot resume'); - } - - console.log('🔊 Is speaking after resume:', isSpeaking.value); - console.log('⏸️ Is paused after resume:', isPaused.value); - } - - const cancelAudio = () => { - stop() - } - - const stop = () => { - isSpeaking.value = false - isPaused.value = false - isComplete.value = false - playTime = audioContext ? audioContext.currentTime : 0 - - sourceNodes.forEach(node => { - try { - node.stop() - node.disconnect() - } catch (e) {} - }) - sourceNodes = [] - - if (socket) { - socket.close() - socket = null - } - - isHeaderDecoded = false - pendingText = null - } - - const reset = () => { - stop() - isSpeaking.value = false - isPaused.value = false - isComplete.value = false - playTime = audioContext ? audioContext.currentTime : 0 - initWebSocket() - } - - const initWebSocket = () => { - if (!audioContext) { - console.warn('⚠️ WebSocket TTS not supported in current environment'); - return; - } - - const thisPlayId = currentPlayId - console.log('🔌 Initializing WebSocket connection'); - console.log('🔗 WebSocket URL:', wsUrl); - console.log('🆔 This play ID:', thisPlayId); - - socket = new WebSocket(wsUrl) - socket.binaryType = 'arraybuffer' - - // 设置心跳检测,避免超时 - const heartbeatInterval = setInterval(() => { - if (socket && socket.readyState === WebSocket.OPEN) { - socket.send(JSON.stringify({ type: 'ping' })); - } - }, 30000); // 每30秒发送一次心跳 - - socket.onopen = () => { - console.log('✅ WebSocket connection opened'); - if (pendingText && thisPlayId === activePlayId) { - const seepdText = extractSpeechText(pendingText) - console.log('📤 Sending text to TTS server:', seepdText.substring(0, 100) + '...'); - socket.send(seepdText) - pendingText = null - } else { - console.log('❌ No pending text or play ID mismatch'); - console.log('📝 Pending text exists:', !!pendingText); - console.log('🆔 Play ID match:', thisPlayId === activePlayId); - } - } - - socket.onerror = (error) => { - console.error('❌ WebSocket error:', error); - } - - socket.onclose = (event) => { - console.log('🔌 WebSocket connection closed:', event.code, event.reason); - clearInterval(heartbeatInterval); - } - - socket.onmessage = async (e) => { - if (thisPlayId !== activePlayId) return // 忽略旧播放的消息 - - if (typeof e.data === 'string') { - try { - const msg = JSON.parse(e.data) - console.log('📨 TTS server message:', msg); - if (msg.status === 'complete') { - console.log('✅ TTS synthesis completed'); - isComplete.value = true - // 计算剩余播放时间,确保播放完整 - const remainingTime = audioContext ? Math.max(0, (playTime - audioContext.currentTime) * 1000) : 0; - console.log('⏱️ Remaining play time:', remainingTime + 'ms'); - setTimeout(() => { - if (thisPlayId === activePlayId) { - console.log('🔇 TTS playback finished, setting isSpeaking to false'); - isSpeaking.value = false - } - }, remainingTime + 500) // 额外500ms缓冲时间 - } - } catch (e) { - console.log('[TTSPlayer] 文本消息:', e.data) - } - } else if (e.data instanceof ArrayBuffer) { - if (!isHeaderDecoded) { - try { - const decoded = await WavDecoder.decode(e.data) - sampleRate = decoded.sampleRate - numChannels = decoded.channelData.length - decoded.channelData.forEach((channel, i) => { - const audioBuffer = audioContext.createBuffer(1, channel.length, - sampleRate) - audioBuffer.copyToChannel(channel, 0) - playBuffer(audioBuffer) - }) - isHeaderDecoded = true - } catch (err) { - console.error('WAV 解码失败:', err) - } - } else { - const pcm = new Int16Array(e.data) - const audioBuffer = pcmToAudioBuffer(pcm, sampleRate, numChannels) - playBuffer(audioBuffer) - } - } - } - } - - const pcmToAudioBuffer = (pcm, sampleRate, numChannels) => { - if (!audioContext) return null; - - const length = pcm.length / numChannels - const audioBuffer = audioContext.createBuffer(numChannels, length, sampleRate) - for (let ch = 0; ch < numChannels; ch++) { - const channelData = audioBuffer.getChannelData(ch) - for (let i = 0; i < length; i++) { - const sample = pcm[i * numChannels + ch] - channelData[i] = sample / 32768 - } - } - return audioBuffer - } - - const playBuffer = (audioBuffer) => { - if (!audioContext || !audioBuffer) return; - - console.log('🎵 playBuffer called, duration:', audioBuffer.duration + 's'); - if (!isSpeaking.value) { - playTime = audioContext.currentTime - console.log('🎵 Starting new audio playback at time:', playTime); - } - const source = audioContext.createBufferSource() - source.buffer = audioBuffer - source.connect(audioContext.destination) - source.start(playTime) - sourceNodes.push(source) - playTime += audioBuffer.duration - isSpeaking.value = true - console.log('🎵 Audio scheduled, new playTime:', playTime); - - // 添加音频播放结束监听 - source.onended = () => { - console.log('🎵 Audio buffer finished playing'); - } - } - - onUnmounted(() => { - stop() - }) - - // 页面刷新/关闭时 - onMounted(() => { - if (typeof window !== 'undefined') { - window.addEventListener('beforeunload', cancelAudio) - } - }) - - onBeforeUnmount(() => { - cancelAudio() - if (typeof window !== 'undefined') { - window.removeEventListener('beforeunload', cancelAudio) - } - }) - - onHide(cancelAudio) - onUnload(cancelAudio) - +import { + ref, + onUnmounted, + onBeforeUnmount, + onMounted +} from 'vue' +import { + onHide, + onUnload +} from '@dcloudio/uni-app' +import WavDecoder from '@/lib/wav-decoder@1.3.0.js' + +export function useTTSPlayer(httpUrl) { + const isSpeaking = ref(false) + const isPaused = ref(false) + const isComplete = ref(false) + + // #ifdef H5 + const audioContext = typeof window !== 'undefined' && (window.AudioContext || window.webkitAudioContext) + ? new(window.AudioContext || window.webkitAudioContext)() + : null + // #endif + + // #ifdef MP-WEIXIN + const audioContext = null // 微信小程序不支持 AudioContext + // #endif + + let currentAudioBuffer = null + let currentSource = null + let playTimeOffset = 0 + + const speak = async (text) => { + if (!audioContext) { + console.warn('⚠️ TTS not supported in current environment'); + return; + } + + console.log('🎤 TTS speak function called'); + console.log('📝 Text to synthesize:', text ? text.substring(0, 100) + '...' : 'No text'); + console.log('🔗 HTTP URL:', httpUrl); + + // 停止当前播放 + stop() + + try { + // 提取要合成的文本 + const speechText = extractSpeechText(text) + console.log('📤 Sending text to TTS server via GET:', speechText.substring(0, 100) + '...'); + + // 构建GET请求URL + const url = `${httpUrl}?text=${encodeURIComponent(speechText)}` + console.log('🔗 Final GET URL:', url); + + // 发送GET请求获取语音数据 + const response = await fetch(url) + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + // 获取二进制数据 + const arrayBuffer = await response.arrayBuffer() + console.log('✅ Received audio data, size:', arrayBuffer.byteLength + ' bytes'); + + // 解码音频数据 + const decoded = await WavDecoder.decode(arrayBuffer) + console.log('✅ Audio decoded, sampleRate:', decoded.sampleRate, 'channels:', decoded.channelData.length); + + // 播放音频 + playDecodedAudio(decoded) + } catch (error) { + console.error('❌ TTS synthesis failed:', error); + isSpeaking.value = false + isComplete.value = false + } + } + + const playDecodedAudio = (decoded) => { + if (!audioContext) return; + + // 使用第一个声道的数据 + const audioBuffer = audioContext.createBuffer(1, decoded.channelData[0].length, decoded.sampleRate) + audioBuffer.copyToChannel(decoded.channelData[0], 0) + + currentAudioBuffer = audioBuffer + + // 创建音频源 + currentSource = audioContext.createBufferSource() + currentSource.buffer = audioBuffer + currentSource.connect(audioContext.destination) + + // 监听播放结束 + currentSource.onended = () => { + console.log('🎵 Audio playback completed'); + isSpeaking.value = false + isComplete.value = true + } + + // 开始播放 + currentSource.start() + isSpeaking.value = true + isPaused.value = false + isComplete.value = false + console.log('� Audio playback started'); + } + + const pause = () => { + if (!audioContext || !isSpeaking.value || isPaused.value) { + console.warn('⚠️ Cannot pause TTS playback'); + return; + } + + console.log('⏸️ TTS pause called'); + + if (audioContext.state === 'running') { + audioContext.suspend() + isPaused.value = true + // 保存当前播放位置 + playTimeOffset = audioContext.currentTime + console.log('✅ Audio paused successfully'); + } + } + + const resume = () => { + if (!audioContext || !isSpeaking.value || !isPaused.value) { + console.warn('⚠️ Cannot resume TTS playback'); + return; + } + + console.log('▶️ TTS resume called'); + + if (audioContext.state === 'suspended') { + audioContext.resume() + isPaused.value = false + console.log('✅ Audio resumed successfully'); + } + } + + const cancelAudio = () => { + stop() + } + + const stop = () => { + console.log('⏹️ TTS stop called'); + + if (currentSource) { + try { + currentSource.stop() + currentSource.disconnect() + } catch (e) { + console.error('❌ Error stopping audio source:', e); + } + currentSource = null + } + + if (audioContext && audioContext.state === 'running') { + try { + audioContext.suspend() + } catch (e) { + console.error('❌ Error suspending audio context:', e); + } + } + + isSpeaking.value = false + isPaused.value = false + isComplete.value = false + currentAudioBuffer = null + playTimeOffset = 0 + + console.log('✅ TTS playback stopped'); + } + + onUnmounted(() => { + stop() + }) + + // 页面刷新/关闭时 + onMounted(() => { + if (typeof window !== 'undefined') { + window.addEventListener('beforeunload', cancelAudio) + } + }) + + onBeforeUnmount(() => { + cancelAudio() + if (typeof window !== 'undefined') { + window.removeEventListener('beforeunload', cancelAudio) + } + }) + + onHide(cancelAudio) + onUnload(cancelAudio) + return { speak, pause, @@ -300,68 +198,68 @@ export function useTTSPlayer(wsUrl) { isSpeaking, isPaused, isComplete - } -} - -function extractSpeechText(markdown) { - console.log('🔍 extractSpeechText called'); - console.log('📝 Input markdown length:', markdown ? markdown.length : 0); - console.log('📝 Input markdown preview:', markdown ? markdown.substring(0, 200) + '...' : 'No markdown'); - - const jobRegex = /``` job-json\s*({[\s\S]*?})\s*```/g; - const jobs = []; - let match; - let lastJobEndIndex = 0; - let firstJobStartIndex = -1; - - // 提取岗位 json 数据及前后位置 - while ((match = jobRegex.exec(markdown)) !== null) { - const jobStr = match[1]; - try { - const job = JSON.parse(jobStr); - jobs.push(job); - if (firstJobStartIndex === -1) { - firstJobStartIndex = match.index; - } - lastJobEndIndex = jobRegex.lastIndex; - console.log('✅ Found job:', job.jobTitle); - } catch (e) { - console.warn('JSON 解析失败', e); - } - } - - console.log('📊 Jobs found:', jobs.length); - console.log('📍 First job start index:', firstJobStartIndex); - console.log('📍 Last job end index:', lastJobEndIndex); - - // 提取引导语(第一个 job-json 之前的文字) - const guideText = firstJobStartIndex > 0 ? - markdown.slice(0, firstJobStartIndex).trim() : - ''; - - // 提取结束语(最后一个 job-json 之后的文字) - const endingText = lastJobEndIndex < markdown.length ? - markdown.slice(lastJobEndIndex).trim() : - ''; - - console.log('📝 Guide text:', guideText); - console.log('📝 Ending text:', endingText); - - // 岗位信息格式化为语音文本 - const jobTexts = jobs.map((job, index) => { - return `第 ${index + 1} 个岗位,岗位名称是:${job.jobTitle},公司是:${job.companyName},薪资:${job.salary},地点:${job.location},学历要求:${job.education},经验要求:${job.experience}。`; - }); - - // 拼接总语音内容 - const finalTextParts = []; - if (guideText) finalTextParts.push(guideText); - finalTextParts.push(...jobTexts); - if (endingText) finalTextParts.push(endingText); - - const finalText = finalTextParts.join('\n'); - console.log('🎤 Final TTS text length:', finalText.length); - console.log('🎤 Final TTS text preview:', finalText.substring(0, 200) + '...'); - console.log('🎤 Final TTS text parts count:', finalTextParts.length); - - return finalText; + } +} + +function extractSpeechText(markdown) { + console.log('🔍 extractSpeechText called'); + console.log('📝 Input markdown length:', markdown ? markdown.length : 0); + console.log('📝 Input markdown preview:', markdown ? markdown.substring(0, 200) + '...' : 'No markdown'); + + const jobRegex = /``` job-json\s*({[\s\S]*?})\s*```/g; + const jobs = []; + let match; + let lastJobEndIndex = 0; + let firstJobStartIndex = -1; + + // 提取岗位 json 数据及前后位置 + while ((match = jobRegex.exec(markdown)) !== null) { + const jobStr = match[1]; + try { + const job = JSON.parse(jobStr); + jobs.push(job); + if (firstJobStartIndex === -1) { + firstJobStartIndex = match.index; + } + lastJobEndIndex = jobRegex.lastIndex; + console.log('✅ Found job:', job.jobTitle); + } catch (e) { + console.warn('JSON 解析失败', e); + } + } + + console.log('📊 Jobs found:', jobs.length); + console.log('📍 First job start index:', firstJobStartIndex); + console.log('📍 Last job end index:', lastJobEndIndex); + + // 提取引导语(第一个 job-json 之前的文字) + const guideText = firstJobStartIndex > 0 ? + markdown.slice(0, firstJobStartIndex).trim() : + ''; + + // 提取结束语(最后一个 job-json 之后的文字) + const endingText = lastJobEndIndex < markdown.length ? + markdown.slice(lastJobEndIndex).trim() : + ''; + + console.log('📝 Guide text:', guideText); + console.log('📝 Ending text:', endingText); + + // 岗位信息格式化为语音文本 + const jobTexts = jobs.map((job, index) => { + return `第 ${index + 1} 个岗位,岗位名称是:${job.jobTitle},公司是:${job.companyName},薪资:${job.salary},地点:${job.location},学历要求:${job.education},经验要求:${job.experience}。`; + }); + + // 拼接总语音内容 + const finalTextParts = []; + if (guideText) finalTextParts.push(guideText); + finalTextParts.push(...jobTexts); + if (endingText) finalTextParts.push(endingText); + + const finalText = finalTextParts.join('\n'); + console.log('🎤 Final TTS text length:', finalText.length); + console.log('🎤 Final TTS text preview:', finalText.substring(0, 200) + '...'); + console.log('🎤 Final TTS text parts count:', finalTextParts.length); + + return finalText; } \ No newline at end of file diff --git a/main.js b/main.js index ed48c03..14c45f4 100644 --- a/main.js +++ b/main.js @@ -14,15 +14,7 @@ import AppLayout from './components/AppLayout/AppLayout.vue'; import Empty from './components/empty/empty.vue'; import NoBouncePage from '@/components/NoBouncePage/NoBouncePage.vue' import MsgTips from '@/components/MsgTips/MsgTips.vue' -import SelectPopup from '@/components/selectPopup/selectPopup.vue' import SelectPopupPlugin from '@/components/selectPopup/selectPopupPlugin'; -import RenderJobs from '@/components/renderJobs/renderJobs.vue'; -import RenderCompanys from '@/components/renderCompanys/renderCompanys.vue'; -import uniIcons from './uni_modules/uni-icons/components/uni-icons/uni-icons.vue' -import uniPopup from './uni_modules/uni-popup/components/uni-popup/uni-popup.vue' -import uniDataSelect from './uni_modules/uni-data-select/components/uni-data-select/uni-data-select.vue' -import uniSwipeAction from './uni_modules/uni-swipe-action/components/uni-swipe-action/uni-swipe-action.vue' -import uniSwipeActionItem from './uni_modules/uni-swipe-action/components/uni-swipe-action-item/uni-swipe-action-item.vue' import storeRc from './utilsRc/store/index.js' import {processFileUrl,} from '@/utilsRc/common.js' // iconfont.css 已在 App.vue 中通过 @import 引入,无需在此处重复引入 @@ -48,14 +40,6 @@ export function createApp() { app.component('Empty', Empty) app.component('NoBouncePage', NoBouncePage) app.component('MsgTips', MsgTips) - app.component('SelectPopup', SelectPopup) - app.component('RenderJobs', RenderJobs) - app.component('RenderCompanys', RenderCompanys) - app.component('uni-icons', uniIcons) - app.component('uni-popup', uniPopup) - app.component('uni-data-select', uniDataSelect) - app.component('uni-swipe-action', uniSwipeAction) - app.component('uni-swipe-action-item', uniSwipeActionItem) app.config.globalProperties.$processFileUrl = processFileUrl; diff --git a/pages/complete-info/company-info.vue b/packageA/pages/complete-info/company-info.vue similarity index 100% rename from pages/complete-info/company-info.vue rename to packageA/pages/complete-info/company-info.vue diff --git a/pages/complete-info/complete-info.vue b/packageA/pages/complete-info/complete-info.vue similarity index 99% rename from pages/complete-info/complete-info.vue rename to packageA/pages/complete-info/complete-info.vue index 88bcf8a..13fdd4c 100644 --- a/pages/complete-info/complete-info.vue +++ b/packageA/pages/complete-info/complete-info.vue @@ -364,7 +364,7 @@ const changeSkillName = (index) => { state.currentEditingSkillIndex = index; // 跳转到技能查询页面 uni.navigateTo({ - url: `/pages/complete-info/skill-search?selected=${encodeURIComponent(JSON.stringify([]))}` + url: `/packageA/pages/complete-info/skill-search?selected=${encodeURIComponent(JSON.stringify([]))}` }); }; diff --git a/pages/complete-info/components/map-location-picker.vue b/packageA/pages/complete-info/components/map-location-picker.vue similarity index 100% rename from pages/complete-info/components/map-location-picker.vue rename to packageA/pages/complete-info/components/map-location-picker.vue diff --git a/pages/complete-info/skill-search.vue b/packageA/pages/complete-info/skill-search.vue similarity index 100% rename from pages/complete-info/skill-search.vue rename to packageA/pages/complete-info/skill-search.vue diff --git a/pages/job/companySearch.vue b/packageA/pages/job/companySearch.vue similarity index 100% rename from pages/job/companySearch.vue rename to packageA/pages/job/companySearch.vue diff --git a/pages/job/publishJob.vue b/packageA/pages/job/publishJob.vue similarity index 100% rename from pages/job/publishJob.vue rename to packageA/pages/job/publishJob.vue diff --git a/pages/nearby/components/four.vue b/packageA/pages/nearby/components/four.vue similarity index 100% rename from pages/nearby/components/four.vue rename to packageA/pages/nearby/components/four.vue diff --git a/pages/nearby/components/one.vue b/packageA/pages/nearby/components/one.vue similarity index 100% rename from pages/nearby/components/one.vue rename to packageA/pages/nearby/components/one.vue diff --git a/pages/nearby/components/three.vue b/packageA/pages/nearby/components/three.vue similarity index 100% rename from pages/nearby/components/three.vue rename to packageA/pages/nearby/components/three.vue diff --git a/pages/nearby/components/two.vue b/packageA/pages/nearby/components/two.vue similarity index 100% rename from pages/nearby/components/two.vue rename to packageA/pages/nearby/components/two.vue diff --git a/pages/nearby/nearby.vue b/packageA/pages/nearby/nearby.vue similarity index 100% rename from pages/nearby/nearby.vue rename to packageA/pages/nearby/nearby.vue diff --git a/packageA/pages/personalInfo/personalInfo.vue b/packageA/pages/personalInfo/personalInfo.vue index 12ed6c8..306b8b7 100644 --- a/packageA/pages/personalInfo/personalInfo.vue +++ b/packageA/pages/personalInfo/personalInfo.vue @@ -472,7 +472,7 @@ function changeSkillName(index) { // 将当前已选中的技能名称传递给查询页面 const selectedSkills = state.skills.map(skill => skill.name).filter(name => name); uni.navigateTo({ - url: `/pages/complete-info/skill-search?selected=${encodeURIComponent(JSON.stringify(selectedSkills))}` + url: `/packageA/pages/complete-info/skill-search?selected=${encodeURIComponent(JSON.stringify(selectedSkills))}` }); } diff --git a/packageRc/pages/jobList/jobList.vue b/packageRc/pages/jobList/jobList.vue index 32bd2ab..54c41ff 100644 --- a/packageRc/pages/jobList/jobList.vue +++ b/packageRc/pages/jobList/jobList.vue @@ -70,7 +70,7 @@ - + @@ -731,7 +731,7 @@ const handleLoginSuccess = () => { // 处理附近工作点击 const handleNearbyClick = () => { if (checkLogin()) { - navTo("/pages/nearby/nearby"); + navTo("/packageA/pages/nearby/nearby"); } }; diff --git a/pages.json b/pages.json index f7fa64d..516b86c 100644 --- a/pages.json +++ b/pages.json @@ -31,50 +31,9 @@ } }, { - "path": "pages/complete-info/complete-info", + "path": "pages/search/search", "style": { - "navigationBarTitleText": "补全信息" - } - }, - { - "path": "pages/complete-info/company-info", - "style": { - "navigationBarTitleText": "企业信息" - } - }, - { - "path": "pages/complete-info/components/map-location-picker", - "style": { - "navigationBarTitleText": "选择地址" - } - }, - { - "path": "pages/complete-info/skill-search", - "style": { - "navigationBarTitleText": "技能查询" - } - }, - { - "path": "pages/nearby/nearby", - "style": { - "navigationBarTitleText": "附近", - "navigationBarBackgroundColor": "#4778EC", - "navigationBarTextStyle": "white" - } - }, - { - "path": "pages/job/publishJob", - "style": { - "navigationBarTitleText": "发布岗位" - } - }, - { - "path": "pages/job/companySearch", - "style": { - "navigationBarTitleText": "选择企业", - "disableScroll": false, - "enablePullDownRefresh": false, - "backgroundColor": "#f5f5f5" + "navigationBarTitleText": "搜索职位" } }, { @@ -86,12 +45,6 @@ "enablePullDownRefresh": false } }, - { - "path": "pages/search/search", - "style": { - "navigationBarTitleText": "搜索职位" - } - }, { "path": "pages/service/career-planning", "style": { @@ -161,6 +114,53 @@ { "root": "packageA", "pages": [ + { + "path": "pages/complete-info/complete-info", + "style": { + "navigationBarTitleText": "补全信息" + } + }, + { + "path": "pages/complete-info/company-info", + "style": { + "navigationBarTitleText": "企业信息" + } + }, + { + "path": "pages/complete-info/components/map-location-picker", + "style": { + "navigationBarTitleText": "选择地址" + } + }, + { + "path": "pages/complete-info/skill-search", + "style": { + "navigationBarTitleText": "技能查询" + } + }, + { + "path": "pages/nearby/nearby", + "style": { + "navigationBarTitleText": "附近", + "navigationBarBackgroundColor": "#4778EC", + "navigationBarTextStyle": "white" + } + }, + { + "path": "pages/job/publishJob", + "style": { + "navigationBarTitleText": "发布岗位" + } + }, + { + "path": "pages/job/companySearch", + "style": { + "navigationBarTitleText": "选择企业", + "disableScroll": false, + "enablePullDownRefresh": false, + "backgroundColor": "#f5f5f5" + } + }, { "path": "pages/addWorkExperience/addWorkExperience", "style": { diff --git a/pages/index/components/index-one.vue b/pages/index/components/index-one.vue index a7aee53..ac83904 100644 --- a/pages/index/components/index-one.vue +++ b/pages/index/components/index-one.vue @@ -197,7 +197,7 @@ - + @@ -744,7 +744,7 @@ onMounted(() => { console.log('收到citySelected事件,选择的城市:', city); selectedCity.value = city; // 可以在这里添加根据城市筛选职位的逻辑 - conditionSearch.value.jobLocationAreaCode = city.code; + conditionSearch.value.regionCode = city.code; getJobRecommend('refresh'); }); @@ -807,13 +807,13 @@ onLoad(() => { const handleNearbyClick = (options ) => { // #ifdef MP-WEIXIN if (checkLogin()) { - navTo('/pages/nearby/nearby'); + navTo('/packageA/pages/nearby/nearby'); } // #endif // #ifdef H5 const token = options.token || uni.getStorageSync('zkr-token'); if (token) { - navTo('/pages/nearby/nearby'); + navTo('/packageA/pages/nearby/nearby'); } // #endif }; diff --git a/pages/mine/mine.vue b/pages/mine/mine.vue index 9ad6cba..e398bd1 100644 --- a/pages/mine/mine.vue +++ b/pages/mine/mine.vue @@ -201,7 +201,7 @@ function seeDetail() { function goToJobHelper() { // 跳转到求职者信息补全页面 - navTo('/pages/complete-info/complete-info'); + navTo('/packageA/pages/complete-info/complete-info'); } // 跳转到素质测评 function goCa(){ diff --git a/utils/streamRequest.js b/utils/streamRequest.js index 7073f97..0b5c529 100644 --- a/utils/streamRequest.js +++ b/utils/streamRequest.js @@ -51,12 +51,101 @@ function StreamRequestMiniProgram(url, data = {}, onDataReceived, onError, onCom } }); - // 监听分块数据 - requestTask.onChunkReceived((res) => { - try { - const decoder = new TextDecoder('utf-8'); - const chunk = decoder.decode(new Uint8Array(res.data)); - console.log('📦 收到分块数据:', chunk); + // UTF-8解码函数,用于微信小程序真机环境 + function utf8Decode(uint8Array) { + let result = ''; + let i = 0; + const len = uint8Array.length; + + while (i < len) { + const byte1 = uint8Array[i]; + + // 1字节字符 (0xxxxxxx) + if (byte1 < 0x80) { + result += String.fromCharCode(byte1); + i++; + } + // 2字节字符 (110xxxxx 10xxxxxx) + else if (byte1 >= 0xC0 && byte1 < 0xE0) { + if (i + 1 < len) { + const byte2 = uint8Array[i + 1]; + if (byte2 >= 0x80 && byte2 < 0xC0) { + const codePoint = ((byte1 & 0x1F) << 6) | (byte2 & 0x3F); + result += String.fromCharCode(codePoint); + i += 2; + continue; + } + } + // 无效的UTF-8序列,跳过 + result += '�'; + i++; + } + // 3字节字符 (1110xxxx 10xxxxxx 10xxxxxx) + else if (byte1 >= 0xE0 && byte1 < 0xF0) { + if (i + 2 < len) { + const byte2 = uint8Array[i + 1]; + const byte3 = uint8Array[i + 2]; + if ((byte2 >= 0x80 && byte2 < 0xC0) && (byte3 >= 0x80 && byte3 < 0xC0)) { + const codePoint = ((byte1 & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F); + result += String.fromCharCode(codePoint); + i += 3; + continue; + } + } + // 无效的UTF-8序列,跳过 + result += '�'; + i++; + } + // 4字节字符 (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx) + else if (byte1 >= 0xF0 && byte1 < 0xF8) { + if (i + 3 < len) { + const byte2 = uint8Array[i + 1]; + const byte3 = uint8Array[i + 2]; + const byte4 = uint8Array[i + 3]; + if ((byte2 >= 0x80 && byte2 < 0xC0) && (byte3 >= 0x80 && byte3 < 0xC0) && (byte4 >= 0x80 && byte4 < 0xC0)) { + let codePoint = ((byte1 & 0x07) << 18) | ((byte2 & 0x3F) << 12) | ((byte3 & 0x3F) << 6) | (byte4 & 0x3F); + // 处理UTF-16代理对 + if (codePoint >= 0x10000) { + codePoint -= 0x10000; + const highSurrogate = (codePoint >> 10) + 0xD800; + const lowSurrogate = (codePoint & 0x3FF) + 0xDC00; + result += String.fromCharCode(highSurrogate, lowSurrogate); + } else { + result += String.fromCharCode(codePoint); + } + i += 4; + continue; + } + } + // 无效的UTF-8序列,跳过 + result += '�'; + i++; + } + // 无效的UTF-8序列,跳过 + else { + result += '�'; + i++; + } + } + + return result; + } + + // 监听分块数据 + requestTask.onChunkReceived((res) => { + try { + // 微信小程序兼容处理:微信小程序不支持TextDecoder,使用自定义UTF-8解码 + let chunk = ''; + if (typeof TextDecoder !== 'undefined') { + // 支持TextDecoder的环境(如开发者工具) + const decoder = new TextDecoder('utf-8'); + chunk = decoder.decode(new Uint8Array(res.data)); + } else { + // 微信小程序真机环境,使用自定义UTF-8解码函数 + const uint8Array = new Uint8Array(res.data); + chunk = utf8Decode(uint8Array); + } + console.log('📦 收到分块数据:', chunk); buffer += chunk; let lines = buffer.split("\n");