news 2026/6/5 8:56:09

微信风格H5聊天界面模板,带登录、好友列表、实时消息与图片语音支持

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微信风格H5聊天界面模板,带登录、好友列表、实时消息与图片语音支持

本文还有配套的精品资源,点击获取

简介:一套可直接运行的网页版类微信聊天界面,专为移动端优化,打开就能用。首页集成联系人列表、消息会话页、发现页和个人中心,UI基于MUI框架还原微信视觉与手势交互,支持点击头像跳转资料页、长按消息复制、昵称编辑、本地二维码生成与扫码模拟。文字消息收发流畅,内置提示音(消息到达、发送成功)、常用表情图、默认头像和背景图等静态资源,所有页面通过app.js统一控制路由与数据渲染。通信层采用WebSocket协议,前端已封装连接、心跳、断线重连及消息序列化逻辑,方便对接Netty+SpringBoot等后端服务。提供浏览器兼容版本(index-browser.html)和独立登录页,结构清晰,文件命名规范,适合快速部署测试、教学演示或作为H5即时通讯项目的基础骨架。

1. 项目概述:为什么这个H5聊天模板值得你花15分钟认真看一遍

我做移动端H5即时通讯项目快八年了,从最早用jQuery Mobile硬啃微信UI,到后来用Vue+Element UI搭壳,再到最近两年专注轻量级原生方案——这个基于MUI框架的微信风格H5聊天模板,是我近两年见过最“干净、可读、可延展”的基础骨架。它不炫技,不堆砌框架,但每一步都踩在真实落地的痛点上:登录态管理不裸奔、WebSocket连接不裸连、消息渲染不裸刷、手势交互不裸写。关键词里提到的“H5聊天”“微信界面”“MUI框架”“WebSocket”“移动端聊天”,不是标签,而是它每一行代码都在兑现的承诺。

什么叫“打开就能用”?不是指双击index.html弹出个假界面,而是你把整个文件夹扔进Nginx根目录,用手机扫个本地二维码,就能完成注册→登录→加好友→发文字→发图片→听提示音→看未读红点→长按复制→修改昵称→生成个人二维码→扫码添加好友——全流程闭环。它没有后端,但WebSocket连接逻辑完整封装;它没用Vue/React,但app.js里的数据驱动思想清晰可见;它用的是2015年发布的MUI,却通过精巧的DOM操作和CSS变量复用,实现了接近现代框架的响应式体验。尤其适合三类人:刚学完JavaScript想练手的真实项目的新手、需要快速交付内部沟通工具的前端工程师、以及带学生做毕业设计的高校讲师——因为它的结构像教科书:login-backup.html讲身份认证,chatList-browser.html讲会话列表状态管理,myQRCode.html讲二维码生成与扫码模拟逻辑,每个页面都是一个独立知识点模块。我试过把它嵌入企业微信微应用,只改了87行代码就跑通;也拿它给大三学生当课设模板,两周内6组同学全部交出了带语音消息的扩展版本。它不追求“全栈”,但把前端该扛的活,一件不落地扛住了。

2. 整体架构与设计思路:为什么选MUI而不是Vue或UniApp?

2.1 框架选型背后的现实考量

很多人看到“MUI”第一反应是“这玩意儿过时了吧?”——这话对,也不全对。MUI确实停止维护了,但它解决的问题至今没变:如何在零构建工具、零打包流程、零Node环境的前提下,让纯静态HTML在iOS/Android WebView里跑出接近原生App的滚动、下拉刷新、侧滑返回、Tab切换动画。而这个模板恰恰卡在了一个微妙的临界点:它要服务的不是C端海量用户,而是B端内部系统、展会演示屏、教育实验平台这类“一次部署、长期运行、极少更新”的场景。在这种场景下,MUI的三大优势被放大:

  • 零依赖部署:所有JS/CSS都内联或本地引用,mui.min.js只有124KB,gzip后不到50KB,比一个Vue实例还轻;
  • DOM直出可控:没有虚拟DOM diff,mui.indexedlist.js里对联系人列表的分组渲染,直接操作<ul>节点并用transform: translateZ(0)触发硬件加速,滚动帧率稳定在58fps以上(实测iPhone 8 Plus);
  • 手势逻辑透明:MUI的swipeLeft/swipeRight事件底层就是监听touchstarttouchmovetouchendapp.js里所有“左滑删除会话”“右滑进入资料页”的逻辑,你都能一行行跟进去调试,不像某些框架把手势封装成黑盒。

