前端

前端使用VUE的element组件,Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库。

第一个问题:前端excel的导入导出

因业务需要,需要对商品进行批量的导入导出,信息为商品信息,供货商或者运营部门可进行excel的批量导入,而不是一个个的输入商品信息。

具体从零开始,参考了如下文章:

https://www.cnblogs.com/guwufeiyang/p/13245875.html

使用的两个js

Blob.js

Export2Excel.js

引入之后Export2Excel.js可能有点问题,相比较原版,头部的引入:

第一步:需要安装三个依赖
npm install -S file-saver xlsx (这里其实安装了2个依赖)
npm install -D script-loade

使用了require(‘script-loader!file-saver’);方式进行。

fileReader.onload 函数 要有fileReader.readAsBinaryString(files.raw);方法。

本次实现的上传excel并不是将excel传到服务器上进行解析,而是将excel的内容在js中解析,最后将内容发送到后台继续处理。

所以excel的内容映射放在了JS端进行了操作。

excel的导出则是反向操作,将excel的表头与内容规定好,用前端已有的数据进行填充即可。

未解决问题:上传的时候,有图片上传,excel将填写图片地址,但地址之后上传之后才能生成。

第二个问题:文件下载

由于mall系统的请求返回数据都为json,且原项目是没有文件下载功能,当使用json请求时,返回的数据不能解析,使用string传输数据,前端也无法正常反转成二进制,保存的文件总是提示文件已损坏。所以请求使用了http原生的接收request,返回response,在本项目中,在respone拦截器中将返会的原生http的response分出来进行结果判断。

下载商品模板excel

通过

1
String url = this.getClass().getResource("/").getPath() + "file/" + fileName;

得到文件在服务器上的地址。将contentType返回值设置成application/octet-stream,则前端点击下载即可。

1
String contentType = "application/octet-stream;charset=utf-8";

前端:

1
window.URL.createObjectURL(new Blob([res]))

记得将请求的responseType: ‘arraybuffer’设置好。之后生成a元素触发点击。然后再删除a元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 下载文件调用
* @param 接口返回数据 文件名
*/

