List-View Messages

本文详细列出了ListView控件的所有窗口消息,并指明了每个消息在不同COMCTL32版本中的适用情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

List-View Messages

The following table shows all the window messages that seem ever to have been defined specifically for List-View controls. For each message, a quick attempt has been made to determine the range of applicable COMCTL32 versions.

The description of the early history is complicated by the separate treatment of builds for Windows and NT. Some messages that date from a version 4.00 supplied with Internet Explorer 3.00 for Windows NT are not found in the version 4.00 from the original Windows 95, but become common to both Windows and NT builds of version 4.70 and higher. Builds of COMCTL32 for Windows, as opposed to NT, were for many years not just natively ANSI but exclusively ANSI, so that many messages have both ANSI and Unicode forms as early as version 3.51 for NT but do not have Unicode forms in the Windows builds until version 5.80.

More than a handful of messages, especially among recent additions, are highlighted. These are not documented in the January 2007 edition of the Software Development Kit (SDK) for Windows Vista, nor are they defined in the COMMCTRL.H header from that SDK. The names given here for these messages are invented, Microsoft’s names being not known.

Message Symbolic Name Applicable Versions Remarks
0x1000LVM_GETBKCOLOR3.50 and higherreturns indirect result of GetBackgroundColor method
0x1001LVM_SETBKCOLOR3.50 and higherpasses lParam to SetBackgroundColor method; 
returns TRUE for success, else FALSE
0x1002LVM_GETIMAGELIST3.50 and higher 
0x1003LVM_SETIMAGELIST3.50 and higher 
0x1004LVM_GETITEMCOUNT3.50 and higherreturns indirect result of GetItemCount method
0x1005LVM_GETITEMA3.50 and higher 
0x1006LVM_SETITEMA3.50 and higher 
0x1007LVM_INSERTITEMA3.50 and higher 
0x1008LVM_DELETEITEM3.50 and higherpasses wParam to DeleteItem method; 
returns TRUE for success, else FALSE
0x1009LVM_DELETEALLITEMS3.50 and highercalls DeleteAllItems method; 
returns TRUE for success, else FALSE
0x100ALVM_GETCALLBACKMASK3.50 and higherreturns indirect result of GetCallbackMask method
0x100BLVM_SETCALLBACKMASK3.50 and higherpasses wParam to SetCallbackMask method; 
returns TRUE
0x100CLVM_GETNEXTITEM3.50 and highertranslates to GetNextItem method
0x100DLVM_FINDITEMA3.50 and higher 
0x100ELVM_GETITEMRECT3.50 and highertranslates to GetItemRect method
0x100FLVM_SETITEMPOSITION3.50 and higher 
0x1010LVM_GETITEMPOSITION3.50 and higher 
0x1011LVM_GETSTRINGWIDTHA3.50 and higher 
0x1012LVM_HITTEST3.50 and higher 
0x1013LVM_ENSUREVISIBLE3.50 and highertranslates to EnsureItemVisible method
0x1014LVM_SCROLL3.50 and higherpasses wParam and lParam to ScrollView method; 
returns TRUE for success, else FALSE
0x1015LVM_REDRAWITEMS3.50 and higherpasses wParam and lParam to RedrawItems method; 
returns TRUE for success, else FALSE
0x1016LVM_ARRANGE3.50 and higherpasses wParam to ArrangeItems method; 
returns TRUE for success, else FALSE
0x1017LVM_EDITLABELA3.50 and higher 
0x1018LVM_GETEDITCONTROL3.50 and higherreturns indirect result of GetEditControl method
0x1019LVM_GETCOLUMNA3.50 and higher 
0x101ALVM_SETCOLUMNA3.50 and higher 
0x101BLVM_INSERTCOLUMNA3.50 and higher 
0x101CLVM_DELETECOLUMN3.50 and higherpasses wParam to DeleteColumn method; 
returns TRUE for success, else FALSE
0x101DLVM_GETCOLUMNWIDTH3.50 and higher 
0x101ELVM_SETCOLUMNWIDTH3.50 and higherpasses wParam and lParam (sign-extended from 16 bits) to SetColumnWidth method; 
returns TRUE for success, else FALSE
0x101FLVM_GETHEADER4.70 and higher 
0x1021LVM_CREATEDRAGIMAGE3.50 and higherpasses wParam and lParam to CreateDragImage method; 
returns indirect result
0x1022LVM_GETVIEWRECT3.50 and higherpasses lParam to GetViewRect method; 
returns TRUE for success, else FALSE
0x1023LVM_GETTEXTCOLOR3.50 and higherreturns indirect result of GetTextColor method
0x1024LVM_SETTEXTCOLOR3.50 and higherpasses lParam to SetTextColor method
0x1025LVM_GETTEXTBKCOLOR3.50 and higherreturns indirect result of GetTextBackgroundColor method
0x1026LVM_SETTEXTBKCOLOR3.50 and higherpasses lParam to SetTextBackgroundColor method; 
returns TRUE
0x1027LVM_GETTOPINDEX3.50 and higherreturns indirect result of GetTopIndex method
0x1028LVM_GETCOUNTPERPAGE3.50 and higherreturns indirect result of GetCountPerPage method
0x1029LVM_GETORIGIN3.50 and higherpasses lParam to GetOrigin method; 
returns TRUE for success, else FALSE
0x102ALVM_UPDATE3.50 and higherpasses wParam to UpdateItem method; 
returns TRUE
0x102BLVM_SETITEMSTATE3.50 and highertranslates to SetItemState method
0x102CLVM_GETITEMSTATE3.50 and highertranslates to GetItemState method
0x102DLVM_GETITEMTEXTA3.50 and higher 
0x102ELVM_SETITEMTEXTA3.50 and higher 
0x102FLVM_SETITEMCOUNT3.50 and higherpasses wParam and lParam to SetItemCount method; 
returns TRUE for success, else FALSE
0x1030LVM_SORTITEMS3.50 and higherpasses FALSE, wParam and lParam to SortItems method; 
returns TRUE for success, else FALSE
0x1031LVM_SETITEMPOSITION323.50 and higherpasses wParam and lParam to SetItemPosition method; 
returns TRUE for success, else FALSE; 
fails trivially if lParam is NULL
0x1032LVM_GETSELECTEDCOUNT3.50 and higherreturns indirect result of GetSelectedCount method
0x1033LVM_GETITEMSPACING3.50 and higherpasses wParam to GetItemSpacing method; 
returns indirect results composed into one DWORD
0x1034LVM_GETISEARCHSTRINGA3.50 and higher 
0x1035LVM_SETICONSPACING4.00 (NT) and higherpasses 16-bit components of lParam to SetIconSpacing method; 
returns indirect results composed into one DWORD
0x1036LVM_SETEXTENDEDLISTVIEWSTYLE4.00 (NT) and higherpasses wParam and lParam to SetExtendedStyle method; 
returns indirect result
0x1037LVM_GETEXTENDEDLISTVIEWSTYLE4.00 (NT) and higherreturns indirect result of GetExtendedStyle method
0x1038LVM_GETSUBITEMRECT4.00 (NT) and highertranslates to GetSubItemRect method
0x1039LVM_SUBITEMHITTEST4.00 (NT) and higher 
0x103ALVM_SETCOLUMNORDERARRAY4.00 (NT) and higherpasses wParam and lParam to SetColumnOrderArray method; 
returns TRUE for success, else FALSE
0x103BLVM_GETCOLUMNORDERARRAY4.00 (NT) and higherpasses wParam and lParam to GetColumnOrderArray method; 
returns TRUE for success, else FALSE
0x103CLVM_SETHOTITEM4.70 and highertranslates to SetHotItem method
0x103DLVM_GETHOTITEM4.70 and higherreturns iItem member from indirect result of GetHotItem method
0x103ELVM_SETHOTCURSOR4.70 and higherpasses lParam to SetHotCursor method; 
returns indirect result
0x103FLVM_GETHOTCURSOR4.70 and higherreturns indirect result of GetHotCursor method
0x1040LVM_APPROXIMATEVIEWRECT4.70 and highertranslates to ApproximateViewRect method
0x1041LVM_SETWORKAREAS4.70 and higherpasses wParam and lParam to SetWorkAreas method; 
returns zero
0x1042LVM_GETSELECTIONMARK4.71 and higherreturns iItem member from indirect result of GetSelectionMark method
0x1043LVM_SETSELECTIONMARK4.71 and highertranslates to SetSelectionMark method
0x1044LVM_SETBKIMAGEA4.71 and higher 
0x1045LVM_GETBKIMAGEA4.71 and higher 
0x1046LVM_GETWORKAREAS4.71 and higherpasses wParam and lParam to GetWorkAreas method; 
returns zero
0x1047LVM_SETHOVERTIME4.71 and higherpasses lParam to SetHoverTime method; 
returns indirect result
0x1048LVM_GETHOVERTIME4.71 and higherreturns indirect result of GetHoverTime method
0x1049LVM_GETNUMBEROFWORKAREAS4.71 and higherpasses lParam to GetWorkAreaCount method; 
returns zero
0x104ALVM_SETTOOLTIPS4.71 and higherpasses wParam to SetToolTip method; 
returns indirect result
0x104BLVM_GETITEMW3.51 and higher (NT); 
5.80 and higher (Windows)
passes lParam to GetItem method; 
returns TRUE for success, else FALSE
0x104CLVM_SETITEMW3.51 and higher (NT); 
5.80 and higher (Windows)
passes lParam to SetItem method; 
returns TRUE for success, else FALSE
0x104DLVM_INSERTITEMW3.51 and higher (NT); 
5.80 and higher (Windows)
passes lParam to InsertItem method; 
returns indirect result
0x104ELVM_GETTOOLTIPS4.71 and higherreturns indirect result of GetToolTip method
0x104FLVM_GETHOTLIGHTCOLOR4.71 and higherreturns indirect result of GetHotLightColor method
0x1050LVM_SETHOTLIGHTCOLOR4.71 and higherpasses lParam to SetHotLightColor method; 
returns TRUE
0x1051LVM_SORTITEMSEX5.80 and higherpasses TRUE, wParam and lParam to SortItems method; 
returns TRUE for success, else FALSE
0x1052LVM_SETRANGEOBJECT5.80 and higherpasses wParam and lParam to SetRangeObject method; 
returns TRUE for success, else FALSE
0x1053LVM_FINDITEMW3.51 and higher (NT); 
5.80 and higher (Windows)
translates to FindItem method
0x1054LVM_RESETEMPTYTEXT5.80 and highercalls ResetEmptyText method; 
returns TRUE
0x1055LVM_SETFROZENITEM6.00 and higherpasses wParam and lParam to SetFrozenItem method; 
returns TRUE for success, else FALSE
0x1056LVM_GETFROZENITEM6.00 and higherreturns indirect result of GetFrozenItem method
0x1057LVM_GETSTRINGWIDTHW3.51 and higher (NT); 
5.80 and higher (Windows)
passes lParam to GetStringWidth method; 
returns indirect result
0x1058LVM_SETFROZENSLOT6.00 and higherpasses wParam and lParam to SetFrozenSlot method; 
returns TRUE for success, else FALSE
0x1059LVM_GETFROZENSLOT6.00 and higherpasses lParam to GetFrozenSlot method; 
returns TRUE for success, else FALSE
0x105ALVM_SETVIEWMARGIN6.00 and higherpasses lParam to SetViewMargin method; 
returns TRUE for success, else FALSE
0x105BLVM_GETVIEWMARGIN6.00 and higherpasses lParam to GetViewMargin method; 
returns TRUE for success, else FALSE
0x105CLVM_GETGROUPSTATE6.10 and higherpasses wParam and lParam to GetGroupState method; 
returns indirect result
0x105DLVM_GETFOCUSEDGROUP6.10 and higherreturns indirect result of GetFocusedGroup method
0x105ELVM_EDITGROUPLABEL6.10 and higherpasses wParam to EditGroupLabel method; 
returns TRUE for success, else FALSE
0x105FLVM_GETCOLUMNW3.51 and higher (NT); 
5.80 and higher (Windows)
passes wParam and lParam to GetColumn method; 
returns TRUE for success, else FALSE
0x1060LVM_SETCOLUMNW3.51 and higher (NT); 
5.80 and higher (Windows)
passes wParam and lParam to SetColumn method; 
returns TRUE for success, else FALSE
0x1061LVM_INSERTCOLUMNW3.51 and higher (NT); 
5.80 and higher (Windows)
passes wParam and lParam to InsertColumn method; 
returns indirect result
0x1062LVM_GETGROUPRECT6.10 and highertranslates to GetGroupRect method
0x1073LVM_GETITEMTEXTW3.51 and higher (NT); 
5.80 and higher (Windows)
 
