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

news2024/12/26 21:05:14

#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.coloradmin.cn/o/2254471.html

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

相关文章

高通---Camera调试流程及常见问题分析

文章目录 一、概述二、Camera配置的整体流程三、Camera的代码架构图四、Camera数据流的传递五、camera debug FAQ 一、概述 在调试camera过程中&#xff0c;经常会遇到各种状况&#xff0c;本篇文章对camera调试的流程进行梳理。对常见问题的提供一些解题思路。 二、Camera配…

软件/游戏运行提示xrnm.dll丢失无法继续执行怎么办?xrnm.dll缺少最佳解决方法

xrnm.dll 文件并不是一个标准的Windows系统文件&#xff0c;也不是广泛已知的第三方应用程序的一部分。因此&#xff0c;如果你遇到了提示 xrnm.dll 文件丢失或缺失的问题&#xff0c;这可能是由于特定软件或游戏的要求&#xff0c;或者是某种错误配置、恶意软件感染或其他问题…

流媒体之linux下离线部署FFmpeg 和 SRS

前言 用户对网络做了限制&#xff0c;只能访问指定的网址&#xff0c;和没网没啥区别&#xff0c;导致无法连接外网&#xff0c;无法获取安装包&#xff0c;还有一些编译需要的开源工具 用户需要用平台查看库房的海康摄像头实时监控&#xff0c;只能在库房里一台纯净的ubantu…

在LabVIEW中实现HARQ协议

HARQ&#xff08;Hybrid Automatic Repeat reQuest&#xff09;可以在LabVIEW中实现。HARQ是一种结合了前向纠错&#xff08;FEC&#xff09;和自动重传请求&#xff08;ARQ&#xff09;的技术&#xff0c;用于提高数据传输的可靠性&#xff0c;尤其是在无线通信和数据链路中。…

网络协议(TCP/IP模型)

目录 网络初识 网络协议 协议分层 协议拆分 分层 协议分层的优势 1.封装效果 2.解耦合 TCP/IP五层模型 协议之间配合工作&#xff08;详解&#xff09; 网络初识 网络核心概念&#xff1a; 局域网&#xff1a;若干电脑连接在一起&#xff0c;通过路由器进行组网。 …

PH热榜 | 2024-12-05

1. Oopsie 标语&#xff1a;用AI和会话回放调试Flutter和React Native应用 介绍&#xff1a;Zipy推出的Oopsie是一款你唯一需要的AI赋能移动端调试工具&#xff0c;它能提供▶️会话回放、&#x1f916;错误监控、&#x1f4a1;AI生成的概要分析&#xff0c;以及&#x1f525…

文化央企再一次声明

央企再次声明 中传国华&#xff08;北京&#xff09;科技有限公司&#xff0c;成立于2023年5月29日&#xff0c;原法定代表人曹忠喜&#xff0c;统一社会信用代码&#xff1a;91110117MACL4B9A91&#xff0c;我司中传世纪控股&#xff08;北京&#xff09;有限公司系该司的原股…

如何延长相机电池续航时间

如果你曾在拍摄过程中突然发现相机电池电量不足&#xff0c;就会知道那有多让人紧张和沮丧了。无论你是在拍摄小朋友的生日派对、家庭聚会&#xff0c;还是作为一名专业摄影师在工作&#xff0c;保持电池有电都是至关重要的。否则&#xff0c;你就有可能错过精彩瞬间&#xff0…

day06【入门】MySQL学习(3)完结!!!!

今日学习目标&#xff0c;mysql剩余的一小部分。开始接口自动化测试的学习。 目录 1、自关联 2、子查询 2.1 标量子查询 2.2 列子查询 2.3 表级子查询 2.4 作业 3、MySQL内置函数 3.1 concat字符串连接 3.2 length(str) 3.3 left字符串 3.4 right字符串 3.5 subs…

使用 Apache Commons IO 实现文件读写

