const _app = getApp() // 办公助手服务配置 const ServiceConfig = { // 样例API地址,请替换为您的真实服务地址 apiUrl: 'https://kt.colexiu.com/edu-app/open/coze/agent', // 请求超时时间(毫秒) timeout: 60000, // 请求头配置 headers: { 'Content-Type': 'application/json' } } // 用户信息管理 const UserManager = { // 获取用户唯一标识 getUserIdentifier: function () { return new Promise((resolve, _reject) => { // 尝试从缓存获取用户ID const cachedUserId = tt.getStorageSync('user_id') if (cachedUserId) { resolve(cachedUserId) return } // 调用登录API获取用户标识 tt.login({ force: false, // 不强制调起登录框 success: function (res) { console.log('登录成功:', res) if (res.isLogin && res.code) { // 已登录用户,使用code作为临时标识 // 实际应用中,应该将code发送到服务端换取openid const userId = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9) // 缓存用户ID tt.setStorageSync('user_id', userId) resolve(userId) } else if (res.anonymousCode) { // 匿名用户,使用anonymousCode作为标识 const anonymousUserId = 'anonymous_' + res.anonymousCode.substr(0, 16) // 缓存匿名用户ID tt.setStorageSync('user_id', anonymousUserId) resolve(anonymousUserId) } else { // 生成默认用户ID const defaultUserId = 'default_' + Date.now() tt.setStorageSync('user_id', defaultUserId) resolve(defaultUserId) } }, fail: function (err) { console.error('登录失败:', err) // 生成默认用户ID作为备选 const defaultUserId = 'default_' + Date.now() tt.setStorageSync('user_id', defaultUserId) resolve(defaultUserId) } }) }) }, // 清除用户信息 clearUserInfo: function () { tt.removeStorageSync('user_id') } } Page({ data: { appName: '酷乐秀', version: '1.0.0', messageContent: '', showReply: false, replyContent: '', isLoading: false, apiError: false, currentUserId: '', chatMessages: [], // 聊天消息列表(按时间顺序) scrollTop: 0, // 滚动位置 scrollIntoView: '' // 滚动锚点 }, onLoad: function () { console.log('办公效率助手小程序已加载') this.setData({ appName: '酷乐秀', version: '1.0.0' }) // 初始化用户标识 this.initUserIdentifier() }, // 初始化用户标识 initUserIdentifier: function () { const that = this UserManager.getUserIdentifier() .then(userId => { console.log('获取到用户ID:', userId) that.setData({ currentUserId: userId }) }) .catch(err => { console.error('获取用户ID失败:', err) // 生成默认用户ID const defaultUserId = 'default_' + Date.now() that.setData({ currentUserId: defaultUserId }) tt.setStorageSync('user_id', defaultUserId) }) }, onShow: function () { console.log('办公效率助手小程序页面显示') }, // 页面初次渲染完成 onReady: function () { console.log('页面初次渲染完成') // 可以在这里添加需要在页面渲染完成后执行的逻辑 }, // 点击发送消息按钮(兼容两种方法名) handleSendMessage: function () { this.handleSendPrivateMessage() }, // 点击发送消息按钮 handleSendPrivateMessage: function () { const content = this.data.messageContent.trim() if (!content) { tt.showToast({ title: '请输入消息内容', icon: 'none', duration: 2000 }) return } // 将用户消息添加到聊天记录 const userMessage = { type: 'user', content: content, time: this.getCurrentTime(), timestamp: Date.now() } this.setData({ chatMessages: [...this.data.chatMessages, userMessage], isLoading: true, apiError: false, messageContent: '' }, () => { // 滚动到底部 this.scrollToBottom() }) // 调用助手服务 this.callReplyService(content) }, // 调用助手服务 callReplyService: function (userMessage) { const that = this // 获取用户ID并调用服务 UserManager.getUserIdentifier() .then(userId => { console.log('调用助手服务,用户ID:', userId) // 更新当前用户ID that.setData({ currentUserId: userId }) // 发起服务请求 tt.request({ url: ServiceConfig.apiUrl, method: 'POST', data: { message: userMessage, userId: userId, // 添加用户ID参数 appId: 'yyszkt_help', timestamp: Date.now(), // 可以根据需要添加更多参数 context: 'music_assistant', // 保持接口参数不变 language: 'zh-CN', userType: userId.startsWith('anonymous_') ? 'anonymous' : 'registered' }, header: ServiceConfig.headers, timeout: ServiceConfig.timeout, success: function (res) { console.log('服务调用成功:', res) // 处理助手回复 let assistantReply = '' if (res.statusCode === 200 && res.data) { // 根据API响应格式处理回复 if (typeof res.data === 'string') { assistantReply = res.data } else if (res.data.reply) { assistantReply = res.data.reply } else if (res.data.choices && res.data.choices[0] && res.data.choices[0].message) { assistantReply = res.data.choices[0].message.content } else { assistantReply = '已收到您的消息,我这边正在为您整理建议。' } } else { // API调用失败,使用备用回复 assistantReply = that.generateFallbackResponse(userMessage) } // 将助手回复添加到聊天记录 const assistantReplyMessage = { type: 'assistant', content: assistantReply, time: that.getCurrentTime(), timestamp: Date.now() } that.setData({ chatMessages: [...that.data.chatMessages, assistantReplyMessage], replyContent: assistantReply, showReply: true, isLoading: false }, () => { // 滚动到底部 that.scrollToBottom() }) // 清空输入框 that.setData({ messageContent: '' }) }, fail: function (err) { console.error('服务调用失败:', err) // 显示错误提示 tt.showToast({ title: '网络异常,使用本地建议', icon: 'none', duration: 2000 }) // 使用本地备用建议 const fallbackReply = that.generateFallbackResponse(userMessage) that.setData({ replyContent: fallbackReply, showReply: true, isLoading: false, apiError: true }) // 清空输入框 that.setData({ messageContent: '' }) }, complete: function () { console.log('服务调用完成') } }) }) .catch(err => { console.error('获取用户ID失败:', err) // 使用默认用户ID继续调用 const defaultUserId = 'default_' + Date.now() that.callReplyServiceWithUserId(userMessage, defaultUserId) }) }, // 使用指定用户ID调用服务 callReplyServiceWithUserId: function (userMessage, userId) { const that = this tt.request({ url: ServiceConfig.apiUrl, method: 'POST', data: { message: userMessage, userId: userId, appId: 'yyszkt_help', timestamp: Date.now(), context: 'music_assistant', language: 'zh-CN', userType: userId.startsWith('anonymous_') ? 'anonymous' : 'registered' }, header: ServiceConfig.headers, timeout: ServiceConfig.timeout, success: function (res) { // 成功处理逻辑... let assistantReply = '' if (res.statusCode === 200 && res.data) { if (typeof res.data === 'string') { assistantReply = res.data } else if (res.data.reply) { assistantReply = res.data.reply } else if (res.data.choices && res.data.choices[0] && res.data.choices[0].message) { assistantReply = res.data.choices[0].message.content } else { assistantReply = '已收到您的消息,我这边正在为您整理建议。' } } else { assistantReply = that.generateFallbackResponse(userMessage) } // 将助手回复添加到聊天记录 const assistantReplyMessage = { type: 'assistant', content: assistantReply, time: that.getCurrentTime(), timestamp: Date.now() } that.setData({ chatMessages: [...that.data.chatMessages, assistantReplyMessage], replyContent: assistantReply, showReply: true, isLoading: false }, () => { // 滚动到底部 that.scrollToBottom() }) that.setData({ messageContent: '' }) }, fail: function (err) { console.error('服务调用失败:', err) tt.showToast({ title: '网络异常,使用本地建议', icon: 'none', duration: 2000 }) const fallbackReply = that.generateFallbackResponse(userMessage) // 将助手回复添加到聊天记录 const assistantReplyMessage = { type: 'assistant', content: fallbackReply, time: that.getCurrentTime(), timestamp: Date.now() } that.setData({ chatMessages: [...that.data.chatMessages, assistantReplyMessage], replyContent: fallbackReply, showReply: true, isLoading: false, apiError: true }, () => { // 滚动到底部 that.scrollToBottom() }) that.setData({ messageContent: '' }) }, complete: function () { } }) }, // 生成备用回复(当API调用失败时使用) generateFallbackResponse: function (userMessage) { return this.generateReplyResponse(userMessage) }, // 生成助手回复内容(本地备用) generateReplyResponse: function (userMessage) { return '当前客服不在线,暂无法回复消息,请稍等。' }, // 关闭助手回复区域 handleCloseReply: function () { this.setData({ showReply: false, replyContent: '', apiError: false }) }, // 重新获取用户ID(用于调试或重置) refreshUserIdentifier: function () { const that = this UserManager.clearUserInfo() tt.showLoading({ title: '重新获取用户信息...', mask: true }) UserManager.getUserIdentifier() .then(userId => { tt.hideLoading() that.setData({ currentUserId: userId }) tt.showToast({ title: '用户信息更新成功', icon: 'success', duration: 2000 }) }) .catch(_err => { tt.hideLoading() tt.showToast({ title: '用户信息更新失败', icon: 'none', duration: 2000 }) }) }, // 输入框内容变化 handleInputChange: function (e) { this.setData({ messageContent: e.detail.value }) }, // 跳转产品介绍 handleGoProduct: function () { tt.redirectTo({ url: '/pages/product/index' }) }, // 跳转申请试用 handleGoTrial: function () { tt.redirectTo({ url: '/pages/trial/index' }) }, // 获取当前时间 getCurrentTime: function () { const now = new Date() const hours = now.getHours().toString().padStart(2, '0') const minutes = now.getMinutes().toString().padStart(2, '0') return `${hours}:${minutes}` }, // 滚动到底部 - 完全重写的专业级实现 scrollToBottom: function () { this.setData({ scrollIntoView: '' }) setTimeout(() => { this.setData({ scrollIntoView: 'bottom-anchor' }) }, 20) setTimeout(() => { this.setData({ scrollTop: this.data.scrollTop + 9999 }) }, 120) setTimeout(() => { this.ensureLastMessageInView(0) }, 180) }, // 确保最后一条消息完整显示在消息区可视范围内 ensureLastMessageInView: function (retryCount) { const MAX_RETRY = 5 if (retryCount >= MAX_RETRY) { return } const query = tt.createSelectorQuery() query.selectAll('.message-item').boundingClientRect() query.select('.chat-container').boundingClientRect() query.exec((res) => { const messageRects = res[0] const chatRect = res[1] if (!messageRects || !messageRects.length || !chatRect) { return } const lastMessageRect = messageRects[messageRects.length - 1] const safeBottom = chatRect.bottom - 16 const overlapHeight = lastMessageRect.bottom - safeBottom if (overlapHeight > 0) { this.setData({ scrollIntoView: '', scrollTop: this.data.scrollTop + overlapHeight + 24 }) setTimeout(() => { this.ensureLastMessageInView(retryCount + 1) }, 60) } }) }, })