0x1074LVM_SETITEMTEXTW3.51 and higher (NT); 
5.80 and higher (Windows)
translates to SetItemText method
0x1075LVM_GETISEARCHSTRINGW3.51 and higher (NT); 
5.80 and higher (Windows)
 
0x1076LVM_EDITLABELW3.51 and higher (NT); 
5.80 and higher (Windows)
 
0x108ALVM_SETBKIMAGEW4.71 and higher (NT) ; 
5.80 and higher (Windows)
passes lParam to SetBackgroundImage method; 
returns TRUE for success, else FALSE
0x108BLVM_GETBKIMAGEW4.71 and higher (NT) ; 
5.80 and higher (Windows)
passes lParam to GetBackgroundImage method; 
returns TRUE for success, else FALSE
0x108CLVM_SETSELECTEDCOLUMN6.00 and higherpasses wParam to SetSelectedColumn method; 
returns TRUE for success, else FALSE
0x108ELVM_SETVIEW6.00 and higherpasses wParam (zero-extended from 16 bits) to SetView method; 
returns 1 for success, else -1
0x108FLVM_GETVIEW6.00 and higherreturns indirect result of GetView method
0x1091LVM_INSERTGROUP6.00 and higherpasses wParam and lParam to InsertGroup method; 
returns indirect result
0x1093LVM_SETGROUPINFO6.00 and higher 
0x1095LVM_GETGROUPINFO6.00 and higher 
0x1096LVM_REMOVEGROUP6.00 and higher 
0x1098LVM_GETGROUPCOUNT6.10 and higherreturns indirect result of GetGroupCount method
0x1099LVM_GETGROUPINFOBYINDEX6.10 and higher 
0x109BLVM_SETGROUPMETRICS6.00 and higherpasses lParam to SetGroupMetrics method; 
return TRUE
0x109CLVM_GETGROUPMETRICS6.00 and higherpasses lParam to GetGroupMetrics method; 
return TRUE
0x109DLVM_ENABLEGROUPVIEW6.00 and higher 
0x109ELVM_SORTGROUPS6.00 and higherpasses wParam and lParam to SortGroups method; 
returns TRUE for success, else FALSE
0x109FLVM_INSERTGROUPSORTED6.00 and higherpasses wParam to InsertGroupSorted method; 
returns indirect result
0x10A0LVM_REMOVEALLGROUPS6.00 and higher 
0x10A1LVM_HASGROUP6.00 and higherpasses wParam to HasGroup method; 
returns indirect result
0x10A2LVM_SETTILEVIEWINFO6.00 and higherpasses lParam to SetTileViewInfo method; 
returns TRUE for success, else FALSE
0x10A3LVM_GETTILEVIEWINFO6.00 and higherpasses lParam to GetTileViewInfo method; 
returns TRUE for success, else FALSE
0x10A4LVM_SETTILEINFO6.00 and higherpasses lParam to SetTileInfo method; 
returns TRUE for success, else FALSE
0x10A5LVM_GETTILEINFO6.00 and higherpasses lParam to GetTileInfo method; 
returns TRUE for success, else FALSE
0x10A6LVM_SETINSERTMARK6.00 and higherpasses lParam to SetInsertMark method; 
returns TRUE for success, else FALSE
0x10A7LVM_GETINSERTMARK6.00 and higherpasses lParam to GetInsertMark method; 
returns TRUE for success, else FALSE
0x10A8LVM_INSERTMARKHITTEST6.00 and higherpasses wParam and lParam to HitTestInsertMark method; 
returns TRUE for success, else FALSE; 
fails trivially if wParam or lParam is NULL
0x10A9LVM_GETINSERTMARKRECT6.00 and higherpasses lParam to GetInsertMarkRect method; 
returns TRUE for success, else FALSE
0x10AALVM_SETINSERTMARKCOLOR6.00 and higherpasses lParam to SetInsertMarkColor method; 
returns indirect result
0x10ABLVM_GETINSERTMARKCOLOR6.00 and higherreturns indirect result of GetInsertMarkColor method
0x10ADLVM_SETINFOTIP6.00 and higherpasses lParam to SetInfoTip method; 
returns TRUE for success, else FALSE
0x10AELVM_GETSELECTEDCOLUMN6.00 and higherreturns indirect result of GetSelectedColumn method
0x10AFLVM_ISGROUPVIEWENABLED6.00 and higherreturns indirect result of IsGroupViewEnabled method
0x10B0LVM_GETOUTLINECOLOR6.00 and higherreturns indirect result of GetOutlineColor method
0x10B1LVM_SETOUTLINECOLOR6.00 and higherpasses lParam to SetOutlineColor method; 
returns indirect result
0x10B2LVM_SETKEYBOARDSELECTED6.00 and highertranslates to SetKeyboardSelected method
0x10B3LVM_CANCELEDITLABEL6.00 and highercalls CancelEditLabel method; 
returns TRUE
0x10B4LVM_MAPINDEXTOID6.00 and higherpasses wParam to MapIndexToId method; 
returns indirect result
0x10B5LVM_MAPIDTOINDEX6.00 and higherpasses wParam to MapIdToIndex method; 
returns indirect result
0x10B6LVM_ISITEMVISIBLE6.00 and highertranslates to IsItemVisible method
0x10B7LVM_EDITSUBITEM6.10 and highertranslates to EditSubItem method
0x10B8LVM_ENSURESUBITEMVISIBLE6.10 and highertranslates to EnsureSubItemVisible method
0x10B9LVM_GETCLIENTRECT6.10 and higherpasses FALSE and lParam to GetClientRect method; 
returns TRUE
0x10BALVM_GETFOCUSEDCOLUMN6.10 and higherreturns indirect result of GetFocusedColumn method
0x10BBLVM_SETOWNERDATACALLBACK6.10 and higherpasses wParam to SetOwnerDataCallback method; 
returns TRUE
0x10BCLVM_RECOMPUTEITEMS6.10 and higherpasses lParam to RecomputeItems method; 
returns TRUE
0x10BDLVM_QUERYINTERFACE6.10 and higherpasses wParam and lParam to QueryInterface method; 
returns TRUE for success, else FALSE
0x10BELVM_SETGROUPSUBSETCOUNT6.10 and higherpasses lParam to SetGroupSubsetCount method; 
returns TRUE for success, else FALSE; 
fails trivially if wParam is non-zero
0x10BFLVM_GETGROUPSUBSETCOUNT6.10 and higherreturns indirect result of GetGroupSubsetCount method; 
returns zero trivially if wParam or lParam is non-zero
0x10C0LVM_ORDERTOINDEX6.10 and higher 
0x10C1LVM_GETACCVERSION6.10 and higher 
0x10C2LVM_MAPACCIDTOACCINDEX6.10 and higher 
0x10C3LVM_MAPACCINDEXTOACCID6.10 and higher 
0x10C4LVM_GETOBJECTCOUNT6.10 and higher 
0x10C5LVM_GETOBJECTRECT6.10 and higher 
0x10C6LVM_ACCHITTEST6.10 and higher 
0x10C7LVM_GETFOCUSEDOBJECT6.10 and higher 
0x10C8LVM_GETOBJECTROLE6.10 and higher 
0x10C9LVM_GETOBJECTSTATE6.10 and higher 
0x10CALVM_ACCNAVIGATE6.10 and higher 
0x10CBLVM_INVOKEDEFAULTACTION6.10 and higher 
0x10CCLVM_GETEMPTYTEXT6.10 and higher 
0x10CDLVM_GETFOOTERRECT6.10 and higher 
0x10CELVM_GETFOOTERINFO6.10 and higher 
0x10CFLVM_GETFOOTERITEMRECT6.10 and higher 
0x10D0LVM_GETFOOTERITEM6.10 and higher 
0x10D1LVM_GETITEMINDEXRECT6.10 and highertranslates to GetSubItemRect method
0x10D2LVM_SETITEMINDEXSTATE6.10 and highertranslates to SetItemState method
0x10D3LVM_GETNEXTITEMINDEX6.10 and highertranslates to GetNextItem method
0x10D4LVM_SETPRESERVEALPHA6.10 and higher 



