From 3d2c26650cffb21c365ec9340276afd550f4cbd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=AF=E8=BE=89?= <13935151924@163.com> Date: Mon, 13 Oct 2025 16:01:49 +0800 Subject: [PATCH] =?UTF-8?q?app=E6=B7=BB=E5=8A=A0=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E7=BB=8F=E5=8E=86=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/md-render/md-render.vue | 40 +- hook/useTTSPlayer.js | 93 ++++- .../addWorkExperience/addWorkExperience.vue | 388 ++++++++++++++++++ packageA/pages/myResume/myResume.vue | 128 ++++-- pages.json | 8 + pages/chat/components/ai-paging.vue | 114 ++++- pages/index/components/index-one.vue | 5 +- pages/login/login.vue | 9 +- stores/useUserStore.js | 21 +- stores/userChatGroupStore.js | 11 +- .../dist/cache/.vite/deps/_metadata.json | 8 +- utils/streamRequest.js | 89 +++- 12 files changed, 840 insertions(+), 74 deletions(-) create mode 100644 packageA/pages/addWorkExperience/addWorkExperience.vue diff --git a/components/md-render/md-render.vue b/components/md-render/md-render.vue index 4bc7762..9d02c3e 100644 --- a/components/md-render/md-render.vue +++ b/components/md-render/md-render.vue @@ -263,15 +263,43 @@ ol { diff --git a/packageA/pages/myResume/myResume.vue b/packageA/pages/myResume/myResume.vue index cdc0c94..ea4507b 100644 --- a/packageA/pages/myResume/myResume.vue +++ b/packageA/pages/myResume/myResume.vue @@ -161,63 +161,111 @@ const { getDictData, oneDictData } = useDictStore(); const isUploading = ref(false); // 上传中状态 const uploadedResumeName = ref(''); // 已上传简历文件名 const uploadedResumeUrl = ref(''); // 已上传 -const workExperiences = ref([ - { - id: 1, // 唯一标识(实际项目用后端返回ID) - companyName: 'XX科技有限公司', // 公司名称 - position: '前端开发工程师', // 职位 - startDate: '2021-07', // 开始日期(格式:YYYY-MM) - endDate: '2023-09', // 结束日期(空/undefined 表示“至今”) - description: - '1. 负责公司小程序及H5页面开发,基于UniApp+Vue3技术栈;2. 优化前端性能,首屏加载时间减少30%;3. 参与封装通用组件库,提升团队开发效率。', // 工作描述 - }, - { - id: 2, - companyName: 'YY互联网公司', - position: '实习前端工程师', - startDate: '2020-12', - endDate: '2021-06', - description: '1. 协助完成后台管理系统页面开发;2. 修复页面兼容性问题,适配多浏览器;3. 整理前端开发文档。', - }, -]); +const workExperiences = ref([]); // 工作经历列表 +const isLoading = ref(false); // 加载状态 -// 页面加载时可从接口拉取数据 +// 获取工作经历列表 +const getWorkExperiences = async () => { + try { + isLoading.value = true; + console.log('完整用户信息:', userInfo.value); + + // 获取用户ID - 使用可选链操作符避免错误 + const userId = userInfo.value?.userId; + console.log('用户ID:', userId); + + if (!userId) { + console.log('用户ID为空,等待用户信息加载...'); + // 如果用户ID为空,不执行任何操作,避免触发退出登录 + return; + } + + // 只传递userId参数 + console.log('请求参数:', { userId }); + + // 使用try-catch包装请求,避免自动退出登录 + try { + // 参数拼接到URL后面 + const resData = await $api.createRequest(`/app/userworkexperiences/list?userId=${userId}`, {}, 'get'); + console.log('工作经历列表响应:', resData); + + if (resData.code === 200 && resData.rows) { + workExperiences.value = resData.rows; + console.log('工作经历数据设置成功:', resData.rows); + console.log('总数量:', resData.total); + } else { + console.log('接口返回非200状态或无数据:', resData); + // 如果接口返回错误,不显示错误提示,避免用户困惑 + workExperiences.value = []; // 设置为空数组 + } + } catch (requestError) { + console.error('接口请求失败:', requestError); + // 接口请求失败时,不显示错误提示,静默处理 + workExperiences.value = []; // 设置为空数组 + } + + } catch (error) { + console.error('获取工作经历失败:', error); + // 静默处理错误,不显示错误提示 + workExperiences.value = []; + } finally { + isLoading.value = false; + } +}; + +// 页面加载时获取数据 onLoad(() => { - // 实际项目中替换为接口请求: - // getWorkExperiences().then(res => { - // workExperiences.value = res.data; - // }).catch(err => { - // showToast({ title: '数据加载失败', icon: 'none' }); - // }); + // 延迟获取数据,确保用户信息完全加载 + setTimeout(() => { + if (userInfo.value?.userId) { + getWorkExperiences(); + } + }, 1000); +}); + +// 页面显示时刷新数据 +onShow(() => { + // 延迟获取数据,确保用户信息完全加载 + setTimeout(() => { + if (userInfo.value?.userId) { + getWorkExperiences(); + } + }, 1000); }); // 整体编辑/添加(跳转编辑页) const handleEditOrAdd = () => { - // 跳转至“批量编辑”或“添加经历”页面(根据实际需求设计编辑页) - // router.push({ - // path: '/pages/workExperience/edit', - // query: { type: workExperiences.value.length > 0 ? 'edit' : 'add' } - // }); + // 跳转到添加经历页面,传递添加标识 + navTo('/packageA/pages/addWorkExperience/addWorkExperience?type=add'); }; // 编辑单个经历 const handleEditItem = (item) => { - // 跳转至单个编辑页,并携带当前经历数据 - // router.push({ - // path: '/pages/workExperience/editItem', - // query: { item: JSON.stringify(item) } // 复杂数据需转JSON字符串(UniApp路由query不支持直接传对象) - // }); + // 跳转到编辑页面,传递编辑标识和数据 + const itemData = encodeURIComponent(JSON.stringify(item)); + navTo(`/packageA/pages/addWorkExperience/addWorkExperience?type=edit&data=${itemData}`); }; // 删除单个经历(带确认弹窗) -const handleDeleteItem = (index) => { +const handleDeleteItem = async (item, index) => { uni.showModal({ title: '确认删除', content: '此操作将删除该工作经历,是否继续?', - success: (res) => { + success: async (res) => { if (res.confirm) { - workExperiences.value.splice(index, 1); // 删除本地数据 - $api.msg('删除成功'); + try { + // 调用删除接口 + const deleteRes = await $api.createRequest(`/cms/userworkexperiences/${item.id}`, {}, 'delete'); + if (deleteRes.code === 200) { + workExperiences.value.splice(index, 1); // 删除本地数据 + $api.msg('删除成功'); + } else { + $api.msg(deleteRes.msg || '删除失败'); + } + } catch (error) { + console.error('删除工作经历失败:', error); + $api.msg('删除失败,请重试'); + } } }, }); diff --git a/pages.json b/pages.json index 6b48c5b..d1ba1d1 100644 --- a/pages.json +++ b/pages.json @@ -65,6 +65,14 @@ "navigationBarTitleText": "", "navigationStyle": "custom" } + }, + { + "path" : "packageA/pages/addWorkExperience/addWorkExperience", + "style" : + { + "navigationBarTitleText" : "添加工作经历", + "navigationStyle": "custom" + } } ], diff --git a/pages/chat/components/ai-paging.vue b/pages/chat/components/ai-paging.vue index d08a0ae..4621a24 100644 --- a/pages/chat/components/ai-paging.vue +++ b/pages/chat/components/ai-paging.vue @@ -370,7 +370,57 @@ const sendMessage = (text) => { filesList.value = []; useChatGroupDBStore() .getStearm(values, normalArr, scrollToBottom, { - onComplete: () => console.log('Display complete'), + onDataReceived: (data, message, index) => { + // 流式朗读:只在内容足够长且包含完整信息时才开始朗读 + if (!message.self && message.displayText && message.displayText.trim()) { + // 检查是否已经开始朗读这条消息 + if (speechIndex.value !== index) { + // 延迟TTS,等待内容更完整 + // 只有在内容长度超过50个字符或者包含岗位信息时才开始朗读 + const hasJobInfo = message.displayText.includes('```job-json') || + message.displayText.includes('岗位') || + message.displayText.includes('公司') || + message.displayText.includes('薪资'); + + if (message.displayText.length > 50 || hasJobInfo) { + console.log('🎵 Starting streaming TTS for message index:', index); + console.log('📝 Current text length:', message.displayText.length); + console.log('📝 Has job info:', hasJobInfo); + + // 开始朗读当前消息 + speechIndex.value = index; + readMarkdown(message.displayText, index); + } else { + console.log('⏳ Waiting for more content before TTS, current length:', message.displayText.length); + } + } + } + }, + onComplete: () => { + console.log('🎯 onComplete callback triggered'); + console.log('📊 Messages array length:', messages.value.length); + + // 确保最后一条AI消息的朗读完成 + const lastMessageIndex = messages.value.length - 1; + if (lastMessageIndex >= 0) { + const lastMessage = messages.value[lastMessageIndex]; + if (!lastMessage.self && lastMessage.displayText && lastMessage.displayText.trim()) { + console.log('🎵 Final TTS for complete message'); + console.log('📝 Final text length:', lastMessage.displayText.length); + console.log('📝 Final text preview:', lastMessage.displayText.substring(0, 100) + '...'); + + // 停止当前的朗读(如果有的话) + if (isSpeaking.value) { + console.log('🛑 Stopping current TTS to start final complete TTS'); + cancelAudio(); + } + + // 开始朗读完整的内容 + speechIndex.value = lastMessageIndex; + readMarkdown(lastMessage.displayText, lastMessageIndex); + } + } + }, }) .then(() => { getGuess(); @@ -620,21 +670,71 @@ function confirmFeeBack(value) { }); } +// 防抖定时器 +let ttsDebounceTimer = null; + function readMarkdown(value, index) { - speechIndex.value = index; - if (speechIndex.value !== index) { + console.log('🎤 readMarkdown called'); + console.log('📝 Text to speak:', value ? value.substring(0, 100) + '...' : 'No text'); + console.log('🔢 Index:', index); + console.log('🔢 Current speechIndex:', speechIndex.value); + console.log('⏸️ Is paused:', isPaused.value); + console.log('🔊 Is speaking:', isSpeaking.value); + + // 清除之前的防抖定时器 + if (ttsDebounceTimer) { + clearTimeout(ttsDebounceTimer); + } + + // 如果当前正在播放其他消息,先停止 + if (speechIndex.value !== index && speechIndex.value !== 0) { + console.log('🛑 Stopping current speech and starting new one'); + speechIndex.value = index; speak(value); return; } - if (isPaused.value) { + + speechIndex.value = index; + + // 如果当前正在播放且暂停了,直接恢复 + if (isPaused.value && isSpeaking.value) { + console.log('▶️ Resuming paused speech'); resume(); - } else { - speak(value); + return; } + + // 如果当前正在播放且没有暂停,不需要重新开始 + if (isSpeaking.value && !isPaused.value) { + console.log('🔊 Already speaking, no need to restart'); + return; + } + + // 使用防抖,避免频繁调用TTS + ttsDebounceTimer = setTimeout(() => { + console.log('🎵 Starting new speech'); + console.log('🎵 Calling speak function with text length:', value ? value.length : 0); + try { + speak(value); + console.log('✅ Speak function called successfully'); + } catch (error) { + console.error('❌ Error calling speak function:', error); + } + }, 300); // 300ms防抖延迟 } function stopMarkdown(value, index) { - pause(value); + console.log('⏸️ stopMarkdown called for index:', index); + console.log('🔢 Current speechIndex:', speechIndex.value); + console.log('🔊 Is speaking:', isSpeaking.value); + console.log('⏸️ Is paused:', isPaused.value); + + // 清除防抖定时器 + if (ttsDebounceTimer) { + clearTimeout(ttsDebounceTimer); + ttsDebounceTimer = null; + } + speechIndex.value = index; + pause(); } function refreshMarkdown(index) { if (isTyping.value) { diff --git a/pages/index/components/index-one.vue b/pages/index/components/index-one.vue index 9948e24..987f723 100644 --- a/pages/index/components/index-one.vue +++ b/pages/index/components/index-one.vue @@ -96,7 +96,8 @@ - {{ config.appInfo.areaName }} + + @@ -341,7 +342,9 @@ function choosePosition(index) { } function handelHostestSearch(val) { + console.log(val.value); pageState.search.order = val.value; + pageState.search.jobType = val.value === 3 ? 1 : 0; if (state.tabIndex === 'all') { getJobRecommend('refresh'); } else { diff --git a/pages/login/login.vue b/pages/login/login.vue index f9593ec..3e0f4c0 100644 --- a/pages/login/login.vue +++ b/pages/login/login.vue @@ -288,9 +288,12 @@ function complete() { if (result.valid) { $api.createRequest('/app/user/resume', fromValue, 'post').then((resData) => { $api.msg('完成'); - getUserResume(); - uni.reLaunch({ - url: '/pages/index/index', + // 获取用户信息并存储到store中 + getUserResume().then((userInfo) => { + console.log('用户信息已存储到store:', userInfo); + uni.reLaunch({ + url: '/pages/index/index', + }); }); }); } else { diff --git a/stores/useUserStore.js b/stores/useUserStore.js index c76adc0..882f15b 100644 --- a/stores/useUserStore.js +++ b/stores/useUserStore.js @@ -108,8 +108,11 @@ const useUserStore = defineStore("user", () => { const setUserInfo = (values) => { userInfo.value = values.data; + resume.value = values.data; // 将用户信息同时存储到resume中 // role.value = values.role; hasLogin.value = true; + // 持久化存储用户信息到本地缓存 + uni.setStorageSync('userInfo', values.data); } @@ -127,6 +130,21 @@ const useUserStore = defineStore("user", () => { seesionId.value = seesionIdVal } + // 从本地缓存恢复用户信息 + const restoreUserInfo = () => { + const cachedUserInfo = uni.getStorageSync('userInfo'); + const cachedToken = uni.getStorageSync('token'); + if (cachedUserInfo && cachedToken) { + userInfo.value = cachedUserInfo; + resume.value = cachedUserInfo; + token.value = cachedToken; + hasLogin.value = true; + Completion.value = getResumeCompletionPercentage(cachedUserInfo); + return true; + } + return false; + } + // 导入 return { hasLogin, @@ -139,7 +157,8 @@ const useUserStore = defineStore("user", () => { getUserResume, initSeesionId, seesionId, - Completion + Completion, + restoreUserInfo } }) diff --git a/stores/userChatGroupStore.js b/stores/userChatGroupStore.js index 8b688fe..51c9a8b 100644 --- a/stores/userChatGroupStore.js +++ b/stores/userChatGroupStore.js @@ -107,7 +107,7 @@ const useChatGroupDBStore = defineStore("messageGroup", () => { return await baseDB.db.add(massageName.value, payload); } - async function getStearm(text, fileUrls = [], progress) { + async function getStearm(text, fileUrls = [], progress, options = {}) { return new Promise((resolve, reject) => { try { @@ -163,6 +163,11 @@ const useChatGroupDBStore = defineStore("messageGroup", () => { ...newMsg }; progress && progress(); + + // 调用外部传入的onDataReceived回调 + if (options.onDataReceived) { + options.onDataReceived(data, newMsg, index); + } } function onError(error) { @@ -177,6 +182,10 @@ const useChatGroupDBStore = defineStore("messageGroup", () => { toggleTyping(false); window.removeEventListener("unload", handleUnload); handleUnload(); + // 调用外部传入的onComplete回调 + if (options.onComplete) { + options.onComplete(); + } resolve(); } diff --git a/unpackage/dist/cache/.vite/deps/_metadata.json b/unpackage/dist/cache/.vite/deps/_metadata.json index 55b3d2d..1b39def 100644 --- a/unpackage/dist/cache/.vite/deps/_metadata.json +++ b/unpackage/dist/cache/.vite/deps/_metadata.json @@ -1,8 +1,8 @@ { - "hash": "64b0126f", - "configHash": "c3ada311", - "lockfileHash": "5d26acb0", - "browserHash": "6c46b053", + "hash": "6baa819c", + "configHash": "7c25a4d3", + "lockfileHash": "e3b0c442", + "browserHash": "1418816b", "optimized": {}, "chunks": {} } \ No newline at end of file diff --git a/utils/streamRequest.js b/utils/streamRequest.js index e6cb6bf..6a5471d 100644 --- a/utils/streamRequest.js +++ b/utils/streamRequest.js @@ -31,13 +31,19 @@ export default function StreamRequest(url, data = {}, onDataReceived, onError, o const decoder = new TextDecoder("utf-8"); let buffer = ""; + let retryCount = 0; + const maxRetries = 3; + while (true) { const { done, value } = await reader.read(); - if (done) break; + if (done) { + console.log("📡 Stream reading completed"); + break; + } buffer += decoder.decode(value, { stream: true @@ -45,6 +51,9 @@ export default function StreamRequest(url, data = {}, onDataReceived, onError, o let lines = buffer.split("\n"); buffer = lines.pop(); // 可能是不完整的 JSON 片段,留待下次解析 + + console.log(`📦 Processing ${lines.length} lines, buffer length: ${buffer.length}`); + for (let line of lines) { if (line.startsWith("data: ")) { const jsonData = line.slice(6).trim(); @@ -55,20 +64,84 @@ export default function StreamRequest(url, data = {}, onDataReceived, onError, o } try { - const parsedData = JSON.parse(jsonData); - const content = parsedData?.choices?.[0]?.delta?.content ?? - parsedData?.choices?.[0]?.delta?.reasoning_content ?? - ""; - if (content) { - onDataReceived && onDataReceived(content); + // 检查JSON数据是否完整 + if (jsonData && jsonData.trim() && jsonData !== "[DONE]") { + const parsedData = JSON.parse(jsonData); + + // 处理标准的choices格式 + if (parsedData?.choices?.[0]?.delta?.content) { + const content = parsedData.choices[0].delta.content; + if (content) { + onDataReceived && onDataReceived(content); + } + } + // 处理reasoning_content + else if (parsedData?.choices?.[0]?.delta?.reasoning_content) { + const content = parsedData.choices[0].delta.reasoning_content; + if (content) { + onDataReceived && onDataReceived(content); + } + } + // 处理tool响应 + else if (parsedData?.tool?.response) { + const content = parsedData.tool.response; + if (content) { + onDataReceived && onDataReceived(content); + } + } + // 处理其他格式的数据(如jobs_array_number, durationSeconds等) + else { + console.log("📦 收到非内容数据:", Object.keys(parsedData)); + } } } catch (e) { - console.error("JSON 解析失败:", e, "原始数据:", jsonData); + console.error("JSON 解析失败:", e.message, "原始数据长度:", jsonData.length, "数据预览:", jsonData.substring(0, 100) + "..."); + // 不抛出错误,继续处理下一个数据块 } } } } + // 处理剩余的缓冲区数据 + if (buffer.trim()) { + console.log("📦 Processing remaining buffer:", buffer.substring(0, 100) + "..."); + const lines = buffer.split("\n"); + for (let line of lines) { + if (line.startsWith("data: ")) { + const jsonData = line.slice(6).trim(); + if (jsonData && jsonData !== "[DONE]") { + try { + const parsedData = JSON.parse(jsonData); + + // 处理标准的choices格式 + if (parsedData?.choices?.[0]?.delta?.content) { + const content = parsedData.choices[0].delta.content; + if (content) { + onDataReceived && onDataReceived(content); + } + } + // 处理reasoning_content + else if (parsedData?.choices?.[0]?.delta?.reasoning_content) { + const content = parsedData.choices[0].delta.reasoning_content; + if (content) { + onDataReceived && onDataReceived(content); + } + } + // 处理tool响应 + else if (parsedData?.tool?.response) { + const content = parsedData.tool.response; + if (content) { + onDataReceived && onDataReceived(content); + } + } + } catch (e) { + console.warn("处理剩余数据时JSON解析失败:", e.message); + } + } + } + } + } + onComplete && onComplete(); resolve(); } catch (error) {