在 Java 编程中&#xff0c;文件读写是常见的操作。虽然 Java 标准库提供了基本的文件 I/O 功能&#xff0c;但使用 Apache Commons IO 库可以进一步简化这些操作&#xff0c;提高开发效率。Apache Commons IO 是一个强大的工具库&#xff0c;提供了许多实用的类和方法&#xf…

9. 一分钟读懂“策略模式”

9.1 模式介绍 策略模式是一种行为型设计模式&#xff0c;用于在运行时灵活切换对象的行为或算法&#xff0c;它将算法封装为独立的类&#xff0c;使得它们可以互相替换&#xff0c;而不会影响使用这些算法的客户端代码。 策略模式的核心思想是&#xff1a;定义一系列可互换的算…

使用Oracle通过gateway连接MSSQL

环境概述 某医院的his系统Oracle数据库要和体检系统进行数据通讯&#xff0c;需要从Oracle能查到sqlserver的数据。本次通过Oracle gateway来解决此问题。 HIS服务器&#xff1a;windows server 2016数据库oracle11.2.0.4&#xff0c;假设IP是192.168.100.9 体检服务器&…

社区医疗服务可视化系统设计与实现

文末获取源码和万字论文&#xff0c;制作不易&#xff0c;感谢点赞支持。 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;…

Alibaba EasyExcel 导入导出全家桶

一、阿里巴巴EasyExcel的优势 首先说下EasyExcel相对 Apache poi的优势&#xff1a; EasyExcel也是阿里研发在poi基础上做了封装&#xff0c;改进产物。它替开发者做了注解列表解析&#xff0c;表格填充等一系列代码编写工作&#xff0c;并将此抽象成通用和可扩展的框架。相对p…

掌控时间,成就更好的自己

在个人成长的道路上&#xff0c;时间管理是至关重要的一环。有效的时间管理能够让我们更加高效地完成任务&#xff0c;实现自己的目标&#xff0c;不断提升自我。 时间对每个人都是公平的&#xff0c;一天只有 24 小时。然而&#xff0c;为什么有些人能够在有限的时间里做出卓…

十、JavaScript的应用

10.1 JavaScript概述 10.1.1 JavaScript简介 JavaScript是一种基于 对象(0bject) 和 事件驱动(EventDriven) 并具有安全性能的脚本语言&#xff0c;能够与HTML(超文本标记语言)、Java语言一起在Web页面中与web 客户交互&#xff0c;它无须经过先将数据传给服务器端(Server)、再…

服务器上的常见Linux命令教程

在管理服务器&#xff08;如香港服务器&#xff09;时&#xff0c;掌握常见的 Linux 命令 是非常重要的&#xff0c;它们可以帮助你高效地完成服务器管理任务&#xff0c;如文件操作、进程管理、用户管理、网络配置等。 以下是一个系统化的 Linux 常见命令教程&#xff0c;分为…

开发手札:Win+Mac下工程多开联调

最近完成一个Windows/Android/IOS三端多人网络协同项目V1.0版本&#xff0c;进入测试流程了。为了方便自测&#xff0c;需要用unity将一个工程打开多次&#xff0c;分别是Win/IOS/Android版本&#xff0c;进行多角色联调。 在Win开发机上&#xff0c;以Windows版本为主版…

2024 阿里云Debian12.8安装apach2【图文讲解】

1. 更新系统&#xff0c;确保您的系统软件包是最新的 sudo apt update sudo apt upgrade -y 2. 安装 Apache Web 服务器 apt install apache2 -y 3. 安装 PHP 及常用的扩展 apt install php libapache2-mod-php -y apt install php-mysql php-xml php-mbstring php-curl php…

Chromium网络调试篇-Fiddler 5.21.0 使用指南:捕获浏览器HTTP(S)流量(二)

概述 在上一篇文章中&#xff0c;我们介绍了Fiddler的基础功能和如何安装它。今天我们将深入探讨如何使用Fiddler来捕获HTTP请求&#xff0c;这是Fiddler的一个核心能力&#xff0c;对于前端开发者、测试人员以及安全研究人员来说非常有用。捕获HTTP请求可以帮助我们更好地理解…