按vue组件实例类型实现非侵入式国际化多语言翻译

#vue3##国际化##本地化##international#

web界面国际化,I18N(Internationalization,国际化),I11L(International,英特纳雄耐尔),L10N(Localization,本地化),显示文字多语言化是其主要内容。

浏览器的js提供了Intl全局对象,html提供了translate属性。

console.log(Intl,window.Intl);
<p translate="no">Don't translate this!</p>

某些浏览器插件支持一键“翻译网页”。其实现很简单,递归遍历找到<span><text>等文字标签,按通用字典对照翻译,并监听dom的变化,对弹出的内容或更新的内容也能即时翻译。这种一网打尽式的翻译,对某些wiki、doc、博客、新闻类网站比较适合,但是对一些ERP等管理系统,不太适合:

  1. 不精准。不该翻译的地方翻译了,比如用户输入的内容:产品编码、产品型号,Grid的cell内容...除非能用“translate='no'”把不翻译的dom标注得很完整。
  2. 不专业。翻译的字典是通用的,可能因为行业不同,牛头不对马嘴。插件能支持用我自己的字典?  因此,ERP这种管理系统,一般要有自己的多语言方案。

要做一个翻译方案,要考虑以下问题:

  1. 字典。字典格式(json,csv,xml)、字典的读取、解析、编辑。
  2. 国家语言标识。语言代码( Language Code)、国家代码( Country Code)、Windows Language Code Identifier (LCID),一般不再使用旧的Code Page(简体中文936,繁体中文950)。
  3. 语言切换。LCID存哪里。是否重加载页面。
  4. 翻译函数。高效的把一个文本字串,查找字典,翻译成对照文本。
  5. 翻译的时机。在何时调用翻译函数。