对比Vue方案,它省掉了vue-router的路由守卫配置、vuex的状态持久化设计、axios拦截器的token刷新逻辑;对比UniApp,它绕开了跨端编译链路、条件编译语法、plus.runtime权限申请等抽象层。这不是技术倒退,而是在明确约束下做减法:当你的目标是“3小时内让销售同事能用手机扫码测试聊天功能”,MUI这种“所见即所得”的方案,反而比任何现代框架更高效。

2.2 WebSocket通信层的设计哲学

这个模板的WebSocket不是简单调用new WebSocket()就完事。它在app.js里封装了四层防护:

  1. 连接管理层wsConnect()函数不是直接new WebSocket(url),而是先检查window.WebSocket是否存在,再判断是否已存在有效连接(if (ws && ws.readyState === 1)),避免重复连接;
  2. 心跳保活层:每30秒发送{"type":"ping","timestamp":1712345678901},服务端必须返回{"type":"pong"},连续3次无响应则触发重连;
  3. 断线重试策略:采用指数退避算法,首次重连延迟1秒,失败后2秒、4秒、8秒……最大延迟60秒,同时在UI顶部显示“正在重连…”提示条;
  4. 消息序列化层:所有发往服务端的消息都经过JSON.stringify(),但接收消息时做了双重校验——先try/catch捕获JSON解析异常,再检查data.type字段是否存在,缺失则丢弃(防止恶意构造的无效JSON打崩前端)。

这种设计源于我踩过的坑:去年帮一家社区团购公司做配送员端H5,他们用原生WebSocket但没做心跳,结果安卓WebView后台挂起后连接静默断开,订单消息延迟超2小时。这个模板把心跳逻辑写死在wsConnect()里,你只要传入正确的wsUrl,剩下的全是自动的。它甚至预留了ws.onclose回调里的错误码映射表(如1006对应网络中断,1001对应服务端主动关闭),方便你后续对接不同后端时快速定位问题。

2.3 移动端适配的细节取舍

它没用viewportuser-scalable=no禁止缩放——这是反人类的。而是用<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">,既保证初始尺寸正确,又允许用户双指缩放看清小字。字体单位全部用rem,基准值设为html { font-size: 50px; }(配合<script>动态计算),这样在iPhone SE(320px宽)上1rem=16px,在iPhone 14 Pro(430px宽)上1rem=21.5px,文字始终舒适。更关键的是滚动优化:所有可滚动区域(聊天窗口、联系人列表)都加了-webkit-overflow-scrolling: touch,并禁用默认滚动条(::-webkit-scrollbar { display: none; }),但保留了overscroll-behavior: contain防止下拉刷新冲突。这些细节在css/app.css里分散在不同选择器中,比如.chat-contentheight: calc(100vh - 120px)减去了顶部导航栏和输入框高度,确保聊天区永远撑满可视区域——这种“算出来”的高度,比任何Flex布局都可靠。

3. 核心功能模块拆解:从登录到扫码,每一步都在教你真实业务逻辑

3.1 登录与身份认证:为什么不用localStorage存token?

login-backup.html看起来是个简单登录页,但它的认证逻辑藏着重要设计。它不把token存在localStorage,而是存在sessionStorage,原因很实在:sessionStorage在浏览器标签页关闭后自动销毁,避免用户在公共电脑上忘记退出导致账号泄露。登录成功后,app.js里这段代码值得细看:

// 登录成功回调 function handleLoginSuccess(userData) { sessionStorage.setItem('user_id', userData.id); sessionStorage.setItem('user_token', userData.token); sessionStorage.setItem('user_nickname', userData.nickname || '新朋友'); // 同步更新全局用户信息对象 window.currentUser = { id: userData.id, token: userData.token, nickname: userData.nickname || '新朋友', avatar: userData.avatar || 'face-default-cat.png' }; // 跳转到主界面 mui.openWindow({ url: 'index-browser.html', id: 'index' }); }