export function downloadFile(res, fileName) {
if (!res) {
return
}
/*res并不是array*/
if (window.navigator.msSaveBlob) { // IE以及IE内核的浏览器
try {
window.navigator.msSaveBlob(res, fileName) // res为接口返回数据,这里请求的时候已经处理了,如果没处理需要在此之前自行处理var data = new Blob([res.data]) 注意这里需要是数组形式的,fileName就是下载之后的文件名
// window.navigator.msSaveOrOpenBlob(res, fileName); //此方法类似上面的方法,区别可自行百度
} catch (e) {
console.log(e)
}
} else {
let url = window.URL.createObjectURL(new Blob([res]))
let link = document.createElement('a')
link.style.display = 'none'
link.href = url
link.setAttribute('download', fileName)// 文件名
document.body.appendChild(link)
link.click()
document.body.removeChild(link) // 下载完成移除元素
window.URL.revokeObjectURL(url) // 释放掉blob对象
}

第三个问题:输入框比较小,提示信息实现

在商品规格页面,当输入:定价、销售价、成本价、规格编码、规格条码时,由于本身输入框长度不够,当输入字段过长会导致看不到前面输入的是什么了,所以设计了一个Popover 弹出框进行实时展示输入的内容:

只不过是针对input组件:

1
2
3
<el-popover placement="right" title="规格编码" width="200" trigger="hover" :content="scope.row.specificationCode">
<el-input v-model="scope.row.specificationCode" slot="reference"></el-input>
</el-popover>

但这又带来另外的问题,因为设置的是trigger=”hover” ,所以当鼠标扫过的时候就会弹出提示,针对离得很近的表格输入来说,会影响到下一个表格的输入。所以后来删除了此功能。

第四个问题:上传照片与预览图片

还是由于mall系统的请求返回数据都为json,导致上传图片与预览图片数据不正确。原系统使用的是阿里云的oss所以只需要上传充成功后返回url即可。

问题解决吸取excel的经验,直接写原生的http请求,将照片数据直接传到后台,然后后台返回预览照片的url。

问题是前端请求的时候需要认证值,myHeaders: {Authorization: getToken()}, 使用 multiUpload时,可以设置请求头:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<el-upload
:action="url"
list-type="picture-card"
:file-list="fileList"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
:on-preview="handlePreview"
:limit="maxCount"
:headers="myHeaders"
:on-exceed="handleExceed"
>
<i class="el-icon-plus"></i>
</el-upload>

则可以正常上传照片。

问题在于预览的展示问题。当上传照片成功后,保存照片到服务器上(为了对接有赞的时候对照片进行压缩)。之后前端请求照片地址进行展示。

当返回例如http://localhost:8080/product/update/getPhotoFile/1599200758115.jpg地址的时候,会被后台拦截,因为没有设置请求头的认证字段,返回的是封装好的错误提示信息,是以application/json 格式返回,导致前台无法展示照片。并且请求不会到controller这一层,mall的security会拦截请求,所以需要设置请求白名单,并且后端也需要原生http接受请求,正确设置返回类型。

增加白名单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
secure:
ignored:
urls: #安全路径白名单
- /swagger-ui.html
- /swagger-resources/**
- /swagger/**
- /**/v2/api-docs
- /**/*.js
- /**/*.css
- /**/*.png
- /**/*.ico
- /webjars/springfox-swagger-ui/**
- /actuator/**
- /druid/**
- /admin/login
- /admin/register
- /admin/info
- /admin/logout
- /minio/upload
- /**/*.jpg
- /**/*.gif
- /**/*.bmp

添加照片jpg gif bmp的过滤,注意,白名单是大小写敏感的。JPG与jpg不同。

第五个问题:tinymce去除关闭/刷新网页时弹出对话框

mall的富文本使用了插件tinymce,但在el的流程中,最后一步添加富文本后,点击提交会弹出有未保存的数据。虽然数据是已经可以保存到数据库中,但可设置取消弹窗。

autosave_ask_before_unload: false

第六个问题:数值的校验问题

针对输入的金额数据,需要进行格式化,非金额直接转换为0,金额则转换为0.00格式

参考地址:http://www.qdxw.net/xwhtml/813.html

使用进位方式,如果是0.006,则返回0.01;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
export function number_format(number, decimals, dec_point, thousands_sep) {
/*数值转换函数
* 参数说明:
* number:要格式化的数字
* decimals:保留几位小数
* dec_point:小数点符号
* thousands_sep:千分位符号 传空为不设置千分位
* */
number = (number + '').replace(/[^0-9+-Ee.]/g, '');
var n = !isFinite(+number) ? 0 : +number,
prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep,
dec = (typeof dec_point === 'undefined') ? '.' : dec_point,
s = '',
toFixedFix = function (n, prec) {
var k = Math.pow(10, prec);
return '' + Math.ceil(n * k) / k;
};

s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
var re = /(-?\d+)(\d{3})/;
while (re.test(s[0])&&sep!="") {
s[0] = s[0].replace(re, "$1" + sep + "$2");
}

if ((s[1] || '').length < prec) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
}

更改了添加千分位的循环地方,当不想要千分的时候,传入thousands_sep=“”;但原方法会在数值超过千位时会无限循环,除非使用千分位。我在函数中的循环部分添加参数判断。如果传入为空字符串,则不添加千分位。

第一种使用方法:

当input触发函数 onblur时,将输入框的数值进行替换为返回值。

问题:

由于是在新增商品的商品规格中使用,在table中对每一行数据进行校验,一开始选择使用form的校验进行。但发现在写法上商品规格属于form的item的table。所以嵌套层过多,网上教程只涉及到循环form嵌套table,每一行属于item时,才可以。

最终每一行数据只能onblur才能校验以及重新赋值。

这时候遇到了vue的问题,Vue中数组元素被替换,页面没有动态展示,vue无法监测到,需要使用Vue.set实现更新。

后端

第一个问题:读取文件上传前端

将获取到的excel文件传到前端,使用原生http请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void getFile(HttpServletRequest request, HttpServletResponse response, String fileName)
{
/* this.getClass().getResource("/").getPath() 获取程序在服务器中的位置信息 */
LOGGER.info("下载商品的导入表格excel 入参fileName:" + fileName);
String url = this.getClass().getResource("/").getPath() + "file/" + fileName;
String realName = fileName; // 需要的文件名字
String downLoadPath = url; // 这个参数表示文件在服务器的存储路径
String contentType = "application/octet-stream;charset=utf-8";
try
{
request.setCharacterEncoding("UTF-8");
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
long fileLength = new File(downLoadPath).length();
response.setContentType(contentType);
response.setHeader("Content-disposition", "attachment; filename=" + new String(realName.getBytes("utf-8"), "ISO8859-1"));
response.setHeader("Content-Length", String.valueOf(fileLength));
bis = new BufferedInputStream(new FileInputStream(downLoadPath));
bos = new BufferedOutputStream(response.getOutputStream());
byte[] buff = new byte[2048];
int bytesRead;
while (-1 != (bytesRead = bis.read(buff, 0, buff.length)))
{
bos.write(buff, 0, bytesRead);
}
bis.close();
bos.close();
}
catch (Exception e)
{
LOGGER.error("读取文件失异常E" + e);
response.setStatus(500);
}
}

照片文件的读取,设置response.setContentType(“image/jpg”):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 展示照片
*
* @param request
* @param response
* @param fileName
*/
@Override
public void getPhotoFile(HttpServletRequest request, HttpServletResponse response, String fileName)
{
FileInputStream fis = null;
response.setContentType("image/jpg");
try
{
OutputStream out = response.getOutputStream();
String filePath = this.getClass().getResource("/").getPath() + "photos/";
File file = new File(filePath, fileName);
fis = new FileInputStream(file);
byte[] b = new byte[fis.available()];
fis.read(b);
out.write(b);
out.flush();
}
catch (Exception e)
{
response.setStatus(500);
LOGGER.error("getPhotoFile异常" + e);
}
finally
{
if (fis != null)
{
try
{
fis.close();
}
catch (IOException e)
{
LOGGER.error("getPhotoFile关闭文件流异常" + e);
}
}
}
}

第二个问题:在contrller之外的工具类中使用mapper

初始化static 本类。使用PostConstruct 初始化本类,之后在方法中使用该静态方法调用mapper;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class YouzanAPI
{
public static YouzanAPI Utils;

@PostConstruct
public void init()
{
Utils = this;
}
@Autowired
private PmsSkuStockMapper pmsSkuStockMapper;
......
/**
* 组装产品 将产品信息组装成有赞的格式
*
* @param tempP 需要组装的产品
* @return
*/
public YouzanRetailOpenSpuCreateResult createSpu(@NotNull PmsProduct tempP) throws Exception
{
...
List<PmsSkuStock> pmsSkuStockList = Utils.pmsSkuStockMapper.selectByExample(pmsSkuStockExample);
...
}
......
}

暂时整理这么多,end.