备注:本文转载自:http://www.geoffchappell.com/studies/windows/shell/comctl32/controls/listview/messages/index.htm


<script setup lang="ts"> import { nextTick, ref } from 'vue'; // @ts-ignore - These variables are used in the template import { marked as originalMarked } from 'marked'; // @ts-ignore - These variables are used in the template import hljs from 'highlight.js'; import 'highlight.js/styles/github.css'; import { useMessage, useToast } from 'wot-design-uni'; import send from '/static/icons/send.svg'; import { onLoad } from '@dcloudio/uni-app'; // 添加页面生命周期钩子 // @ts-ignore - These variables are used in the template import { getModellist } from '@/api/user.js'; const marked: any = originalMarked; const inputBottom = ref('15rpx'); // 输入框底部间距 const keyboardHeight = ref(0); // 键盘高度 const message = useMessage(); const toast = useToast(); const picker = ref<any>(null); // 添加选择器引用 // 监听键盘高度变化 uni.onKeyboardHeightChange((res) => { keyboardHeight.value = res.height; // 转换为px单位(小程序环境使用px) inputBottom.value = `${res.height}px`; }); // 在组件卸载时取消监听 onUnmounted(() => { uni.offKeyboardHeightChange(); }); // 配置marked选项 marked.setOptions({ highlight: (code: string, lang: string) => { if (lang && hljs.getLanguage(lang)) { try { return hljs.highlight(code, { language: lang }).value; } catch (err) { console.warn('代码高亮失败:', err); return code; } } return code; }, gfm: true, breaks: true, headerIds: false, mangle: false }); function renderMarkdown(content: string) { try { // 在小程序中,我们需要确保返回的是字符串 const html = marked(content); // 处理代码块的样式类,确保返回字符串 return String(html).replace(/<pre><code/g, '<pre><code class="hljs"'); } catch (err) { console.error('Markdown渲染失败:', err); return content; } } interface Message { role: 'user' | 'assistant'; content: string; typing?: boolean; } const messages = ref<Message[]>([ { role: 'assistant', content: '你好,我是你的智能助手。有什么问题我可以帮你解答吗?' } ]); const inputMessage = ref(''); const loading = ref(false); const scrollTop = ref(0); const messageListRef = ref<HTMLElement | null>(null); // 防抖函数 function debounce(fn: Function, delay: number) { let timer: number | null = null; return function (this: any, ...args: any[]) { if (timer) clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, delay); }; } // 节流函数 function throttle(fn: Function, delay: number) { let lastTime = 0; let timer: number | null = null; return function (this: any, ...args: any[]) { const now = Date.now(); const remaining = delay - (now - lastTime); if (remaining <= 0) { if (timer) { clearTimeout(timer); timer = null; } fn.apply(this, args); lastTime = now; } else if (!timer) { timer = setTimeout(() => { fn.apply(this, args); lastTime = Date.now(); timer = null; }, remaining); } }; } // 滚动到底部的函数 const scrollToBottom = throttle(async () => { if (!messageListRef.value) return; const lastMessage = messages.value[messages.value.length - 1]; if (lastMessage?.typing) return; // 打字过程中不触发滚动 await nextTick(); const query = uni.createSelectorQuery(); query .select('.message-list') .boundingClientRect((data: any) => { if (data) { scrollTop.value = data.height; } }) .exec(); }, 200); // 增加节流时间,减少滚动更新频率 // 监听消息列表变化,自动滚动到底部 watch( () => messages.value.length, () => { if (messages.value[messages.value.length - 1]?.typing) return; scrollToBottom(); } ); // 监听最后一条消息的内容变化(用于打字效果) watch( () => messages.value[messages.value.length - 1]?.content, async () => { const lastMessage = messages.value[messages.value.length - 1]; if (lastMessage?.typing) { // 在打字过程中,每次内容变化都尝试滚动 await nextTick(); const query = uni.createSelectorQuery(); query .select('.message-list') .boundingClientRect((data: any) => { if (data && data.height > scrollTop.value) { scrollTop.value = data.height; } }) .exec(); } } ); // 模拟打字效果的函数 async function typeMessage(fullContent: string) { try { // 获取最后一条消息的引用 const lastMessage = messages.value[messages.value.length - 1]; // 初始化内容并标记为正在输入 lastMessage.content = ''; lastMessage.typing = true; // 确保滚动到底部 await nextTick(); await scrollToBottom(); // 逐步更新内容 const chars = Array.from(fullContent); let currentContent = ''; const batchSize = 1; for (let i = 0; i < chars.length; i += batchSize) { currentContent += chars.slice(i, i + batchSize).join(''); lastMessage.content = currentContent; // 直接更新最后一条消息 await new Promise((resolve) => setTimeout(resolve, 10)); await scrollToBottom(); } // 最终状态更新 lastMessage.content = fullContent; lastMessage.typing = false; await nextTick(); await scrollToBottom(); } catch (error) { console.error('打字效果执行失败:', error); const lastMessage = messages.value[messages.value.length - 1]; lastMessage.content = fullContent; lastMessage.typing = false; await scrollToBottom(); } } async function sendMessage() { if (!inputMessage.value.trim()) return; const userContent = inputMessage.value; // 添加用户消息 messages.value.push({ role: 'user', content: userContent }); // 清空输入框 inputMessage.value = ''; loading.value = true; try { // 创建初始的助手消息 const assistantMessage: Message = { role: 'assistant', content: '', typing: true }; messages.value.push(assistantMessage); await nextTick(); const token = uni.getStorageSync('token'); // 发送请求 const { data } = await uni.request({ url: 'https://qa.mini.xmaas.cn/chat/completions', method: 'POST', header: { logic: 'check', Authorization: `${token}`, 'Content-Type': 'application/json' }, data: { position: 0, model: pickerValue.value, stream: true, messages: [{ role: 'user', content: userContent }] } }); // 处理流式数据 const streamData = data as string; const chunks = streamData .split('\n\n') // 分割事件 .filter((chunk) => chunk.trim().startsWith('data:')); // 过滤有效数据 let fullContent = ''; for (const chunk of chunks) { try { const jsonStr = chunk.replace(/^data:/, '').trim(); if (!jsonStr) continue; const eventData = JSON.parse(jsonStr); const contentChunk = eventData.choices[0]?.delta?.content || ''; // 处理Unicode转义字符 const decodedContent = unescape(contentChunk.replace(/\\u/g, '%u')); fullContent += decodedContent; } catch (err) { console.error('解析数据块失败:', err); } } // 更新最后一条消息内容,并触发打字效果 messages.value[messages.value.length - 1].content = fullContent; await typeMessage(fullContent); // 使用逐字打印效果 // 结束打字状态 messages.value[messages.value.length - 1].typing = false; } catch (error) { console.error('请求失败:', error); messages.value[messages.value.length - 1].content = '回答生成失败,请稍后重试'; } finally { loading.value = false; await scrollToBottom(); } } function loadMoreMessages() { // TODO: 实现加载更多历史消息 console.log('加载更多消息'); } // ... existing code ... function handleBack() { uni.navigateBack({ delta: 1, fail: () => { // 如果返回失败(比如没有上一页),则跳转到首页 uni.switchTab({ url: '/pages/index' }); } }); } //清空对话 // 修改 handClear 函数 async function handClear() { const token = uni.getStorageSync('token'); try { // 1. 调用清空接口 await uni.request({ url: 'https://qa.mini.xmaas.cn/message/deleteByChatId', method: 'DELETE', header: { 'Content-Type': 'application/json', Authorization: `${token}` } }); // 2. 重置本地消息状态为初始欢迎消息 messages.value = [ { role: 'assistant', content: '你好,我是你的智能助手。有什么问题我可以帮你解答吗?' } ]; // 3. 重置滚动位置到顶部 scrollTop.value = 0; // 4. 显示成功提示(已经在 beforeConfirm 中处理) } catch (error) { console.error('清空消息失败:', error); toast.error('清空消息失败'); } } function beforeConfirm() { message .confirm({ msg: '是否删除', title: '提示', beforeConfirm: ({ resolve }) => { toast.loading('删除中...'); setTimeout(() => { toast.close(); handClear(); resolve(true); toast.success('删除成功'); }, 2000); } }) .then(() => {}) .catch((error) => { console.log(error); }); } const columns = ref<Record<string, any>>([]); const pickerValue = ref<string>(''); // 添加获取模型列表的函数 async function fetchModelList() { try { const data = await getModellist(); if (data && data.models && data.models.length > 0) { // 将接口数据映射为选择器需要的格式 columns.value = data.models.map((model: any) => ({ value: model.name, label: model.name })); // 设置默认选中第一个模型 if (columns.value.length > 0) { pickerValue.value = columns.value[0].value; } } } catch (error) {} } function handleChange({ value }: any) { pickerValue.value = value; } // 添加按钮点击处理函数 function handleButtonClick() { picker.value?.open(); } function pauseLoading() { console.log(22222222222); } // 添加历史消息获取函数 async function fetchHistoryMessages() { const token = uni.getStorageSync('token'); try { loading.value = true; const { data }: any = await uni.request({ url: 'https://qa.mini.xmaas.cn/message/findByChatId', method: 'GET', header: { 'Content-Type': 'application/json', Authorization: `${token}` } }); // 处理接口返回的数据 if (data && data.messages && data.messages.length > 0) { // 按时间排序(确保消息顺序正确) const sortedMessages = data.messages.sort((a: any, b: any) => a.created_time - b.created_time); messages.value = []; // 转换为需要的格式 sortedMessages.forEach((msg: any) => { messages.value.push({ role: msg.model_id === 0 ? 'user' : 'assistant', content: msg.content, typing: false // 历史消息不需要打字效果 }); }); // 滚动到底部 await nextTick(); scrollToBottom(); } else { // 没有历史消息时显示欢迎语 setWelcomeMessage(); } } catch (error) { console.error('获取历史消息失败:', error); toast.error('加载历史消息失败'); // 请求失败时也显示欢迎语 setWelcomeMessage(); } finally { loading.value = false; } } function setWelcomeMessage() { messages.value = [ { role: 'assistant', content: '你好,我是你的智能助手。有什么问题我可以帮你解答吗?' } ]; } // 在页面加载时获取历史消息 onLoad(() => { fetchHistoryMessages(); fetchModelList(); }); //长按复制 function handleCopy(e: any) { // 确保是复制操作 if (e.detail.action === 'copy') { // 获取消息内容 const content = e.currentTarget.dataset.content; // 复制到剪贴板 uni.setClipboardData({ data: content, success: () => { // 使用您现有的 toast 组件 toast.success('复制成功'); }, fail: () => { toast.error('复制失败'); } }); } } // +++ 添加长按事件处理 +++ function handleLongPress(e: any, content: string) { console.log('长按事件触发', content); } </script> <template> <view class="chat-container"> <wd-navbar safe-area-inset-top placeholder left-arrow fixed :bordered="false" @click-left="handleBack"> <template #right> <view class="custom-right"> <wd-icon name="clear" size="22px" @click="beforeConfirm" /> </view> </template> </wd-navbar> <scroll-view ref="messageListRef" scroll-y class="chat-messages" :scroll-top="scrollTop" :scroll-with-animation="false" :scroll-anchoring="true" :enhanced="true" :bounces="false" @scrolltoupper="loadMoreMessages" > <view class="message-list"> <view v-for="(message, index) in messages" :key="index" class="message-item" :class="[message.role, { typing: message.typing }]" > <view v-if="message.role === 'assistant'" class="message-avatar"> <image src="/static/svg/Hara.svg" mode="aspectFill" style="width: 44rpx; height: 44rpx" /> <text>Hara</text> </view> <view class="message-content"> <!-- 统一使用rich-text渲染Markdown --> <view v-if="!message.typing" @longpress="(e) => handleLongPress(e, message.content)" > <rich-text :nodes="renderMarkdown(message.content)" :data-content="message.content" /> </view> <rich-text v-else :nodes="renderMarkdown(message.content)" /> <view v-if="message.typing" class="typing-indicator"> <view class="dot" /> <view class="dot" /> <view class="dot" /> </view> </view> </view> </view> </scroll-view> <wd-select-picker ref="picker" v-model="pickerValue" :columns="columns" @change="handleChange" custom-class="hidden-picker" custom-style="z-index:120" type="radio" :show-confirm="false" ></wd-select-picker> <view class="button_picker" style="margin-bottom: 45rpx; margin-left: 25rpx"> <wd-button type="success" @click="handleButtonClick" custom-class="handbtn"> <text style="font-family: PingFang SC">{{ pickerValue }}</text> <wd-icon name="arrow-down" size="16px" custom-style="transform: translateY(3rpx)"></wd-icon> </wd-button> </view> <view class="chat-input safe-area-bottom" :style="{ bottom: inputBottom }"> <input v-model="inputMessage" type="text" placeholder="向你的专属知识库提问吧~" :rows="2" class="message-textarea" :disabled="loading" :adjust-position="false" @keypress.enter.prevent="sendMessage" /> <!-- <wd-button type="primary" size="small" :loading="loading" :disabled="!inputMessage.trim()" @click="sendMessage"> 发送 </wd-button> --> <image :src="loading ? '/static/icons/Pause.svg' : !inputMessage.trim() ? '/static/icons/ic_send.svg' : '/static/icons/arrow.svg'" @click="loading ? pauseLoading() : inputMessage.trim() ? sendMessage() : null" style="width: 56rpx; height: 56rpx" /> </view> </view> </template> <style lang="scss" scoped> :deep(.handbtn) { background-color: #d8f2f3 !important; color: #14c3c9 !important; width: auto !important; border-radius: 16rpx !important; font-family: 'PingFang SC'; display: flex; align-items: center; } .button_picker { margin-bottom: 90rpx; margin-left: 80rpx; } /* 隐藏原生选择器控件 */ :deep(.hidden-picker) { .wd-select-picker__field { display: none !important; } .data-v-d4a8410a.wd-icon.wd-icon-check { color: #14c3c9 !important; } .data-v-aa3a6253.wd-button.is-primary.is-large.is-round.is-block { background-color: #7f6ce0 !important; } } .custom-right { display: flex; align-items: center; /* 垂直居中 */ gap: 10rpx; /* 元素间距 */ padding-right: 160rpx; /* 右侧距离 */ margin-right: 20rpx; } :deep(.wd-navbar) { background-color: #f4f4f5 !important; // 添加背景色 } :deep(.wd-navbar__title) { color: #000000 !important; } :deep(.wd-icon-arrow-left) { color: #000000 !important; } .chat-container { display: flex; flex-direction: column; height: 100vh; background-color: #f4f4f5; position: relative; } .chat-messages { flex: 0.9; box-sizing: border-box; padding: 20rpx 0 20rpx 20rpx; overflow-y: auto; -webkit-overflow-scrolling: touch; } .message-list { padding-bottom: 20rpx; } .message-item { display: flex; flex-direction: column; margin-bottom: 48rpx; opacity: 1; transform: translateY(0); transition: opacity 0.3s, transform 0.3s; // padding: 0 20rpx; &.typing { .message-content { min-width: 120rpx; } } &.user { flex-direction: row-reverse; .message-content { background-color: #272727; border-radius: 24rpx 24rpx 24rpx 24rpx; padding: 20rpx; color: #fff; box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1); max-width: 80%; :deep(pre), :deep(code) { background-color: rgba(255, 255, 255, 0.1); color: #fff; } } } &.assistant .message-content { max-width: 100%; // background-color: #fff; padding: 0 !important; // border-radius: 4rpx 20rpx 20rpx 20rpx; } } .message-avatar { width: 140rpx; flex-shrink: 0; margin: 0; display: flex; align-items: center; image { width: 100%; height: 100%; border-radius: 50%; } text { margin-left: 8rpx; color: 737373; font-weight: 400; font-size: 28rpx; } } .message-content { // max-width: 80%; margin: 0 20rpx; font-size: 28rpx; word-break: break-word; overflow-wrap: break-word; :deep(pre) { background-color: #f6f8fa; padding: 16rpx; border-radius: 6rpx; overflow-x: auto; margin: 16rpx 0; white-space: pre-wrap; word-wrap: break-word; } :deep(code) { font-family: Consolas, Monaco, 'Andale Mono', monospace; font-size: 24rpx; padding: 4rpx 8rpx; background-color: #f6f8fa; border-radius: 4rpx; white-space: pre-wrap; word-wrap: break-word; } :deep(p) { margin: 16rpx 0; } :deep(ul), :deep(ol) { padding-left: 32rpx; margin: 16rpx 0; } :deep(table) { border-collapse: collapse; margin: 16rpx 0; width: 100%; } :deep(th), :deep(td) { border: 2rpx solid #dfe2e5; padding: 12rpx 16rpx; } :deep(th) { background-color: #f6f8fa; } :deep(a) { color: #0366d6; text-decoration: none; &:hover { text-decoration: underline; } } :deep(img) { max-width: 100%; height: auto; } :deep(blockquote) { margin: 16rpx 0; padding: 0 16rpx; color: #6a737d; border-left: 4rpx solid #dfe2e5; } } .chat-input { padding: 24rpx 40rpx; position: fixed; left: 20rpx; right: 20rpx; bottom: 15rpx; display: flex; align-items: center; gap: 20rpx; z-index: 100; transition: bottom 0.3s ease; // 添加过渡动画 border-radius: 16rpx; border: 2rpx solid var(--Extra-Shallow-Theme, #f4f3fc); background: var(--White, #fff); box-shadow: 0px 8rpx 20rpx 0px rgba(62, 46, 136, 0.17); margin-bottom: 45rpx; .message-textarea { flex: 1; } } .typing-indicator { display: flex; align-items: center; gap: 8rpx; padding: 8rpx 0; .dot { width: 8rpx; height: 8rpx; background-color: #999; border-radius: 50%; animation: typing 1.4s infinite; &:nth-child(2) { animation-delay: 0.2s; } &:nth-child(3) { animation-delay: 0.4s; } } } @keyframes typing { 0%, 60%, 100% { transform: translateY(0); opacity: 0.3; } 30% { transform: translateY(-4rpx); opacity: 1; } } </style> markdown 输出的消息不能复制咋办
07-23
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值