源 | https://www.jianshu.com/p/47bac9ccaaa7
在过去一年里,前端开发发展迅速,前端工程师的薪资亦是水涨船高。2020年也会热度不减,而作为近年来尤为热门的前端框架Vue.js 自是积累了大量关注。
本文将为你总结一下 2019 年里最值得关注的 45 个 Vue.js 开源项目——Let's go!
在过去的一年里,我们比较了将近 12000 个 Vue.js 开源项目和库,并从中挑选了最好的 45 个(占比 0.37%)。
这些项目和库可以分为 3 类:
这是一个极具权威性的表单,精确汇总了 2018 年 1-12 月期间发布的最佳 Vue.js 开源项目。
Mybridge 工作组从受欢迎程度、参与度和新鲜度三方面对它们进行了评估。
这些项目的 Github Star 平均数为 2620,可见其卓越不凡。
开源项目对程序员来说意义非凡,让我们花些时间,一起来看看这些在去年可能被你错过了的 Vue.js 开源项目吧。
1. Vuetify
2. Weex-ui
3. Eagle.js
4. Vuesax
5. Vue-ydui
6. Vue-grid-layout
7. Vue-virtual-scroller
8. Vue-content-loader
9. Mand-mobile
10. Vuikit
11. Vue-design-system v2.0
12. Vue-styleguidist
13. Heyui
14. Vue2-animate v2.0
15. Ui
16. Proppy
17. ZircleUI
18. Vue-overdrive
19. Vue-argon-design-system
20. Vue-cli
21. Vue-devtools(v 4.0)
22. Vue-native-core
23. Tiptap
24. Uni-app
25. Vue-rx v6.0
26. Eros
27. Vue-wait
28. Vue-starter
29. Vue-hooks
30. Portal-vue
31. Vue-fullpage.js
32. Vue-api-query
33. Vuese
34. Vuex-orm
35. Vuex-pathify
36. Vue-vr
37. Vuepress
38. Gridsome
39. Vue-music-webapp
40. Vue-realworld-example-app
41. Page-transitions-travelapp
42. Sample-vue-shop
43. Vuegg
44. Vue-filepond
Vue-filepond
45. Monimo
以上,就是我认为比较值得关注的 Vue.js 开源项目!
习的动力源于兴趣,愿你在学习新知识时,动力源于兴趣而并非其它
写JQuery项目时,使用websocket很简单,不用去考虑模块化,组件之间的访问问题,面向文档编程即可,在Vue项目中使用时,远远没有想象中的那么简单,需要考虑很多场景,本篇文章将与各位开发者分享下vue-native-websocket库的使用以及配置,用其实现群聊功能。先看下最终实现的效果
本文中对于vue-native-websocket库的讲解,项目中配置了vuex,对其不了解的开发者请移步官方文档,如果选择继续阅读本篇文章会比较吃力。
# yarn | npm 安装
yarn add vue-native-websocket | npm install vue-native-websocket --save
复制代码
import VueNativeSock from 'vue-native-websocket'
复制代码
// main.js
// base.lkWebSocket为你服务端websocket地址
Vue.use(VueNativeSock,base.lkWebSocket,{
// 启用Vuex集成,store的值为你的vuex
store: store,
// 数据发送/接收使用使用json格式
format: "json",
// 开启自动重连
reconnection: true,
// 尝试重连的次数
reconnectionAttempts: 5,
// 重连间隔时间
reconnectionDelay: 3000,
// 将数据进行序列化,由于启用了json格式的数据传输这里需要进行重写
passToStoreHandler: function (eventName, event) {
if (!eventName.startsWith('SOCKET_')) { return }
let method='commit';
let target=eventName.toUpperCase();
let msg=event;
if (this.format==='json' && event.data) {
msg=JSON.parse(event.data);
if (msg.mutation) {
target=[msg.namespace || '', msg.mutation].filter((e)=> !!e).join('/');
} else if (msg.action) {
method='dispatch';
target=[msg.namespace || '', msg.action].filter((e)=> !!e).join('/');
}
}
this.store[method](target, msg);
this.store.state.socket.message=msg;
}
});
复制代码
// vuex配置文件
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
token:"",
userID:"",
// 用户头像
profilePicture: "",
socket: {
// 连接状态
isConnected: false,
// 消息内容
message: '',
// 重新连接错误
reconnectError: false
}
},
mutations: {
SOCKET_ONOPEN (state, event) {
// 连接打开触发的函数
Vue.prototype.$socket=event.currentTarget;
state.socket.isConnected=true
},
SOCKET_ONCLOSE (state, event) {
// 连接关闭触发的函数
state.socket.isConnected=false;
console.log(event);
},
SOCKET_ONERROR (state, event) {
// 连接发生错误触发的函数
console.error(state, event)
},
SOCKET_ONMESSAGE (state, message) {
// 收到消息时触发的函数
state.socket.message=message
},
SOCKET_RECONNECT(state, count) {
// 重新连接触发的函数
console.info(state, count)
},
SOCKET_RECONNECT_ERROR(state) {
// 重新连接失败触发的函数
state.socket.reconnectError=true;
},
},
actions: {
customerAdded (context) {
// 新连接添加函数
console.log('action received: customerAdded');
console.log(context)
}
},
modules: {
}
})
复制代码
至此vue-native-websocket配置结束,如需了解更多配置方法,请移步npm仓库
// 监听消息接收
this.$options.sockets.onmessage=(res)=>{
// res.data为服务端返回的数据
const data=JSON.parse(res.data);
// 200为服务端连接建立成功时返回的状态码(此处根据真实后端返回值进行相应的修改)
if(data.code===200){
// 连接建立成功
console.log(data.msg);
}else{
// 获取服务端推送的消息
const msgObj={
msg: data.msg,
avatarSrc: data.avatarSrc,
userID: data.userID
};
// 渲染页面:如果msgArray存在则转json
if(lodash.isEmpty(localStorage.getItem("msgArray"))){
this.renderPage([],msgObj,0);
}else{
this.renderPage(JSON.parse(localStorage.getItem("msgArray")),msgObj,0);
}
}
};
复制代码
// 消息发送函数
sendMessage: function (event) {
if (event.keyCode===13) {
// 阻止编辑框默认生成div事件
event.preventDefault();
let msgText="";
// 获取输入框下的所有子元素
let allNodes=event.target.childNodes;
for(let item of allNodes){
// 判断当前元素是否为img元素
if(item.nodeName==="IMG"){
msgText +=`/${item.alt}/`;
}
else{
// 获取text节点的值
if(item.nodeValue!==null){
msgText +=item.nodeValue;
}
}
}
// 消息发送: 消息内容、状态码、当前登录用户的头像地址、用户id
this.$socket.sendObj({msg: msgText,code: 0,avatarSrc: this.$store.state.profilePicture,userID: this.$store.state.userID});
// 清空输入框中的内容
event.target.innerHTML="";
}
}
复制代码
// 渲染页面函数
renderPage: function(msgArray,msgObj,status){
if(status===1){
// 页面第一次加载,如果本地存储中有数据则渲染至页面
let msgArray=[];
if(localStorage.getItem("msgArray")!==null){
msgArray=JSON.parse(localStorage.getItem("msgArray"));
for (let i=0; i<msgArray.length;i++){
const thisSenderMessageObj={
"msgText": msgArray[i].msg,
"msgId": i,
"avatarSrc": msgArray[i].avatarSrc,
"userID": msgArray[i].userID
};
// 解析并渲染
this.messageParsing(thisSenderMessageObj);
}
}
}else{
// 判断本地存储中是否有数据
if(localStorage.getItem("msgArray")===null){
// 新增记录
msgArray.push(msgObj);
localStorage.setItem("msgArray",JSON.stringify(msgArray));
for (let i=0; i <msgArray.length; i++){
const thisSenderMessageObj={
"msgText": msgArray[i].msg,
"msgId": i,
"avatarSrc": msgArray[i].avatarSrc,
"userID": msgArray[i].userID,
};
// 解析并渲染
this.messageParsing(thisSenderMessageObj);
}
}else{
// 更新记录
msgArray=JSON.parse(localStorage.getItem("msgArray"));
msgArray.push(msgObj);
localStorage.setItem("msgArray",JSON.stringify(msgArray));
const thisSenderMessageObj={
"msgText": msgObj.msg,
"msgId": Date.now(),
"avatarSrc": msgObj.avatarSrc,
"userID": msgObj.userID
};
// 解析并渲染
this.messageParsing(thisSenderMessageObj);
}
}
}
复制代码
// 消息解析
messageParsing: function(msgObj){
// 解析接口返回的数据进行渲染
let separateReg=/(\/[^/]+\/)/g;
let msgText=msgObj.msgText;
let finalMsgText="";
// 将符合条件的字符串放到数组里
const resultArray=msgText.match(separateReg);
if(resultArray!==null){
for (let item of resultArray){
// 删除字符串中的/符号
item=item.replace(/\//g,"");
for (let emojiItem of this.emojiList){
// 判断捕获到的字符串与配置文件中的字符串是否相同
if(emojiItem.info===item){
const imgSrc=require(`../assets/img/emoji/${emojiItem.hover}`);
const imgTag=`<img src="${imgSrc}" width="28" height="28" alt="${item}">`;
// 替换匹配的字符串为img标签:全局替换
msgText=msgText.replace(new RegExp(`/${item}/`,'g'),imgTag);
}
}
}
finalMsgText=msgText;
}else{
finalMsgText=msgText;
}
msgObj.msgText=finalMsgText;
// 渲染页面
this.senderMessageList.push(msgObj);
// 修改滚动条位置
this.$nextTick(function () {
this.$refs.messagesContainer.scrollTop=this.$refs.messagesContainer.scrollHeight;
});
}
复制代码
通过每条消息的userID和vuex中的存储的当前用户的userID来判断当前消息是否为对方发送
<!--消息显示-->
<div class="messages-panel" ref="messagesContainer">
<div class="row-panel" v-for="item in senderMessageList" :key="item.msgId">
<!--发送者消息样式-->
<div class="sender-panel" v-if="item.userID===userID">
<!--消息-->
<div class="msg-body">
<!--消息尾巴-->
<div class="tail-panel">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-zbds30duihuakuangyou"></use>
</svg>
</div>
<!--消息内容-->
<p v-html="item.msgText"/>
</div>
<!--头像-->
<div class="avatar-panel">
<img :src="item.avatarSrc" alt="">
</div>
</div>
<!--对方消息样式-->
<div class="otherSide-panel" v-else>
<!--头像-->
<div class="avatar-panel">
<img :src="item.avatarSrc" alt="">
</div>
<!--消息-->
<div class="msg-body">
<!--消息尾巴-->
<div class="tail-panel">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-zbds30duihuakuangzuo"></use>
</svg>
</div>
<!--消息内容-->
<p v-html="item.msgText"/>
</div>
</div>
</div>
</div>
复制代码
最后更新时间: 2020年2月1日
// main.js 在插件配置里添加connectManually属性
// 开启手动调用 connect() 连接服务器
connectManually: true
复制代码
// 使用this.$connect(URL)方法
this.$connect(`${base.lkWebSocket}/${localStorage.getItem("userID")}`);
复制代码
// beforeDestroy生命周期中调用$disconnect方法
beforeDestroy() {
// 页面销毁时,断开连接
console.log("页面销毁,断开websocket连接");
this.$disconnect();
},
复制代码
// 开启json传输时使用sendObj进行消息发送
this.$socket.sendObj({
});
// 为开启json传输时,使用send()函数进行发送
this.$socket.send("");
复制代码
// src/store/index.js
state{
socket{
// 心跳消息发送时间
heartBeatInterval: 30000,
// 心跳定时器
heartBeatTimer: 0
}
}
mutations:{
// 连接打开
SOCKET_ONOPEN (state, event) {
Vue.prototype.$socket=event.currentTarget;
state.socket.isConnected=true;
// 连接成功时启动定时发送心跳消息,避免被服务器断开连接
state.socket.heartBeatTimer=setInterval(()=> {
const message="心跳消息";
state.socket.isConnected && Vue.prototype.$socket.sendObj({"code":200,"msg":message});
}, state.socket.heartBeatInterval);
},
// 连接关闭
SOCKET_ONCLOSE (state, event) {
state.socket.isConnected=false;
// 连接关闭时停掉心跳消息
clearInterval(state.socket.heartBeatTimer);
state.socket.heartBeatTimer=0;
console.log('连接已断开: ' + new Date());
console.log(event);
},
}
复制代码
品 | 51CTO技术栈(微信号:blog51cto)
最近,网传谷歌正考虑将其AI搜索最新成果置于付费墙之后。
作为全球搜索引擎市场的绝对霸主,谷歌的一举一动牵连甚广。众所周知,搜索领域的生态格局已多年未有剧变,自去年开始,大模型的流行、生成式AI的崛起却搅起了“一池春水”。
这一背景下,“领头羊”谷歌也不得不调整其在搜索方面的布局。如今,谷歌的新动作虽然还在酝酿阶段,但其透露出的信号已经让一些人开始质疑。
据外媒《金融时报》报道,谷歌正在内部进行讨论,是否将部分AI搜索功能转移到高级订阅服务之中。在媒体向谷歌发言人求证时,对方并未否认这一消息,但也没有透露任何细节。
事实上,与多数人预想中不同,谷歌在AI搜索问题上处于两难境地。
尽管微软在Bing搜索引擎中添加生成式AI功能并未显著改变市场份额,但要是在搜索查询中使用生成式AI技术,则有可能改变谷歌现有的以广告为主要收入的商业模式。
去年,谷歌推出了搜索生成体验(SGE)。与微软的必应将ChatGPT制作成“聊天”界面不同,SGE将搜索和生成式AI相结合,用户在搜索时可以获得一份单独的摘要“快照”答案和验证答案的网站链接。同时,谷歌也会明确标记这些回答为“Generative AI is experimental”,并与自然搜索结果进行区分。
当然,在AI生成的结果旁边也会插入广告。可是,从传统的搜索结果列表转变为这种新形式,不得不让人考虑,这是否会改变用户与广告互动的方式,进而影响到谷歌的收入结构。
尽管SGE尚未对谷歌的核心搜索引擎业务产生重大影响,但撇开任何潜在的对公司广告收入的影响不谈,由于AI技术对于资源的高度依赖性,将其推广给更多用户也可能带来高昂的成本。因此,SGE成为了一个极有可能被设置为付费墙后的高级功能。
同时,谷歌发言人也明确表示,他们会积极开发更多与AI相关的增值服务,但他们并没有计划推出无广告的搜索产品。
这一消息传出后,全球最大的高性能计算(HPC)解决方案提供商之一、德国企业Northern Data Group的首席运营官Rosanne Kincaid-Smith表示了不满。在她看来:
第一,谷歌所处的位置独特。它与OpenAI等专注于开发更多新事物的科技公司不同,谷歌拥有庞大的影响力和覆盖范围,且在搜索引擎市场占据主导地位,应当将AI搜索创新成果公之于众,“允许大众接触并使用AI所带来的变革性技术”。
第二,将AI搜索创新成果置于付费墙背后讨论的本质,其实涉及到AI伦理和数据治理问题。“我们所有在这个领域的人……都负有直接的责任,要确保AI的道德管理是他们提供给公众的服务包不可或缺的一部分,而不是需要额外付费的内容。”
先不论这次事件孰是孰非,通过观察谷歌的一系列动作可以发现,谷歌实质上是在努力寻找让人工智能与广告业务共生的方式。不能改变主要的盈利模式,但也不得不在功能上继续加码AI搜索。
“AI+搜索”从来不是一个新鲜议题。但是这一趋势从未像当下一样不可逆转。
随着搜索的AI化进程不断推进,AI搜索成为了全球商业市场上又一“兵家必争之地”,新旧势力纷纷亮剑,无论是想要做蛋糕的还是分蛋糕的,都在努力解锁新场景,寻找真正的杀手锏。
国外,微软一套组合拳强势出击,加持了GPT-4的Bing快速掀起风暴;被刺激到的谷歌随后推出SGE服务,将生成式AI功能整合到搜索中;初创公司Perplexity AI和Glean则分别立足C端和B端市场,在短期内成长为独角兽,实力不容小觑。
国内,同样是群雄林立。百度依托文心一言将旗下传统搜索升级为AI互动式搜索引擎;昆仑万维的天工AI搜索,360集团的360 AI搜索等产品也相继涌现;此外,淘宝启动了“淘宝问问”,抖音则在APP内测试“AI搜”,这波时潮为短视频、电商等领域的玩家提供了新解。
AI搜索赛道看起来竞争已趋白热化,但如果从更大的边际来看,在短期内,AI搜索不可能颠覆现有的搜索生态。
图片
截图来自StatCounter:2023年3月至2024年3月,全球搜索引擎市场份额基本没有太多变化,谷歌依旧一骑绝尘
首先,基于AI所做的创新有限。迄今而至,并没有出现一款真正的杀手级搜索产品。大厂在进行搜索革新时相对谨慎,大部分成熟的搜索产品并没有太多令人惊艳的突破。
再者,AI搜索的正确性有待提高。“真实可靠”是用户对搜索引擎的核心诉求,但在AI生成的产物在互联网上泛滥时,如何保障用户看到可信的搜索结果,仍旧是诸多企业有待解决的问题。
最后,用户惯性阻力巨大。相比AI搜索,传统搜索多年来已经根植于多数人的工作和生活中,路径依赖下人们还没有足够的动力从传统的搜索方式中转向。
曾在短期内迅速吸引了大量用户的小众搜索引擎Neeva仅仅维持了四年就停止运营,最终被Snowflake收购。
其创始人在宣布Neeva关闭的博客文章中写道:“在整个旅程中,我们发现构建搜索引擎是一回事,说服普通用户转向更好的选择则完全是另一回事。”
顺着Neeva的故事,其实我们可以清晰看到围绕搜索引擎构建的两条完全不同的发展之路。
路线1:谷歌模式
在线广告至今仍是谷歌最主要的收入来源。其中,搜索广告业务的地位至关重要。
根据Alphabet发布的2023财年第四季度财报,谷歌第四季度广告营收为655.17亿美元,同比增长11%;其中,核心搜索业务营收480.2亿美元,同比增长13%。
路线2:反谷歌模式
一些新兴的AI搜索企业更倾向于回归搜索本身的价值,让用户看到他们喜欢的内容,而不是更有“商业价值”的内容。
在产品形式上,AI搜索最重要的一点升级在于:用户终于不用在满屏广告中寻找答案了。这种“反谷歌”的搜索方式,决定了其主要收入来源不会是广告。
比如Neeva,其成立的初衷就在于其创始人Ramaswamy认为:从长远来看,以广告为基础的模式必然导致搜索结果的劣化。要想打造更好的搜索引擎,首先需要改变激励措施。
无独有偶,新兴搜索引擎Perplexity.ai、You.com同样依赖订阅来实现盈利,让用户为搜索服务本身付费。
在中国的一众搜索创业者中,有中国版Perplexity之称的秘塔AI搜索同样以“没有广告,直达结果”的slogan声名鹊起。
截图来自秘塔AI官网
据similarweb的统计数据显示,截至3月1日前28天,秘塔AI搜索网站的访问量高达478.3万次。不过目前尚不清楚,秘塔AI未来将采用什么样的商业模式。
综合来看两种路线,就发展现状来说,前者的优势几乎是碾压性的。曾一度被视为创业标杆的Perplexity如今也有了转舵的意向。
本来除了基础的免费版本,Perplexity还提供更为强大的Pro订阅服务。但这种模式并不足以支撑其发展。Perplexity 刚刚宣布,它正在将广告纳入其搜索结果中。
截图来自Perplexity官网
与原来的无广告模式相比,这种新模式显然为公司开辟了更多的收入渠道。分析人士认为,Perplexity决定在其平台中整合广告并非仅仅出于盈利考虑,更是一种维持并提升其服务的战略举措。
“投放广告能够为其提供持续改进平台、保持竞争优势所需的资源。然而,这一新特性引发了更多的问题,比如,如何在不损害用户所期待的质量与完整性前提下,将广告无缝融入搜索结果。”
AI作为变数介入搜索领域已是既定事实。
我们姑且将谷歌的新动作视为其寻求AI搜索商业化之路的投石问路之举。基于其互动形式与广告业务的天然矛盾,在没有找到更合适的盈利模式以前,谷歌必须踩着钢丝,让两者尽可能和谐共存。
而对于新兴的搜索初创企业,AI搜索成为了一个契机,让搜索真正回归其本色:帮助人们找到自己想要的页面,并避开途中的一切障碍。但是如何真正在固有格局中存活下来,实现可持续发展依然是首要问题。同时,屠龙少年是否会终成恶龙,也需要警惕。
对于新技术浪潮带来的影响,人们往往会高估未来一年的变化,而低估未来五年乃至十年的变化。新兴搜索厂商如何在传统市场杀出血路?真正的爆款AI搜索产品会是何种模样?全球搜索生态是否会重新洗牌?这些未解之谜,终将有时间给出答案。
https://www.theregister.com/2024/04/08/google_search_paywall
https://www.thepaper.cn/newsDetail_forward_24783786
https://www.woshipm.com/ai/6009702.html
https://www.singlegrain.com/blog/n/perplexity-ads/
https://www.duidaima.com/Group/Topic/IT/14562
想了解更多AIGC的内容,请访问:
51CTO AI.x社区
https://www.51cto.com/aigc/
来源: 51CTO技术栈
*请认真填写需求信息,我们会在24小时内与您取得联系。