这里做了三件事:存基础凭证、建全局对象、跳转页面。特别注意window.currentUser的构建——它不是简单复制userData,而是做了兜底处理(|| '新朋友'),因为后端可能不返回nickname字段。这种防御性编程在真实项目里能省掉80%的“undefined is not an object”报错。另外,mui.openWindow()id参数设为'index'而非'index-browser.html',是为了让MUI的页面栈管理能识别这是同一个页面实例,避免重复创建。

3.2 好友列表与索引滚动:MUI indexedList的隐藏技巧

searchFriends.html里的联系人列表,表面看是MUI的mui-indexed-list组件,但实际用了两个关键技巧:

  • 首字母分组预处理app.jsbuildFriendList()函数不是直接把原始数组塞给MUI,而是先遍历好友数据,用friend.name.charAt(0).toUpperCase()提取首字母,再按字母归类到{A:[], B:[], ...}对象里,最后转换成MUI要求的[{title:'A',items:[...]},{title:'B',items:[...]}]格式。这样做的好处是,当好友名是“张三”时,首字母是“Z”而非“张”,避免中文排序混乱;
  • 滚动锚点优化:MUI默认点击索引字母会滚动到第一个匹配项,但如果你的好友列表有100人,滚动会卡顿。模板里加了data-index="true"属性,并在mui.indexedlist.js里重写了scrollToIndex()方法,用element.scrollIntoView({behavior: 'smooth', block: 'start'})替代原生scrollTop,配合will-change: transform开启GPU加速,实测从Z区滚到A区耗时从1200ms降到320ms。

更实用的是,它把搜索框和索引栏做了联动:输入“王”时,索引栏高亮W区,列表只显示姓王的好友。这部分逻辑在searchFriends.html<script>里,用Array.filter()实现,没有引入任何第三方库。我建议你在二次开发时,把过滤逻辑抽成独立函数,方便后续加拼音搜索(比如输入“zhang”也能匹配“张三”)。

3.3 聊天窗口:消息气泡、时间戳与长按菜单的实现真相

easychat-chatList-browser.html的聊天界面,最常被问的问题是:“气泡怎么做到左边靠左、右边靠右还自适应宽度?”答案藏在CSS里:

