最近采用了Guns-Separation的框架,这里写一些本人遇到的问题或者是同事遇到的问题(遇到的问题很多,但是由于我没玩过spring boot这些框架很多都是小白问题,所以总是下不定决心写,但是又觉的反而是小白才最需要解答),在此为采用这个框架的其他人提供一些方便,废话不多现在开始吧!
guns文档地址
由于框架的作者推荐采用idea进行开发,本人采用的也是idea
搭建中遇到的问题
1.出现不了GunsApplication项目运行标志,或者有个红色的×
项目如果正常情况下,将会出现一个绿色图标的GunsApplication的这个字样出来,如上图所示。
如果项目开启来,连这个字样都没有出现,或者是如果出现了但是有个红色的叉叉在右上角,那么可能的错误是
1.JDK的版本不对(采用这个框架需要JDK1.8及以上)
配置正确的jdk,配置完成之后记得重启下电脑
2.maven没有进行配置或者配置错误
必须采用自己的maven,而不是idea自带的maven地址。再检查一下maven的setting.xml文件是否配置好,具体配置网上一堆也不多说了。
3.项目打开之后才进行maven配置
这样也是不起效果的,可以看项目的依赖是不是只有jdk而没有maven的,解决办法为把项目删掉,重新来一遍。
4.以上都正确,但是在项目过程中改变过电脑的JDK
该有的依赖都有,该配置的都配置了,但是@SpringBootApplication突然开始爆红,解决办法 删掉本地文件夹中Maven仓库 repository\org\springframework\boot 目录下的spring-boot-autoconfigure 文件夹,然后右键工程maven ->Reload project
查看idea的jdk:点击左上角的file–settings
查看maven的配置
2.idea报JDK不存在
明明安装了JDK,但是idea非得说JDK不存在。最大的可能性是你把jdk的包名给改了,例如本来应该是jdk1.8.0_131 你改成了jdk8。或者采用的是非安装版本的JDK,最简单的办法就是重新下一个安装版的JDK
3 报错,读取配置文件的时候出现’@’ that cannot start any token. (Do not use @ for indentation)
代码肯定是没错误的,出现这个错的原因一般是maven没有正确加载,找到pom文件,选择maven–reload 重新加载一下就ok啦
项目的结构和数据的流动
一个功能模块实现分为前台实现和后台的实现。
guns的前端文档
guns作者的前端结构介绍
前台开发主要涉及到的是-web下的src中的api和view这两个文件夹。
后台一般涉及到六个文件夹
controller:控制层,接受前台的请求参数,并转发给service
entity:实体类
enums:异常信息
mapper:与数据库进行交互的
param:前台参数类,并且设定不为空等的数据校验
service:业务逻辑层,进行后台的逻辑处理
页面中的数据流动
前台: 控件----》触发事件-----》调用api中的发送参数和请求到后台
后台:controller接收请求将接收的参数传递到service—》service接收参数,进行逻辑处理调用mapper的方法进行获取数据-----》mapper与数据库交互获取数据返回给service—》service接收数据并将数据返回给controller—》controller获取到返回的数据,并返回给前台
前台:前天接收到返回值,之后进行处理并展示
开发中遇到的问题
(这个框架用到的控件是Ant Design Vue,所以遇到问题的话可以去查看它的文档,基本上想要的功能都能找到 Ant Design Vue官方文档)
1.时间控件没办法进行正常的赋值或者后台没办法正确接收选择的日期
<a-date-picker placeholder="" @change=“onChange1” style=“width: 100%” v-model=“procuretime” />
采用这个控件,需要知道的是,点击这个控件选中的日期其实返回的不是日期的String,因此我们需要进行类型的转化Moment.js
首先需要引入方法: import moment from ‘moment’
日期转化为字符串采用的是moment(date).format(‘YYYY-MM-DD’)
而把字符串转为日期
moment(date, ‘YYYY-MM-DD’)
2.前台访问后台,但是后台总是拿不到前台的数据,或者后台报访问类型错误
第一个问题:前台确实传递数据给后台,但是后台拿不到,那么可能是api里面的类型设置错误,仔细观察get方法和post方法,参数名称是不一样的哟,post的方法是data,而get是params
第二个问题:如果报什么该方法不是post方法啥的,很明显应该是访问的类型对不上,这时候需要去api中看设定的访问类型,再去看控制器中采用的注解是post还是get。如下图所示,采用的是get方法,那么控制器中采用的注解就是**@GetMapping**,同理那么采用post方法,那么应该采用的就是**@PostMapping**
3.向自定义的mapper传递map参数,无法进行调用
mapper接口,传递了一个map
List<DocOrderDetailsResult> skulist(@Param("map") Map<String, Object> map);
mapper.xml里面需要用 参数名.key 的方式来调用
select * from table where orderno=#{map.orderno}
4.动态加载antdesign组件
需求,根据后台传回来的类型,当前显示的组件需要显示不同的类型,例如后台传回来1,那么当前的框是文本,传过来2那么需要是选择框。 由于antdesign已经在项目中注册过的,我们是随意可以引用的,所以不需要自己注册组件了。
最关键的是 component 这个组件,is是用来绑定具体的组件类型。
参考的文档:
参考1
参考2
页面布局
<a-col :md="6" :sm="24">
<a-form-item label="动态的框" :labelCol="labelCol" :wrapperCol="wrapperCol">
<component :is="current"></component>
</a-form-item>
</a-col>
script中为了测试用,所以偷懒的直接将变量写死,当写死的值从a-input变为a-select,页面显示的框也会从文本框变为下拉选择框
current:'a-select'
效果
5.为该框架配置servelet
有时候还是会用到servelet比如说有些代码比较复杂,是从老项目移植过来的,又懒得改,就需要配置一下servlet了,配置servlet总共有三种方法,最推荐的还是采用注解的办法,由于我只试过其中的两种,所以我也就写两种。参考的博客
参考的博客
首先要在最外面的那个pom.xml里面配置依赖
<!-- Servelt应用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
</dependencies>
注解法:只要在servlet上面加上@WebServlet注解就行,然后定义一下路径就行,最后别忘了在启动类上面增加扫描servlet的注解。比较推荐采用注解法,因为这样的话是可以在servlet中读取配置文件信息的。
servlet
@WebServlet(urlPatterns = "/print1")
public class PrintService extends HttpServlet {
启动类上增加@ServletComponentScan,并且定义扫描的路径,也就是刚刚创建的servlet的包路径
@ServletComponentScan("cn.stylefeng.guns.modular.service")
public class GunsApplication {
注入法:servelet上并不需要任何特殊的操作,只要在启动类增加一个方法。
@Bean //一定要加,不然这个方法不会运行
public ServletRegistrationBean getServletRegistrationBean() { //一定要返回ServletRegistrationBean
ServletRegistrationBean bean = new ServletRegistrationBean(new PrintService()); //放入自己的Servlet对象实例
bean.addUrlMappings("/print1"); //访问路径值
return bean;}
6.当前模块的service想要调用另一个模块的service的接口方法,但是报错
想要实现联级删除,所以在当前的删除方法中需要调用其他模块的删除方法,但是一直出现错误,看了眼控制台分明是连对方的删除方法都没走进去。
错误的原因:在引用对方的接口时候,没有在上面增加 @Resource注解正确做法如下图所示
7.读取yml配置文件的相关内容
yml的配置文件是采用树形结构的,以空格作为分级,同个级别的一定是要左对齐的。以下图为例,有三级结构
spring-datasource-username(以username为例)
1.@value
有时候我们需要去读取相关的配置,比如需要知道当前连接的数据库的用户名什么,也就是说我们需要获取username这个变量的值,如果只需要获取配置文件中一两个变量的话,那么可以采用@Value注解。直接在变量的定义中加上该注解,变量会自动被塞上相应的值,如username这个变量,里面就会被塞上root这个值,之后直接使用就行。
但是使用@Value这个注解有个要求,当前的类必须存在容器中,但是不用担心如果按照guns的结构来,控制器啥的都是可以采用的。但是采用servlet的需要注意,servlet的配置需要采用注解法,才能得到配置文件的值。
2.配置类,但我没试过,需要去看参考的博客
8.静态文件的放置(html,js,打印的模板等)
静态文件放置public文件夹下面,以report.html为例,访问的路径为
/report.html,直接采用相对路径就行。
参考的博客
9.修改默认插入字段(添加人,添加时间,修改人,修改时间)
虽然guns里面为每个实体类都继承了一个父类,父类里面有createTime,createUser等四个字段,但是有可能我们需要的不仅仅是这四个字段又或者虽然有这四个字段,但是字段名不一致。所以接下来就是探究
首先看实体类,我们发现都集成了一个叫做BaseEntity的类。点进继承的类里面,我们发现就是那四个默认插入的字段。
看到所继承的类,很显然默认插入是通过 @TableField(fill = FieldFill.INSERT)和 @TableField(fill = FieldFill.UPDATE)注解进行实现的。前者指定插入的时候塞入默认值,后者指定更新的时候插入默认值。但是光光添加注解是不够的,我们还需要找到具体实现插入的语句是在哪里。然后就找到了CustomMetaObjectHandler
CustomMetaObjectHandler的文件位置
研究下CustomMetaObjectHandler的添加时候自动注入的具体代码,逻辑其实很简单,先判断一下这个实体类有没有相关的字段createUser和createTime如果没有的话就不作处理,如果有的话塞入当前的用户和当前的时间。
所以要实现自己的逻辑,就可以自定义一个类进行继承(推荐自己建立一个类),然后修改CustomMetaObjectHandler里面的逻辑。
10.表格(Table)控件的问题
ant design vue官方文档
在这个框架中作者一般采用的是S-table,这个应该是作者对ant design vue里面的table进行了二次封装,s-table和a-table唯一的不同点在于数据源的赋值,s-table采用的是:data="loadData"而a-table采用的是:data-source=“data”。两个table显示的效果也有差别,S-table可以显示分页的样式,用户可以在界面上选择分页的条数,而a-table默认是十条分页,用户没办法在界面上修改。
所以一般选择s-table进行展示表格的展示,当然如果不需要自行选择分页的话那么a-table完全可以的,因为两者没有其他的差别了。
1.嵌套子表格(动态子表)
可展开表格在官方文档里面有所涉及,但是可惜的是,官方文档里面的子表信息是固定写死的,但是实际项目中子格的信息肯定是根据主表的信息查询出来的。采用的逻辑就是主表查询数据的时候就将子表的数据一并查出,然后将主表中的数据通过数据源赋值给子表。
其中子表中最重要的两句代码
slot=“expandedRowRender”
slot-scope="text"
第二句其实完整的可以采用 slot-scope="record, index, indent, expanded"
第一个参数就代表是父亲的数据,所以实现了父亲数据的获取。
如以下代码为例,父表格通过loadData这个方法去获取数据,而子表格通过
slot=“expandedRowRender”
slot-scope=“record”
:data-source="record.docOrderDetailsResultList"
这三句话实现了子表格的动态展示。
slot-scope获取到了主表的record,之后将record中的docOrderDetailsResultList当做数据源给子表。
<s-table
ref="table"
size="small"
:columns="columns"
:data="loadData"
:alert="true"
:rowKey="(record) => record.orderno"
>
<a-table
size="small"
:columns="innerColumns"
slot-scope="record"
:data-source="record.docOrderDetailsResultList"
:rowKey="(record) => record.orderlineno+record.orderno"
slot="expandedRowRender"
>
</a-table>
</s-table>
从以上的逻辑不难看出,其实这是一个主从表的查询,因此实体类的设计,最主要的就是父类中需要有个子类的集合,毕竟一条头信息对应对应多条明细的信息。主从表的查询可以采用mabatis中的collection标签进行,但是在此处不展开讲解。
public class DocOrderHeaderResult implements Serializable {
private String ordertypename;
private String sostatusname;
private String grossweight;
private String cubic;
ArrayList<DocOrderDetailsResult> docOrderDetailsResultList;
}
2.表格的全部展开和全部收起
有了嵌套子表格,这时候会发现用户不想要一条一条的展开数据,而是想要一键展开子表格的功能和一键收起子表格的功能,那么这个功能如何去实现呢。首先,这个功能当然是在主表中进行实现的,然后具体是什么API,当然还是去看官方的文档。当当当当--------就是它expandedRowKeys,这个属性接收的是一个String的数组,存的是需要展开的行的唯一值。什么叫行的唯一值?其实就是 :rowKey="(record) => record.orderno"中设置的值,很显然我设置的是数据中的orderno这个字段,每一行的orderno都是唯一的,所以可以基于这个值来找到对应的行。所以只要一键展开的时候就需要传入所有的orderno就行,而一键关闭的时候只要清空这个数组里面的数据就行。
如下图所示,expandedRowKeys的值由loadDataTest2来决定。
<s-table
ref="table"
size="small"
:columns="columns"
:data="loadData"
:alert="true"
:expandedRowKeys="loadDataTest2"
:rowKey="(record) => record.orderno"
>
<a-table
size="small"
:columns="innerColumns"
slot-scope="record"
:data-source="record.docOrderDetailsResultList"
:rowKey="(record) => record.orderlineno+record.orderno"
slot="expandedRowRender"
>
</a-table>
</s-table>
首先在加载数据的时候,先把所有的orderno存放到 loadDataTest1这个数组当中。
loadData: parameter => {
return page(Object.assign(parameter, this.queryParam)).then((res) => {
res.data.rows.forEach((item, index) => {
var ret=item.orderno;
this.loadDataTest1.push(ret)
})
return res.data
})
}
然后全部展开,就将loadDataTest1的值赋值给loadDataTest2,如果要全部关闭那么清空loadDataTest2就行。这个loadDataTest1其实相当于一个缓存的作用,这样就不必每次都遍历一遍数据来获取orderno了。
// 全部展开
unFoldAll (record) {
this.loadDataTest2 = this.loadDataTest1
},
// 全部收起
collapseAll () {
this.loadDataTest2 = []
}
完成全部展开和全部收起的功能后,你会惊讶的发现,为什么表格自带的每一行展开和每一行收起好像没反应了,其实很简单,因为这时候自带的展开收起功能它获取的状态反了,点击+的时候实现的是关闭,点击-的时候是展开,所以看上去就没有效果了。这时候就需要重写图标的点击事件,也就是expand。这个事件有个回调函数,Function(expanded, record),会给我们两个参数,第一个expanded是布尔值,true指展开,false代表关闭,第二个是当前的行的数据。所以逻辑很简单还是套用上面的逻辑,展开的时候把当前行的orderno传给loadDataTest2 ,而关闭的时候清空一下loadDataTest2。
// 单个展开
iconClick (expanded, record) {
if(expanded){
this.loadDataTest2 = [ record.orderno ]
}else {
this.loadDataTest2 = [ ]
}
}
但是这个还是有点小问题的,就是一次点开一个,点开另一个的时候,之前的那个会被关闭,但是想要解决很简单,就是每次点开事件就是当前的orderno拼接到loadDataTest2 ,然后关闭的时候只移除当前的orderno就行
常用的功能
1.上传EXCEL
页面采用a-upload控件 action是缓存文件的地址吧,没搞懂反正一定要是能连通的才行,真正执行发送后台的功能,我采用的是handleChange的方法,在这个方法中判断是否文件成功,成功然后采用js中的daoru进行发送到后台,后台接收到文件,通过easypoi将excel文件里面的内容映射为实体类,然后再用实体类进行插入操作。
实体类与excel的映射 主要通过这个注解进行
@Excel(name=“ASN编号”,width = 35.0)
private String asnno;
easypoi的教程如下:
教程
excel如图所示
页面中的部分代码
<a-upload
name="file"
:multiple="true"
action="http://192.168.1.15:8080/api/docAsnHeader/daoru"
@change="handleChange"
>
<a-button> <a-icon type="upload" /> Click to Upload </a-button>
</a-upload>
<script>
import { add, daoRu } from '@/api/modular/docasnheader/docAsnHeaderManage'
handleChange (info) {
if (info.file.status === 'done') {
console.log(info.file.originFileObj)
var files = new FormData()
files.append('userfile', info.file.originFileObj) // 单个文件上传
daoRu(files).then((res) => {
console.log('111')
})
}
}
</script>
对应的api的js中的daoru
export function daoRu (parameter) {
return axios({
url: '/docAsnHeader/daoru',
method: 'post',
data: parameter,
enctype: 'multipart/form-data'
})
控制器中的
@PostMapping("/docAsnHeader/daoru")
@BusinessLog(title = "doc_asn_header_导入", opType = LogAnnotionOpTypeEnum.EDIT)
public String daoru(@RequestParam MultipartFile userfile) throws Exception {
System.out.println("进入导入");
log.info("文件名:"+userfile.getOriginalFilename());
return docAsnHeaderService.daoru(userfile);
}
后台具体实现,实体类上面的
@Excel(name=“ASN编号”,width = 35.0)
private String asnno
将会决定excel中ASN编号,对应到实体类中的asnno中
@Override
public String daoru(MultipartFile userfile) throws Exception {
//测试 将上传的文件写在本地
// InputStream inputStream=userfile.getInputStream();
// byte[] b = userfile.getBytes();
// String path = "C:/Users/Zhang/Desktop/111.xlsx";
// File file=new File(path);
// FileOutputStream fileOutputStream=new FileOutputStream(file);
// fileOutputStream.write(b);
// fileOutputStream.close();
ImportParams params = new ImportParams();
params.setHeadRows(1);//设置标题行为1,因为excel有一行是中文标题
List<DocAsnHeader> docAsnHeaders = ExcelImportUtil.importExcel(userfile.getInputStream(), DocAsnHeader.class,params);
docAsnHeaders.forEach(System.out::println);//仅仅做了输出操作,没进行插入
return null;
}
2.下载EXCEL
下载相较于上传而言是比较简单的,后台就是普通的查询,然后将查询到的数据返回给前台,前台有个控件和数据进行绑定,点击会自动将数据转换成excel让用户下载的。
3.mybatis-plus自定义sql语句
框架中虽然很多sql语句都不必在写了,但是稍微复杂一点的sql语句比如多表关联啊,还是需要人工手动去写的,下面就介绍一下如何去定义sql语句。一个完整的modular应该有以下六个包,而自定义sql最重要的事mapper这个文件下面的编写。
首先去mapper—mapping----文件名.xml
可以看到我的id写的事getRecord,这个id在这个xml中必须是唯一的,因为mapper接口是根据这个id来对应的。
resultType 指定的是返回的类型。
trim 我这里采用的是where的类型,我有三个条件,他会根据条件的个数自动的拼上where和and之类的连接符。比如第一个条件前面加上where 第二个条件前面加上and,如果一个条件都没有不会加where之类的。
if 来判断是否满足条件,满足条件的情况下才执行
#{} 用来标识变量,但是它自动会为变量填上单引号
${} 用来标识变量,不会自动添加单引号
<select id="getRecord" resultType="cn.stylefeng.guns.modular.wms.asn.docasnheader.model.result.DocAsnHeaderResult">
select * from doc_order_header
left join doc_order_details on doc_order_header.id=doc_order_details.id
<trim suffixOverrides="AND|OR" prefixOverrides="AND|OR" prefix="WHERE">
<if test="orderno != null and orderno != '' ">AND doc_order_header.orderno LIKE concat ("%", #{orderno},"%") </if>
<if test="ordertype != null and ordertype != '' ">AND doc_order_header.ordertype LIKE concat ("%", #{ordertype},"%") </if>
<if test="sostatus != null and sostatus != '' ">AND doc_order_header.sostatus = #{sostatus} </if>
</trim>
ORDER BY doc_order_header.addtime desc
</select>
mapper接口
参数的名字,类型,返回值都需要和xml文件里面定义的是对应的才行。
List<DocAsnHeaderResult> getRecord(String orderno,String ordertype,String sostatus);
具体方法的调用在service接口实现类里面,通过this.baseMapper.方法名 去调用
private List<DocAsnHeaderResult> getRecord(DocOrderDetailsParam docOrderDetailsParam){
return this.baseMapper.getRecord("11","22","33");
}
这样就完成了sql的定义以及调用。
4.显示根据查询结果绘制PDF并且显示
由于这个功能在老项目里面已经实现了,写在后端的servlet上的,所以主要的就是如何在guns里面写servelet程序。
我采用的是注入的方式进行配置的
servelet的定义,文件位置就和以前创建controller什么的一样,放在modular的位置下面就行的。但是创建完不要忘记配置
后台servlet代码,用到的jar包如图所示
/**
* 一个service实现
*
* @author stylefeng
* @date 2020/4/9 18:11
*/
public class ExampleService extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("lllll");
String modelName = req.getParameter("reportName");
if(modelName==null||"".equals(modelName)) return;
System.out.println("进来了reportName = "+modelName);
Map<String,Object> map = new HashMap<String,Object>();
int i = 1;
while(req.getParameter("key"+i)!=null &&!"".equals(req.getParameter("key"+i))){
map.put(req.getParameter("key"+i), req.getParameter("value"+i));
i ++;
}
resp.setCharacterEncoding("UTF-8");
resp.setContentType("application/pdf");
Connection conn = null;
OutputStream outputStream = resp.getOutputStream();
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url="jdbc:mysql://192.168.1.201:3306/wms?serverTimezone=UTC";
conn = DriverManager.getConnection(url, "root", "123456");
String filepath="D:/reportModule/"+modelName+".jasper";
//加载模板
File reportFile = new File(filepath);
JasperReport report = (JasperReport) JRLoader.loadObject(reportFile);
//数据库数据填充报表
System.out.println(map.toString());
JasperPrint jprint = JasperFillManager.fillReport(report, map,conn);
System.out.println("1");
//导出报表
JRPdfExporter exporter = new JRPdfExporter(DefaultJasperReportsContext.getInstance());
exporter.setExporterInput(new SimpleExporterInput(jprint));
exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(outputStream));
SimplePdfExporterConfiguration configuration = new SimplePdfExporterConfiguration();
configuration.setCreatingBatchModeBookmarks(true);
exporter.setConfiguration(configuration);
// File file=new File("D:/reportModule/test.pdf");
//
// JRPdfExporter exporter = new JRPdfExporter();
// exporter.setParameter(JRExporterParameter.JASPER_PRINT, jprint);
// exporter.setParameter(JRExporterParameter.OUTPUT_FILE,file);
try {
exporter.exportReport();
} catch (JRException e) {
throw new ServletException(e);
}
} catch (Exception e){
e.printStackTrace();
}finally{
if(conn != null){
try {
conn.close();
conn = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
outputStream.flush();
outputStream.close();
outputStream = null;
}
}
}
}
servlet配置,在GunsApplication这个启动类里面添加如下方法,否则没办法访问
@Bean //一定要加,不然这个方法不会运行
public ServletRegistrationBean getServletRegistrationBean() { //一定要返回ServletRegistrationBean
ServletRegistrationBean bean = new ServletRegistrationBean(new ExampleService()); //放入自己的Servlet对象实例
bean.addUrlMappings("/print1"); //访问路径值
System.out.println("配置了");
return bean;}
前端代码,一定还是要用项目调用后台的通用方法进行调用就是采用api的那种方法,因为采用其他的比如直接访问地址的话会显示无权限的,没有进行token验证啥的。
print1(){
print1(this.queryParam1).then((res) => {
console.log(res)
var binaryData = [];
binaryData.push(res);
var url =window.URL.createObjectURL(new Blob(binaryData, {type:"application/pdf"}));
window.open(url)
}).catch((err) => {
this.$message.error('删除错误:' + err.message)
})
}
api
export function print1 (parameter) {
return axios({
url: '/print1',
method: 'get',
params: parameter,
headers: { 'Content-Type': 'application/pdf' },
responseType: 'arraybuffer'
})
5 国际化
实现中英文切换,主要用到了vue-i18n插件。
分离版本的vue是2.6,所以我下载了旧版本的vue-i18n。
如果要下载指定版本的话,
npm install vue-i18n@8.18.0
下载最新版本
npm install vue-i18n
下载vue-i18n的时候会说没有eslint所以也下载
npm install eslint@^5.0.0
下载完毕后进行配置
main.js
....一堆impoert
import VueI18n from 'vue-i18n'
Vue.use(VueI18n) //添加挂载
const i18n = new VueI18n({
locale: 'zh-CN', // 语言标识, 通过切换locale的值来实现语言切换,this.$i18n.locale
messages: {
'zh-CN': require('../public/lang/cn'), // 中文语言包
'en-US': require('../public/lang/en')// 英文语言包
}
})
new Vue({
router,
store,
i18n, //添加这个
created: bootstrap,
render: h => h(App)
}).$mount('#app')
引入的两个语言包我放在public 目录下
cn.js格式
export const m = {
index: {
query: '查询',
language: '中文'
}
}
en.js的格式
export const m = {
index: {
query: 'query',
language: 'English'
}
}
进行语言的切换在UserMenu.vue里面新增一个菜单项
<a-menu-item key="5" @click="changeLanguage">
<router-link :to="{ name: 'center' }">
<a-icon type="setting" />
<span>{{$t('m.index.language')}}</span>
</router-link>
</a-menu-item>
点击事件执行的方法
changeLanguage () {
console.log('进来了')
console.log(this.$i18n.locale)
if (this.$i18n.locale === 'zh-CN') {
this.$i18n.locale = 'en-US' // 关键语句
} else {
this.$i18n.locale = 'zh-CN' // 关键语句
}
}
点击如下图所示
调用变量的方法根据我给的语言包的格式对照看:
插值表达式: {{$t('m.index.query')}}
例子1: <span> {{$t('m.index.language')}} </span>
例子2:<a-button type="primary" "> {{$t('m.index.query')}} </a-button>
属性中使用:
<span v-text="$t('m.index.query')"></span>
js使用:this.$t('m.index.query')
例子:this.$message.success(this.$t('m.index.query'))