i18n(http://npmmirror.com/package/i18n,http://npmmirror.com/package/i18next)组件提供了一套简单的机制。 对vue框架,i18n对照有个vue-i18n(http://npmmirror.com/package/vue-i18n)。 i18n的实现了多语言化的基本机制,需要程序员配合,在合适的地方调用翻译函数。

我这里要讲的是一种非侵入式的,外挂式的多语言转换机制,比较适合已经完成的系统,突然要加多语言机制。 我的这篇老文章里(https://nodejs.blog.csdn.net/article/details/248218)谈到了这种方法,那是在delphi、c#开发的winform程序中,在web开发中,因为有了react、vue这样的框架实现了web组件化,因此也可以使用类似的方案。这个方案的主要思想是:为每个vue类注册一个翻译函数,在必要时把vue组件实例按其类的翻译函数来逐个翻译。

acroMLClassMethod.register('DataGrid',t_DataGrid);
acroMLClassMethod.register('GridColumn',t_GridColumn);
acroMLClassMethod.register('GridFilterButton',t_GridFilterButton);
acroMLClassMethod.register('Menu',t_Menu);
acroMLClassMethod.register('MenuItem',t_MenuItem);

翻译函数,如:v3-easyui组件的翻译函数,像这个样子:

function t_GridColumn(t,instWrap){let inst=instWrap.$;acroMLClassMethod.translateProps(t,inst.props,['title']);
}
function t_GridFilterButton(t,instWrap){let inst=instWrap.$;let items=instWrap.$data.items;if (items){for(let i=0;i<items.length;i++){let item=items[i];acroMLClassMethod.translateProps(t,item,['text']);}}
}

这个方案的几个特点:

  1. 外挂式。写页面的程序员一般情况下感觉它不存在。特殊的地方还是用显式的使用t函数,或标记translate='no'。
  2. 相对准确。相比浏览器的自动翻译,不会“误伤”。如DataGrid,会翻译column,footer的title,不会翻译到cell。相对于传统i18n机制显式的使用t函数,没那么精准,如:SelectBox下拉框,当选择的文本用于代码比较时是不能翻译的,还是需要显式的标记translate='no'或挂载beforeTranslate事件告知不翻译。
  3. 切换语言时能做到不重加载页面就刷新。传统i18n机制可能也能做到,只要t函数是响应式的,能响应locale的变化。但是,某些第3方组件包,其内置的一些文本字串,切换语言后,即使重加载了其对应的内置字典,也不会自动刷新。如devextreme-vue的DataGrid的noDataText。  

文无第一,武无第二,各种方案没有绝对的好坏,就像react与vue,暂时无法谁碾压谁,适合不同的场景和不同的团队。

这个方案的几个注意点:

  1. 如何从vue组件实例找到其类别。
  2. 注册类别还是注册类别名称。
  3. 何时调用翻译函数。
  4. 不同厂家的vue组件属性修改机制的差别。
  5. 切换语言时如何不重加载页面而刷新。
  6. 人为设定不翻译某些组件的机制。
  7. 切换语言时DDKey从哪里找。  

  1、如何从vue组件实例找到其类别

  • instWrap.$options.name
  • instWrap.$.constructor.name
  • instWrap.$.type.name  
translateCom(t,instWrap,isTranslateChildren=true){//console.log(instWrap);let inst=instWrap.$;let typeName=instWrap.$options.name;if (!typeName){if (inst.constructor && (inst.constructor.name!='Object')) typeName=inst.constructor.name;}if (!typeName) typeName=inst.type.name;if (!typeName){for(let i=0;i<classNameGetters.length;i++){typeName=classNameGetters[i](instWrap);if (typeName) break;}}//console.log(typeName,instWrap);let m;if (typeName){m=classMethods[typeName];}else{let {classID,method}=getClassMethod(inst.type);m=method;}if (m){if (instWrap.$attrs.translate!='no'){m(t,instWrap);}}if (isTranslateChildren){acroMLClassMethod.translateVNode(t,inst.subTree,isTranslateChildren);}},

如果从已知渠道拿不到类别名称,那再实现一个机制,让组件厂商告诉你如何获取。如:devextreme-vue组件,上面3个渠道是得不到组件类别名称的。只有特别处理:

acroMLClassMethod.registerClassNameGetter(function(instWrap){let className;if (instWrap.$_instance) className=instWrap.$_instance.NAME;if (!className) className=instWrap.widget;//if (!className) className=instWrap.$options.$_optionName;return className;
});  

2、注册类别还是注册类别名称

前面看到注册翻译函数时,类别都是使用的类别名称,如果使用类别,也是可以实现的,即:

acroMLClassMethod.register(DataGrid,t_DataGrid);
acroMLClassMethod.register(Menu,t_Menu);

但是,直接用类别,需要把组件加载到内存中,有几个坏处:

  1. 组件包的组件全部加载了。实际项目可能没有用到那么多组件,比如devextreme中的甘特图、枢纽分析Grid组件;
  2. 组件加载机制可能与外部不同。你可能是用一个包加载,外面可能是用分文件加载,这会导致相同组件在内存中有两份,可能导致组件内部逻辑错误。  
//加载方式1:
import DX from 'devextreme-vue';//加载方式2:
import {DxDataGrid,DxColumn,DxPager,DxPaging,DxGroupPanel,DxSearchPanel,DxSelection,DxFilterRow,DxScrolling} from 'devextreme-vue/data-grid';
import {DxTextBox,DxButton as DxTextBoxButton} from 'devextreme-vue/text-box';
import DxButton from 'devextreme-vue/button';
import DxCheckBox from 'devextreme-vue/check-box';
import DxColorBox from 'devextreme-vue/color-box';//加载方式3:
import DxButton from 'devextreme-vue/button.mjs';
import DxCheckBox from 'devextreme-vue/check-box.mjs';  

3、何时调用翻译函数

谢天谢地,vue3还保留了app.mixin。可以混入mounted、updated、unmounted函数到全部的vue组件中,这是监测vue组件生命周期的好地方。

​
import acroMLClassMethod from 'acroml/vue/acroML.ClassMethod.mjs';
import {YJEvent} from 'foil/util.yjEvent.mjs';
/*** 用insts来维护vue组件实例列表。* 因为当多语言切换时,从$root不知道如何遍历到DxDropDownBox模版中的DxDataGrid*/
let insts=[];
let isTranslating=false;class YJLocaleTranslator extends YJEvent{init(app){let self=this;app.mixin({mounted(){/*** app.mixin混入到任何组件的生命周期中。只翻译当前组件。* 注意:这里的this是被混入的vue组件实例,不是YJVueTranslator实例*///let inst=Vue.getCurrentInstance();//console.log('DOM mounted:',this.$options.name,this);let instWrap=this;setTimeout(function(){/*** 混入的函数是先于组件的函数执行。* 用setTimeout造成异步效果,等待让DxDataGrid实例的$_instance有值。 * 如果不用setTimeout,混入到mounted函数中,那时DxDataGrid实例的$_instance也不会有值。*/if (self.translateInst(instWrap)){insts.push(instWrap);}}, 0);},unmounted(){let index=insts.indexOf(this);if (index>=0) insts.splice(index,1);},updated(){/*** 翻译可能导致组件再更新,进入死循环。* update可能有很多原因,大多与显示翻译无关。* 组件更新时,可能把父组件文本属性给子组件,必须再次翻译。*/if (isTranslating) return;let instWrap=this;//console.log('beforeUpdate',instWrap);setTimeout(function(){isTranslating=true;try{self.translateInst(instWrap);}finally{/*** 用setTimeout造成异步效果,让“因为翻译引起的组件更新”不再进入翻译*/setTimeout(function(){isTranslating=false;},0);}},0);}});}translate(){let self=this;let t1=new Date();for(let i=0;i<insts.length;i++){let inst=insts[i];self.translateInst(inst);}let t2=new Date();console.log(`acroml translated ${insts.length} instances used ${t2.getTime()-t1.getTime()} ms.`);}translateInst(instWrap){let self=this;let ops={isTranslate:true};self.emit('beforeTranslate',instWrap,ops);if (!ops.isTranslate) return false;acroMLClassMethod.translateCom(t,instWrap,false);//acroMLClassMethod.translateVNode(t,inst.vnode,false);self.emit('afterTranslate',instWrap);return true;}
}let yjLocaleTranslator=new YJLocaleTranslator();export default yjLocaleTranslator;
export {yjLocaleTranslator}  

4、不同厂家的vue组件属性修改机制的差别

一般的组件,修改instWrap.$.props就可以,而且能做到不重新加载页面就刷新。如:ant-design-vue的Table:

function t_Table(t,instWrap){//console.log('t_Table',instWrap);let inst=instWrap.$;if (inst.props.columns){for(let i=0;i<inst.props.columns.length;i++){let column=inst.props.columns[i];acroMLClassMethod.translateProp(t,column,'title');}}acroMLClassMethod.translateProps(t,inst.props.locale);inst.props.locale={...inst.props.locale};
}

如:element-plus的ElTable:

function t_ElTable(t, instWrap){let inst=instWrap.$;//console.log(instWrap);acroMLClassMethod.translateProps(t,inst.props,['emptyText','confirmFilter','resetFilter','clearFilter','sumText']);let columns=instWrap.columns;if (columns){for(let i=0;i<columns.length;i++){acroMLClassMethod.translateProp(t,columns[i],'label');}}
}

但是,devextreme-vue比较特殊,这样修改instWrap.$.props无效或不会刷新(问了devexrpess厂家,说官方不支持切换语言后不重加载页面刷新)。因为它的核心组件包devextreme,也用于react和angular,而且对于DataGrid的内部组件,有prop和slot两种定义方式:

<DxDataGrid :show-borders="true":groupPanel="{visible:true,emptyPanelText:'File'}":searchPanel="{visible:true,placeholder:'Edit'}">
</DxDataGrid>
<DxDataGrid :show-borders="true"><DxSearchPanel :visible="true" placeholder='Edit' /><DxGroupPanel :visible="true" emptyPanelText='File' />
</DxDataGrid>

因此,它定义了一套API机制来更新属性。

instWrap.$_instance.option();
instWrap.$_instance.state();
instWrap.$_instance._getDefaultOptions();

因此,我们使用这些API来翻译组件:

function wrap_transDevextremeCom(proc){return function(t,instWrap){let inst=instWrap.$_instance;if (!inst){return;}let state;if (inst.state) state=inst.state();let defaultValues=inst._getDefaultOptions();let tag=switchLocale();switchLocale('en');let defaultDDKeys=inst._getDefaultOptions();switchLocale(tag);options=inst.option();//if (inst.NAME=='dxDataGrid') console.log(2,options);/*** inst.option()返回的类型是object,不知为何修改placeholder后用inst.option(options)放回去不生效。* 但是inst.option({'placeholder':'上海'});或inst.option('placeholder','上海');会刷新。* 必须在修改前用JSON.parse(JSON.stringify(options))或options={...options}处理一下。* 有一个警告:error:35 W0001 - dxPopup - 'closeOnOutsideClick' option is deprecated in 22.1. Use the 'hideOnOutsideClick' option instead.*///options=JSON.parse(JSON.stringify(options));options={...options};inst.beginUpdate();try{proc(t,instWrap,options,defaultDDKeys,defaultValues);inst.option(options);/**设置预设参数可能丢掉了状态,恢复 */if (state) inst.state(state);}finally{inst.endUpdate();}}
}

DxDataGrid的翻译就这样写了,也能做到切换语言时不重加载页面而刷新:

function t_DxDataGrid(t,instWrap,options,defaultDDKeys,defaultValues){//console.log('t_DxDataGrid',instWrap);/*** 不要企图通过instWrap.$.props去修改参数,因为DxDataGrid的某些参数有2种写法:* (1)<DxDataGrid searchPanel="{visible:true,placeholder:'search...'}"><DxDataGrid>* (2)<DxDataGrid><DxSearchPanel visible='true' placeholder='search...' /><DxDataGrid>* 用instWrap.$.props时,第2种写法的参数是找不到的。* 不能通过coumns属性更新column的caption,因为<DxDataGrid><DxColumn dataField='ID />这种写法,columns属性为空* 必须通过method:columnOption来更新column的caption*/acroMLClassMethod.translateProps(t,options,['hint','noDataText'],defaultDDKeys,defaultValues);acroMLClassMethod.translateNodeProps(t,options,['loadPanel'],['text']);acroMLClassMethod.translateNodeProps(t,options,['columnChooser'],['emptyPanelText','title']);acroMLClassMethod.translateNodeProps(t,options,['columnChooser','search','editorOptions'],['placeholder']);acroMLClassMethod.translateNodeProps(t,options,['columnFixing','texts'],['fix','leftPosition','rightPosition','unfix']);acroMLClassMethod.translateNodeProps(t,options,['editing','texts'],['addRow','cancelAllChanges','cancelRowChanges','confirmDeleteMessage','confirmDeleteTitle','deleteRow','editRow','saveAllChanges','saveRowChanges','undeleteRow','validationCancelChanges']);acroMLClassMethod.translateNodeProps(t,options,['export','texts'],['exportAll','exportSelectedRows','exportTo']);acroMLClassMethod.translateNodeProps(t,options,['filterPanel','texts'],['clearFilter','createFilter','filterEnabledHint']);acroMLClassMethod.translateNodeProps(t,options,['filterRow'],['applyFilterText','betweenEndText','betweenStartText','resetOperationText','showAllText']);acroMLClassMethod.translateNodeProps(t,options,['groupPanel'],['emptyPanelText'],defaultDDKeys,defaultValues);acroMLClassMethod.translateNodeProps(t,options,['grouping','texts'],['groupByThisColumn','groupContinuedMessage','groupContinuesMessage','ungroup','ungroupAll']);acroMLClassMethod.translateNodeProps(t,options,['headerFilter','texts'],['cancel','emptyValue','ok']);acroMLClassMethod.translateNodeProps(t,options,['pager'],['inforText']);acroMLClassMethod.translateNodeProps(t,options,['searchPanel'],['placeholder'],defaultDDKeys,defaultValues);acroMLClassMethod.translateNodeProps(t,options,['sorting'],['ascendingText','clearText','descendingText']);acroMLClassMethod.translateNodeProps(t,options,['summary','texts'],['avg','avgOtherColumn','count','max','maxOtherColumn','min','minOtherColumn','sum','sumOtherColumn']);let columns=options['columns'];if (columns){for (let i=0;i<columns.length;i++){let column=columns[i];acroMLClassMethod.translateProp(t,column,'caption',column.dataField);acroMLClassMethod.translateProps(t,column,['trueText','falseText']);}}
}  

5、切换语言时如何不重加载页面而刷新

切换语言后,简单粗暴的方式是window.reload()重加载页面。但是全部状态都丢失。一般组件的内置文本,如devextreme-vue的DataGrid的noDataText,groupPanel的emptyPanelText,它是组件创建时按当前内置字典获取的,不会在字典重加载后自动刷新。但是本方案,因为切换语言时,会重翻译全部vue组件实例,所以可以刷新。  

6、人为设定不翻译某些组件的机制

如果不翻译某个组件,可以设置translate属性为no,也可以实现一个beforeTranslate事件机制,如:

yjLocaleTranslator.on('beforeTranslate',function(instWrap,ops){//console.log(instWrap,ops);/*** 不翻译某个组件实例的方法:* (1)给组件设置一个attr:translate='no'* (2)挂载beforeTranslate事件,阻止某些组件翻译。*/if (instWrap===self.$refs.dropdownbox) ops.isTranslate=false;
});  

7、切换语言时DDKey从哪里找

切换语言前,vue组件的显示字串已经翻译成当前语言了,如:"OK"翻译成了简体“确认”,再切换语言到繁体时,哪里去找OK的词?很简单,翻译前把OK作为DDKey保存起来,直接保存在vue组件实例上。

translateProp(t,obj,propName,propDefaultDDKey,propDefaultValue){if (!obj) return;/*** 没有属性值,没必要处理?* 不行,element-plus的ElTableColumn是不给label就不显示。* 但是devextreme-vue的DxColumn的caption可能没设置,但要按data-field来翻译显示。* DxDataGrid的groupPanel的emptyPanelText如果没有设置,使用官方的字典翻译。*///if (!obj[propName]) return;if (!obj._acroml_DDKeys) obj._acroml_DDKeys={};let DDKey=obj._acroml_DDKeys[propName];if (!DDKey){DDKey=obj[propName];/**保存原始的DDKey */if (!DDKey) DDKey=propDefaultDDKey;else if (DDKey===propDefaultValue) DDKey=propDefaultDDKey;}/**如果DDKey是undefine就让obj[propsName]保持undefined(这样组件会使用其当前字典),如果赋值''就显示空白了 */if (DDKey){let newValue=t(DDKey);if (newValue===DDKey){if (propDefaultValue) newValue=propDefaultValue;}if (obj[propName]!=newValue) obj[propName]=newValue;if (newValue!=DDKey) obj._acroml_DDKeys[propName]=DDKey;}else if (propDefaultValue && obj[propName]!=propDefaultValue) obj[propName]=propDefaultValue;},

注:写文章时,涉及到vue组件版本:

  • vue,3.5.12
  • devextreme-vue,23.2.8
  • v3-easyui,3.0.14
  • ant-design-vue,4.2.3
  • element-plus,2.8.1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/62843.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux C/C++编程之静态库

【图书推荐】《Linux C与C一线开发实践&#xff08;第2版&#xff09;》_linux c与c一线开发实践pdf-CSDN博客《Linux C与C一线开发实践&#xff08;第2版&#xff09;&#xff08;Linux技术丛书&#xff09;》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com…

网际协议(IP)与其三大配套协议(ARP、ICMP、IGMP)

网际协议&#xff08;Internet Protocol&#xff0c;IP&#xff09;&#xff0c;又称互联网协议。是OSI中的网络层通信协议&#xff0c;用于跨网络边界分组交换。它的路由功能实现了互联互通&#xff0c;并从本质上建立了互联网。网际协议IP是 TCP/IP 体系中两个最主要的协议之…

flutter 多语言 国际化 flutter Intl的使用方法

一使用 flutter Intl Android studio需要添加插件 flutter Intl 路径 File>>Settings>>Plugins>>Marketplace>>flutter Intl>>Install 安装插件重新启动Android studio Android studio 创建一个flutter测试的新项目 在项目文件中配置 ** 添加…

uniapp实现加密Token并在每次请求前动态更新(vue、微信小程序、原生js也通用!)

导语&#xff1a;在Web开发中&#xff0c;Token作为一种身份验证的机制&#xff0c;被广泛应用于前后端交互过程中。本文将为大家介绍如何在每次请求前动态设置加密的Token&#xff0c;并在请求一次后使Token值加1&#xff08;或其他动态改变的逻辑&#xff09;&#xff0c;从而…

IDL学习笔记(二)IDL处理卫星数据

IDL处理卫星数据 HDF文件数据集属性通用属性 常用HDF4操作函数常用的HDF5操作函数读取HDF文件的一般步骤 HDF4文件读取-----数据信息查询HDF4文件读取示例-----目标数据TIFF输出 HDF文件 数据集属性 数据集名称&#xff0c;是“&#xff1a;”前的一部分&#xff0c;不是long_…

论文阅读——量子退火Experimental signature of programmable quantum annealing

摘要&#xff1a;量子退火是一种借助量子绝热演化解决复杂优化问题的通用策略。分析和数值证据均表明&#xff0c;在理想化的封闭系统条件下&#xff0c;量子退火可以胜过基于经典热化的算法&#xff08;例如模拟退火&#xff09;。当前设计的量子退火装置的退相干时间比绝热演…

TCP/IP协议簇自学笔记

摘抄于大学期间记录在QQ空间的一篇自学笔记&#xff0c;当前清理空间&#xff0c;本来想直接删除掉的&#xff0c;但是感觉有些舍不得&#xff0c;因此先搬移过来。 曾经&#xff0c;我只知道socket函数能进行网络间数据的通信&#xff0c;知道tcp/ip协议也是用来进行网络数据…

JAVA OPCUA 服务端开发,客户端连接会话监听和订阅事件监听

前言 关于使用milo开源库,开发opc ua服务器,有网友咨询如何设置服务端如何监听客户端的连接或断开事件,如何监听客户端发起订阅事件的代码实现,于是我完善了这部分的空缺整理整了这篇教程,希望能解决有同样需求,但是遇到困难的网友!因为milo没有官方文档的教程且网上详…

三数之和 Leecode 2024/12/02

思路&#xff1a;1. 找到所有不重复下标的整数组合。2. 在所有组合中找到和为0的组。 暴力解法超时&#xff01;&#xff01;&#xff01; public static List<List<Integer>> threeSum(int[] nums) {int num_len nums.length;HashSet<List<Integer>&g…

c++领域展开第一幕——入门基础(命名空间、iostream、缺省参数、函数重载、nullptr、inline(内联函数))超详细!!!!

文章目录 前言一、c的第一个程序二、命名空间2.1 namespace 的价值2.2 namespace 的定义2.3 命名空间的使用 三、c的输入和输出四、缺省参数五、函数重载六、nullptr七、inline总结 前言 今天小编带着大家进入c的大门&#xff0c;虽然c难&#xff0c;但好事多磨&#xff0c;一起…

word表格 转换html 并导出.docx和图片(vue)

一、复制word表格转换成html代码 &#xff0c;在页面中显示 并且能导出 1、导出使用了htmlDocx插件 //1、使用html-docx-js 插件 npm install html-docx-js --save npm install html2canvas //2、在页面中引入 import htmlDocx from html-docx-js/dist/html-docx.js; import …

我们项目要升级到flutter架构的几点原因

一、探索 Flutter打造卓越移动应用的新时代框架 在移动应用开发的世界里&#xff0c;Flutter已经成为了一个炙手可热的话题。诞生于Google的怀抱&#xff0c;Flutter以其独特的优势和理念&#xff0c;正在引领一场全球范围内的应用开发 ** 。本文将深入探讨Flutter项目的特点、…

DM-VIO(ROS)+t265配置运行记录(ubuntu18.04+ros melodic)

在工作中需要对DM-VIO算法进行测试&#xff0c;于是配置并记录了一下&#xff1a; 首先运行ros接口的dm-vio&#xff0c;一定要先配置源码 https://github.com/lukasvst/dm-vio在这个网址把源码下载下来并解压&#xff0c;并安装一下依赖&#xff1a; sudo apt-get install …

基于Java Springboot成人教育APP且微信小程序

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 微信…

基于Java Springboot个人财务APP且微信小程序

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 微信…

PotPlayer 最新版本支持使用 Whisper 自动识别语音生成字幕

PotPlayer 最新版本支持使用 Whisper 自动识别语音生成字幕 设置使用下载地址 设置 使用 下载地址 https://www.videohelp.com/software/PotPlayer

【软考网工笔记】网络基础理论——传输层

IPSec协议 Internet协议安全性是一种开放标准的框架结构&#xff0c;通过使用加密的安全服务以确保在Internet协议&#xff08;IP&#xff09;网络上进行保密而安全的通讯。 工作在OSI模型的第三层网络层上&#xff0c;使其在单独使用时适于保护基于TCP或UDP的协议&#xff0…

代码随想录算法训练营第三十四天 | 62.不同路径 | 63. 不同路径 II | 343.整数拆分 | 96.不同的二叉搜索树

Day 34 总结 自己实现中遇到哪些困难 动态规划初始化逻辑&#xff1a;初始化哪些依赖并不存在的格子如何解决当前问题&#xff1f; 通过分解成1个&#xff0c;2个或者多个子问题如何解决子问题&#xff1f;再继续分解&#xff0c;直到要解决一个根部问题&#xff0c;通过根部问…

TinyXML2的一些用法

TinyXML2 原始字符串字面量 TinyXML21. XML文档操作1.1 LoadFile(const char* filename)1.2SaveFile(const char* filename)1.3RootElement()1.4Parse(const char* xml) 2.元素操作2.1 FirstChildElement(const char* name nullptr)2.2 NextSiblingElement(const char* name …

Fastify装饰器:增强你的路由处理功能加入日志

Fastify以其出色的性能和扩展性脱颖而出。装饰器是Fastify提供的一个强大功能&#xff0c;它允许开发者在不修改核心代码的情况下&#xff0c;向请求&#xff08;Request&#xff09;和响应&#xff08;Response&#xff09;对象添加自定义属性和方法。本文将通过一个简单的示例…