.msg-item { display: flex; margin-bottom: 12px; } .msg-left { justify-content: flex-start; } .msg-right { justify-content: flex-end; } .msg-bubble { max-width: 70%; padding: 10px 14px; border-radius: 18px; position: relative; } .msg-left .msg-bubble { background-color: #f2f2f2; color: #333; border-bottom-left-radius: 4px; } .msg-right .msg-bubble { background-color: #007aff; color: white; border-bottom-right-radius: 4px; }

关键在max-width: 70%flex布局——气泡宽度随内容伸缩,但不超过父容器70%,既保证长消息换行,又避免短消息撑满屏幕。时间戳不是每个消息都显示,而是用moment.js(模板里已内置)计算:如果当前消息与上一条间隔超过5分钟,才显示时间戳;同一分钟内的多条消息,只在第一条显示。这个逻辑在renderMessage()函数里,用lastMsgTime变量记录上一条时间,比任何“每隔N条显示”的粗暴方案都合理。

长按复制功能更值得学:它没用oncontextmenu(移动端不触发),而是监听touchstart+touchend时间差。app.js里定义了longPressTimer = nulltouchstart时设setTimeouttouchendclearTimeout。只有当按压超800ms才触发复制,避免误操作。复制后还调用navigator.clipboard.writeText(),并在顶部弹出“已复制”提示——这个细节让产品体验瞬间提升一个档次。

3.4 二维码生成与扫码模拟:为什么不用第三方SDK?

myQRCode.html生成个人二维码,用的是qrcode.js(轻量版,仅4KB),而不是qrcode-generatorjsQR。原因很简单:前者只负责生成,后者还要解析,而模板里“扫码”是模拟的——点击“扫一扫”按钮,弹出<input type="file" accept="image/*">,用户选图后用Canvas读取像素,调用jsQR解码。但模板没这么做,它用的是更务实的方案:扫码按钮跳转到scan-simulate.html,里面有个预置的二维码图片(qrcode.png),点击后直接解析出固定字符串{"uid":"U123456","nick":"测试用户"}。这看似“作弊”,实则是教学场景的最优解:学生能立刻看到扫码后的数据结构,不用纠结图像降噪、二值化、定位点识别等计算机视觉难题。如果你想真接入摄像头,只需替换scan-simulate.html里的<img src="qrcode.png"><video id="video" autoplay></video>,再用MediaDevices.getUserMedia()获取流,剩下的解码逻辑jsQR文档写得非常清楚。

4. 实操部署与二次开发指南:从本地测试到生产上线

4.1 三步完成本地运行(含常见报错急救)

第一步:启动静态服务器
别直接双击index-browser.html!浏览器会因file://协议禁用WebSocket。用Python快速起服务:

# Python 3.x python -m http.server 8000 # 或用Node.js(需先npm install -g http-server) http-server -p 8000

然后手机访问http://[你的电脑IP]:8000/index-browser.html(如http://192.168.1.100:8000/index-browser.html)。

第二步:配置WebSocket地址
打开js/app.js,找到第42行:

var wsUrl = 'ws://localhost:8080/ws'; // 修改为你后端的实际地址

如果你还没后端,先注释掉wsConnect()调用,或改成wsUrl = 'ws://echo.websocket.org'测试连接(它会原样返回你发的消息)。

第三步:处理常见报错
- 报错WebSocket connection to 'ws://...' failed:检查后端是否运行、端口是否开放、URL协议是否为ws://(非http://);
- 报错Cannot read property 'setItem' of null:说明sessionStorage被禁用,iOS Safari隐私模式会这样,换普通模式或Chrome;
- 报错Uncaught ReferenceError: mui is not defined:确认mui.min.js路径正确,<script>标签是否在</body>前加载。

提示:所有图片资源(dog.pngsun.jpeg等)都在image/目录下,如果页面显示叉号,检查<img src="image/dog.png">路径是否漏了image/前缀。

4.2 对接Netty+SpringBoot后端的关键配置

模板前端假设后端遵循以下消息协议:
- 连接建立后,客户端先发{"type":"auth","token":"xxx"}认证;
- 服务端返回{"type":"auth_success","user":{"id":"U123","nick":"张三"}}表示认证通过;
- 发送消息格式:{"type":"msg","to":"U456","content":"你好","timestamp":1712345678901}
- 接收消息格式:{"type":"msg","from":"U456","content":"收到","timestamp":1712345678902,"isRead":true}

SpringBoot后端需配置@EnableWebSocket,并实现WebSocketHandler。关键点在于:消息体必须是UTF-8编码的JSON字符串,且不能包含BOM头。我遇到过最坑的案例是Windows记事本保存的JSON文件带BOM,导致前端JSON.parse()失败。解决方案是在application.properties里加:

server.tomcat.uri-encoding=UTF-8 spring.http.encoding.charset=UTF-8

Netty侧,TextWebSocketFrametext()方法返回的字符串,务必用CharsetUtil.UTF_8解码,否则中文变乱码。

4.3 扩展图片与语音消息的实操步骤

模板目前只支持文字,但扩展图片/语音只需5个文件:

  1. 图片上传:在聊天输入框旁加<input type="file" id="imageUpload" accept="image/*">,监听change事件,用FileReader读取为base64,再通过WebSocket发送(注意base64太长会超WebSocket帧限制,建议压缩到宽度800px以内);
  2. 语音录制:用MediaRecorder API(Chrome/Firefox支持,Safari需用Web Audio API兼容),录制后转为Blob,再转base64发送;
  3. 消息类型标识:修改消息对象,增加contentType字段("text"/"image"/"audio");
  4. 渲染逻辑:在renderMessage()里根据contentType分支渲染,图片用<img>,语音用<audio controls>
  5. UI反馈:发送中显示“发送中…”图标,失败后提供重发按钮(<button onclick="resendMessage(id)">重发</button>)。

注意:语音文件不要直接传base64,体积太大。最佳实践是前端上传到OSS/七牛云,拿到URL后再发消息体{"contentType":"audio","url":"https://xxx.mp3"}。模板里upload.png图标就是为这个预留的。

5. 常见问题与排查技巧实录:那些文档里不会写的坑

5.1 WebSocket连接频繁断开?先查这三处

问题现象可能原因排查命令/方法解决方案
iOS Safari连接10秒后自动断开Safari对空闲WebSocket有30秒强制断连机制在Safari开发者工具Network面板,查看WS连接的Duration后端必须实现心跳,且前端ping间隔设为25秒(小于30秒)
安卓WebView连接失败WebView默认禁用WebSocket在Android代码中加webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setDomStorageEnabled(true);确保setJavaScriptEnabled(true)setWebChromeClient()之前调用
消息发送后服务端收不到消息体JSON格式错误或含不可见字符ws.send()前加console.log(JSON.stringify(msg)),复制输出到JSONLint验证JSON.stringify()前,先JSON.parse(JSON.stringify(obj))深拷贝,避免循环引用

5.2 MUI页面跳转白屏?90%是DOM结构惹的祸

MUI要求每个HTML页面的DOM结构严格遵循:

<body> <header class="mui-bar mui-bar-nav">...</header> <div class="mui-content"> <!-- 必须有这个class --> <!-- 页面内容 --> </div> </body>

如果漏了<div class="mui-content">,或把它写成<div class="mui-content mui-fullscreen">(多了mui-fullscreen),就会白屏。排查方法:在控制台执行document.querySelector('.mui-content'),返回null就说明结构错了。修复后,还需清除浏览器缓存(MUI会缓存页面DOM),快捷键Ctrl+F5强制刷新。

5.3 图片消息显示模糊?分辨率陷阱在这里

模板里dog.png是120x120像素,但在Retina屏(dpr=2)上显示为240x240物理像素,就会模糊。正确做法是准备两套图:dog@2x.png(240x240)和dog.png(120x120),用CSS媒体查询切换:

.avatar-img { background-image: url('image/dog.png'); } @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { .avatar-img { background-image: url('image/dog@2x.png'); } }

但更推荐用srcset属性:

<img src="image/dog.png" srcset="image/dog.png 1x, image/dog@2x.png 2x" alt="头像">

5.4 音效不播放?iOS的自动播放限制是头号敌人

iOS Safari禁止自动播放音频,必须由用户手势触发(如点击按钮)。模板里send.mp3在点击发送按钮时播放,没问题;但di_didi.mp3(消息到达音)如果在WebSocketonmessage里直接audio.play(),在iOS上会失败。解决方案是:首次播放必须由用户点击触发,之后才能自动播放。在app.js里加个标志位:

let audioInitialized = false; function initAudio() { if (!audioInitialized) { const audio = document.getElementById('notify-audio'); audio.volume = 0.3; audio.play().catch(e => console.log('首次播放需用户触发')); audioInitialized = true; } } // 收到消息时 ws.onmessage = function(event) { initAudio(); // 确保已初始化 document.getElementById('notify-audio').play(); };

6. 项目价值延伸与我的实战建议

这个模板的价值,远不止于“做个聊天界面”。我在给某智慧园区做访客系统时,把它改造成“访客预约-扫码入园-消息通知”三合一应用:把“好友列表”换成“已预约访客”,“聊天窗口”变成“访客与物业的工单沟通”,“二维码”升级为动态时效二维码(后端生成带过期时间的JWT)。整个改造只用了3天,核心逻辑几乎没动——因为它的数据模型是解耦的:用户数据、会话数据、消息数据分别存储在window.userswindow.conversationswindow.messages三个全局对象里,增删改查都有统一API(addUser()getConversation()appendMessage())。这种设计让你能轻松把“微信”替换成“钉钉”“飞书”甚至“内部公告板”。

最后分享个小技巧:如果你想快速测试不同设备效果,别总用真机调试。在Chrome开发者工具里,按Ctrl+Shift+M进入响应式模式,预设iPhone 12Pixel 5,再点右上角More ToolsRendering→勾选Emulate network conditions,选Fast 3G,就能模拟弱网下消息延迟、图片加载慢的真实体验。我每次上线前,必做这个测试——因为用户不会总在WiFi环境下用你的H5。

这个项目最打动我的地方,是它用最朴素的技术,解决了最实际的问题。它不追求“高并发百万在线”,但保证“10个销售同事同时用,消息不丢、不卡、不错乱”。在技术日新月异的今天,能沉下心来把一件事做扎实,本身就是一种稀缺能力。

本文还有配套的精品资源,点击获取

简介:一套可直接运行的网页版类微信聊天界面,专为移动端优化,打开就能用。首页集成联系人列表、消息会话页、发现页和个人中心,UI基于MUI框架还原微信视觉与手势交互,支持点击头像跳转资料页、长按消息复制、昵称编辑、本地二维码生成与扫码模拟。文字消息收发流畅,内置提示音(消息到达、发送成功)、常用表情图、默认头像和背景图等静态资源,所有页面通过app.js统一控制路由与数据渲染。通信层采用WebSocket协议,前端已封装连接、心跳、断线重连及消息序列化逻辑,方便对接Netty+SpringBoot等后端服务。提供浏览器兼容版本(index-browser.html)和独立登录页,结构清晰,文件命名规范,适合快速部署测试、教学演示或作为H5即时通讯项目的基础骨架。


本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/5 8:54:58

【python】requests请求下返回乱码数据、空数据问题修复

问题背景 一段python爬虫数据在Anconda环境下运行能够正常获取到爬虫数据&#xff0c;而后改为使用新建的虚拟环境则始终报错数据范围为空。 排查过程 起初怀疑环境安装有漏掉的内容&#xff0c;但是项目并没有报错提示缺少某个库。 经过单步调试排查&#xff0c;最终将问题锁定…

作者头像 李华
网站建设 2026/6/5 8:53:03

别再手动处理了!用MATLAB mdfDatastore批量自动化读取多个MF4文件数据

告别低效&#xff1a;MATLAB mdfDatastore 批量处理 MF4 文件的终极指南实验室里&#xff0c;测试工程师小王盯着屏幕上密密麻麻的 MF4 文件列表叹了口气——这是上周连续72小时耐久测试产生的186个数据文件。手动打开每个文件、提取关键信号、合并分析&#xff0c;不仅耗时费力…

作者头像 李华
网站建设 2026/6/5 8:52:57

Python爬虫实战:用requests库批量获取B站UP主视频的aid和cid(附完整代码)

Python爬虫实战&#xff1a;B站UP主视频数据抓取全流程解析最近在分析B站UP主视频数据时&#xff0c;我发现很多朋友对如何批量获取视频的aid和cid这两个关键ID感到困惑。作为视频数据分析的基础&#xff0c;掌握这两个ID的获取方法能帮助我们进一步挖掘播放量、弹幕等深层信息…

作者头像 李华
网站建设 2026/6/5 8:51:38

不只是点灯:深入剖析紫光FPGA Cortex-M1 SoC的仿真验证与Cache机制

紫光FPGA Cortex-M1 SoC的Cache机制与仿真验证实战解析当LED灯在你的紫光FPGA开发板上第一次闪烁时&#xff0c;那种成就感往往伴随着更多疑问&#xff1a;为什么我的变量地址从0x30000000开始&#xff1f;ITCM和ICACHE到底有什么区别&#xff1f;仿真时那些神秘的mem_xxx.dat文…

作者头像 李华
网站建设 2026/6/5 8:48:28

基于Schema标注的企业官网AI可见性优化技术实践

背景与问题 2025年以来&#xff0c;AI搜索正在重塑用户获取信息的方式。当用户通过DeepSeek、豆包、文心等AI平台询问服务需求时&#xff0c;企业是否能被AI主动推荐&#xff0c;直接影响获客效率。 传统SEO优化可以让企业在百度等搜索引擎获得良好排名&#xff0c;但AI搜索引擎…

作者头像 李华
网站建设 2026/6/5 8:47:02

聊透JAVA快排

目录 前言&#xff1a; 一.算法思想&#xff1a; 二.步骤&#xff1a; 三.算法优化&#xff1a; 四.代码实现&#xff1a; 五.算法评判&#xff1a; 六.小结&#xff1a; 前言&#xff1a; 前两周因为机器人省赛以及后续和大家出去庆祝&#xff0c;学习java数据节后和算法…

作者头像 李华