一、说明

  最近要做文件上传,在网上找了很久都没有一个全面的示例,特此记录下来分享给大家。

  1.文件上传接口可按照springboot默认实现,也可用commons-fileupload组件,本示例使用springboot默认文件上传 2.最后也有commons-fileupload组件接口示例

  2.重点在前端JS实现(也可以使用ajax上传),参考了网上大量上传文件显示进度条博客以及技术方案,在此做了一个统一的总结,方便后续使用

  3.这仅仅是一个示例,大家可根据实际需要改进。

二、前端代码

<!DOCTYPE html>
<html>
<meta charset="UTF-8" />
<head>
<title>文件上传</title>
<link href="https://cdn.bootcss.com/bootstrap/3.3.2/css/bootstrap.css"
rel="stylesheet">
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
</head>
<body class="container">
<br />
<span id="time"></span>
<div class="row">
<input class="btn btn-info btn-xs" type="file" name="file" /><br />
<div class="col-lg-5"
style="padding-left: 0; padding-right: 0; margin-bottom: 0px;">
<div class="progress progress-striped active" style="display: ">
<div id="progressBar" class="progress-bar progress-bar-success"
role="progressbar" aria-valuemin="0" aria-valuenow="0"
aria-valuemax="100" style="width: 20%"></div>
</div>
</div>
<!-- 显示上传速度 -->
<div id="showInfo" class="col-lg-2">0KB/s</div>
</div>
<!-- 显示文件信息 -->
<div id="showFieInfo" class="row">
<label name="upfileName"></label><br />
<label name="upfileSize"></label><br />
<label name="upfileType"></label><br />
</div>
<div class="row">
<input class="btn btn-success btn-xs" type="button" name="upload" value="上传" />
<input class="btn btn-success btn-xs" type="button" name="cancelUpload" value="取消" />
</div>
</body>
<script type="text/javascript">
var fileBtn = $("input[name=file]");
var processBar= $("#progressBar");
var uploadBtn=$("input[name=upload]");
var canelBtn=$("input[name=cancelUpload]");
var ot;//上传开始时间
var oloaded;//已上传文件大小
fileBtn.change(function() {
var fileObj = fileBtn.get(0).files[0]; //js获取文件对象
if (fileObj) {
var fileSize = getSize(fileObj.size);
$("label[name=upfileName]").text('文件名:' + fileObj.name);
$("label[name=upfileSize]").text('文件大小:' + fileSize);
$("label[name=upfileType]").text('文件类型:' + fileObj.type);
uploadBtn.attr('disabled', false);
}
});
// 上传文件按钮点击的时候
uploadBtn.click(function(){
// 进度条归零
setProgress(0);
// 上传按钮禁用
$(this).attr('disabled', true);
// 进度条显示
showProgress();
// 上传文件
uploadFile();
});
function uploadFile(){
var url ="/to/upload";
var fileObj = fileBtn.get(0).files[0];
if(fileObj==null){
alert("请选择文件");
return;
}
// FormData 对象
var form = new FormData();
form.append('file', fileObj); // 文件对象
// XMLHttpRequest 对象
var xhr = new XMLHttpRequest();
//true为异步处理
xhr.open('post', url, true);
//上传开始执行方法
xhr.onloadstart = function() {
console.log('开始上传')
ot = new Date().getTime(); //设置上传开始时间
oloaded = 0;//已上传的文件大小为0
}; xhr.upload.addEventListener('progress', progressFunction, false);
xhr.addEventListener("load", uploadComplete, false);
xhr.addEventListener("error", uploadFailed, false);
xhr.addEventListener("abort", uploadCanceled, false);
xhr.send(form); function progressFunction(evt) {
debugger;
if (evt.lengthComputable) {
var completePercent = Math.round(evt.loaded / evt.total * 100)
+ '%';
processBar.width(completePercent);
processBar.text(completePercent); var time = $("#time");
var nt = new Date().getTime(); //获取当前时间
var pertime = (nt-ot)/1000; //计算出上次调用该方法时到现在的时间差,单位为s
ot = new Date().getTime(); //重新赋值时间,用于下次计算 var perload = evt.loaded - oloaded; //计算该分段上传的文件大小,单位b
oloaded = evt.loaded; //重新赋值已上传文件大小 //上传速度计算
var speed = perload/pertime;//单位b/s
var bspeed = speed;
var units = 'b/s';//单位名称
if(speed/1024>1){
speed = speed/1024;
units = 'k/s';
}
if(speed/1024>1){
speed = speed/1024;
units = 'M/s';
}
speed = speed.toFixed(1);
//剩余时间
var resttime = ((evt.total-evt.loaded)/bspeed).toFixed(1);
$("#showInfo").html(speed+units+',剩余时间:'+resttime+'s');
}
} //上传成功后回调
function uploadComplete(evt) {
uploadBtn.attr('disabled', false);
console.log('上传完成')
}; //上传失败回调
function uploadFailed(evt) {
console.log('上传失败' + evt.target.responseText);
} //终止上传
function cancelUpload() {
xhr.abort();
} //上传取消后回调
function uploadCanceled(evt) {
console.log('上传取消,上传被用户取消或者浏览器断开连接:' + evt.target.responseText);
} canelBtn.click(function(){
uploadBtn.attr('disabled', false);
cancelUpload();
})
}
function getSize(size) {
var fileSize = '0KB';
if (size > 1024 * 1024) {
fileSize = (Math.round(size / (1024 * 1024))).toString() + 'MB';
} else {
fileSize = (Math.round(size / 1024)).toString() + 'KB';
}
return fileSize;
}
function setProgress(w) {
processBar.width(w + '%');
}
function showProgress() {
processBar.parent().show();
}
function hideProgress() {
processBar.parent().hide();
}
</script>
</html>

效果:

三、对上传代码进行组件化封装

UploadCommon.js

/**
* 上传文件公共组件
*
* @param url 上传地址
* @param processBar 进度条 jquery获取的页面组件
* @param speedLab 显示上传速度Label jquery获取的页面组件
* @param uploadBtn 上传按钮 jquery获取的页面组件
* @param cancelBtn 取消上传按钮 jquery获取的页面组件
* @param callBack 上传完成回调函数 上传完成后的回调函数,可以不传
* @author
* @returns
*/
function UploadCommon(url, processBar, speedLab, uploadBtn, cancelBtn, callBack){ function init() {
// 每次回调监测上传开始时间
var startTime = null
// 已上传文件大小
var oloaded = null
var xhr = new XMLHttpRequest()
function setProgress(w) {
processBar.width(w + '%');
processBar.text(w + '%');
}
function progressMonitor(evt){
if (evt.lengthComputable) {
var completePercent = Math.round(evt.loaded / evt.total * 100)
setProgress(completePercent)
var nowTime = new Date().getTime()
// 计算出上次调用该方法时到现在的时间差,单位为s
var pertime = (nowTime - startTime) / 1000
          // 重新赋值时间,用于下次计算
startTime = new Date().getTime()
// 计算该分段上传的文件大小,单位b
var perload = evt.loaded - oloaded
// 重新赋值已上传文件大小,用以下次计算
oloaded = evt.loaded // 上传速度计算,单位b/s
var speed = perload / pertime
var bspeed = speed
// 单位名称
var units = 'bit/s'if (speed / 1024 > 1) {
speed = speed / 1024
units = 'Kb/s'
}
if (speed / 1024 > 1) {
speed = speed / 1024
units = 'Mb/s'
}
speed = speed.toFixed(1);
// 剩余时间
var resttime = ((evt.total - evt.loaded) / bspeed).toFixed(1)
speedLab.html(speed + units + ',剩余时间:' + resttime + 's')
}
}
// 上传成功后回调
function uploadComplete(evt) {
uploadBtn.attr('disabled', false)
var status = evt.currentTarget.status
if (status == 401) {
alert('请登录后再上传')
return
}
if (status == 403) {
alert('无权限操作')
return
}
if (status != 200) {
alert('上传异常,错误码' + status)
return
}
var response=JSON.parse(evt.currentTarget.response)
if (response.code!='200') {
alert('上传处理异常' + response.msg)
return
}
console.log('上传成功')
if ( callBack != null && typeof callBack != 'undefined') {
callBack()
}
}; // 上传失败回调
function uploadFailed(evt) {
alert('上传处理失败' + evt.target.responseText)
}
// 终止上传
function cancelUpload() {
xhr.abort()
}
// 上传取消后回调
function uploadCanceled(evt) {
alert('文件上传已终止:' + evt.target.responseText)
}
// 添加取消上传事件
cancelBtn.click(function() {
uploadBtn.attr('disabled', false)
cancelUpload();
})
this.uploadFile = function(formData) {
// 上传按钮禁用
uploadBtn.attr('disabled', true);
setProgress(0)
// true为异步处理
xhr.open('post', url, true)
// 上传开始执行方法
xhr.onloadstart = function() {
console.log('开始上传')
// 设置上传开始时间
startTime = new Date().getTime()
// 已上传的文件大小为0
oloaded = 0
}
xhr.upload.addEventListener('progress', progressMonitor, false)
xhr.addEventListener("load", uploadComplete, false)
xhr.addEventListener("error", uploadFailed, false)
xhr.addEventListener("abort", uploadCanceled, false)
xhr.send(formData);
} this.setProgressValue=function(w){
processBar.width(w + '%')
processBar.text(w + '%')
}
}
return new init()
}

调用

$(document).ready(function() {
var addVersionBtn=$('#addVersionBtn') //<button>
var cancelUploadBtn=$('#cancelUploadBtn') //<button>
var fileInput=$("#filewareFile") //<input>
var processBar = $("#progressBar"); //div
var fileNameLab=$("label[name=upfileName]") //<label>
var fileSizeLab=$("label[name=upfileSize]") //...
var fileTypeLab=$("label[name=upfileType]") //...
var speedLab=$("#showSpeed") //<label> var url='/api/file' //获取文件上传实例
var upload=UploadCommon(url,processBar,speedLab,addVersionBtn,cancelUploadBtn,initPageInfo) // 文件选择框变更事件
fileInput.change(function() {
var fileObj = fileInput.get(0).files[0]; // js获取文件对象
if (fileObj) {
var fileSize = getSize(fileObj.size);
fileNameLab.text('文件名:' + fileObj.name);
fileSizeLab.text('文件大小:' + fileSize);
fileTypeLab.text('文件类型:' + fileObj.type);
addVersionBtn.attr('disabled', false);
}
}); // 点击上传固件事件
addVersionBtn.click(function(){
var versionInfo=$('#version').val()
var file = fileInput.get(0).files[0]
var strategyInfo=$('#versionType').val()
if(file==null){
alert("固件文件不能为空")
return
}
if(versionInfo==''){
alert("版本号不能为空")
return
}
if(strategyInfo==''){
alert("升级策略不能为空")
return
} // 创建提交数据
var formData = new FormData();
formData.append('firmFile', fileInput.get(0).files[0]);
formData.append('version', versionInfo);
formData.append('strategy', strategyInfo); // 上传文件
upload.uploadFile(formData)
})
});

四,服务端接口

1.springboot默认实现

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/>
</parent> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 添加Swagger2依赖,用于生成接口文档 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
<!--end-->
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

application.yml

server:
port: 8080
tomcat:
uri-encoding: UTF-8
application:
name: demo
thymeleaf:
encoding: UTF-8
cache: true
mode: LEGACYHTML5
devtools:
restart:
enabled: true
http:
multipart:
maxFileSize: 500Mb
maxRequestSize: 500Mb
location: D:/tmp
debug: false

接口:

@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) {
if (file == null || file.isEmpty()) {
return "file is empty";
}
// 获取文件名
String fileName = file.getOriginalFilename();
// 文件存储路径
String filePath = "D:/data/" + UUID.randomUUID().toString().replaceAll("-", "") + "_" + fileName;
logger.info("save file to:" + filePath);
File dest = new File(filePath);
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
try {
file.transferTo(dest);
return "success";
} catch (Exception e) {
e.printStackTrace();
}
return "fail";
}

启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.transaction.annotation.EnableTransactionManagement; @SpringBootApplication
@EnableTransactionManagement
public class Application extends SpringBootServletInitializer { @Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
} public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

2.使用commons-fileupload上传组件

application.yml

server:
port: 8080
tomcat:
uri-encoding : UTF-8
spring:
application:
name: svc-demo
thymeleaf:
encoding: UTF-8
cache: false
mode: LEGACYHTML5
debug: false

pom .xml

  <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--添加文件上传支持-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!--添加html5支持-->
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

进程类

public class Progress{
private long bytesRead; //已读取文件的比特数
private long contentLength;//文件总比特数
private long items; //正读的第几个文件 public long getBytesRead(){
return bytesRead;
} public void setBytesRead(long bytesRead){
this.bytesRead = bytesRead;
} public long getContentLength() {
return contentLength;
} public void setContentLength(long contentLength) {
this.contentLength = contentLength;
} public long getItems() {
return items;
} public void setItems(long items)\{
this.items = items;
}
}

监听类

@Component
public class FileUploadProgressListener implements ProgressListener{ private HttpSession session;
public void setSession(HttpSession session){
this.session=session;
Progress status = new Progress();//保存上传状态
session.setAttribute("status", status);
}
@Override
public void update(long bytesRead, long contentLength, int items) {
Progress status = (Progress) session.getAttribute("status");
status.setBytesRead(bytesRead);
status.setContentLength(contentLength);
status.setItems(items); } }

文件上传处理类

public class CustomMultipartResolver extends CommonsMultipartResolver{
// 注入第二步写的FileUploadProgressListener
@Autowired
private FileUploadProgressListener progressListener; public void setFileUploadProgressListener(FileUploadProgressListener progressListener){
this.progressListener = progressListener;
} @Override
public MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException{
String encoding = determineEncoding(request);
FileUpload fileUpload = prepareFileUpload(encoding);
     //fileUpload.setFileSizeMax(1024 * 1024 * 500);// 单个文件最大500M
//fileUpload.setSizeMax(1024 * 1024 * 500);// 一次提交总文件最大500M
progressListener.setSession(request.getSession());// 问文件上传进度监听器设置session用于存储上传进度
fileUpload.setProgressListener(progressListener);// 将文件上传进度监听器加入到 fileUpload 中
try{
List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
return parseFileItems(fileItems, encoding);
} catch (FileUploadBase.SizeLimitExceededException ex) {
throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
} catch (FileUploadException ex){
throw new MultipartException("Could not parse multipart servlet request", ex);
}
}
}

控制器

@RestController
public class FileController{
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return "文件为空";
     }
// 获取文件名
String fileName = file.getOriginalFilename();// 文件上传后的路径
// 文件上传后的路径
String filePath = null;
try{
filePath = new File("").getCanonicalPath() + "/tmp/uploadFile/";
} catch (IOException e){
e.printStackTrace();
}
//存储路径
String tagFilePath = filePath + CommonUtil.getCurrentTime() + fileName;
File dest = new File(tagFilePath);
// 检测是否存在目录
if (!dest.getParentFile().exists()){
dest.getParentFile().mkdirs();
}
try{
file.transferTo(dest);
} catch (IllegalStateException e){
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
return fileName + "上传失败";
}
}

启动类

//注意取消自动Multipart配置,否则可能在上传接口中拿不到file的值
@EnableAutoConfiguration(exclude = { MultipartAutoConfiguration.class })
@SpringBootApplication
public class Application extends SpringBootServletInitializer{ //注入自定义的文件上传处理类
@Bean(name = "multipartResolver")
public MultipartResolver multipartResolver() {
CustomMultipartResolver customMultipartResolver = new CustomMultipartResolver();
return customMultipartResolver;
} @Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application){
return application.sources(Application.class);
} public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

springboot带有进度条的上传的更多相关文章

  1. JS原生上传大文件显示进度条-php上传文件

    JS原生上传大文件显示进度条-php上传文件 在php.ini修改需要的大小: upload_max_filesize = 8M    post_max_size = 10M    memory_li ...

  2. Springmvc+uploadify实现文件带进度条批量上传

    网上看了很多关于文件上传的帖子,众口不一,感觉有点乱,最近正好公司的项目里用到JQuery的uploadify控件做文件上传,所以整理下头绪,搞篇文档出来,供亲们分享. Uploadify控件的主要优 ...

  3. Jquery.Uploadify实现批量上传显示进度条 取消 上传后缩略图显示 可删除

    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="UpLoad.aspx.cs&q ...

  4. js 文件异步上传 显示进度条 显示上传速度 预览文件

    通常文件异步提交有几个关键 1.支持拖拽放入文件.2.限制文件格式.3.预览图片文件.4.上传进度,速度等,上传途中取消上传.5.数据与文件同时上传 现在开始笔记: 需要一个最基础的元素<inp ...

  5. 一个利用 Parallel.For 并行处理任务,带有进度条(ProgressBar)的 WinForm 实例(下)

    接着上一篇:一个利用 Parallel.For 并行处理任务,带有进度条(ProgressBar)的 WinForm 实例(上) 直接贴代码了: using System; using System. ...

  6. 带有进度条的WebView

    带有进度条的WebView 本篇继于WebView的使用 效果图 自定义一个带有进度条的WebView package com.kongqw.kbox.view; import android.con ...

  7. Android 中带有进度条效果的按钮(Button)

    安卓中带有进度条效果的按钮,如下图: 1.布局文件如下activity_main.xml <RelativeLayout xmlns:android="http://schemas.a ...

  8. SpringBoot集成百度UEditor图片上传后直接访问404解决办法

    SpringBoot项目上传图片一般是上传至远程服务器存储,开发过程中可能会上传至当前项目的某个静态目录中,此时就会遇到这个问题,文件在上传之后直接访问并不能被访问到,必须重新加载项目. 首先分析一下 ...

  9. springboot整合ueditor实现图片上传和文件上传功能

    springboot整合ueditor实现图片上传和文件上传功能 写在前面: 在阅读本篇之前,请先按照我的这篇随笔完成对ueditor的前期配置工作: springboot+layui 整合百度富文本 ...

随机推荐

  1. Git下载安装及设置详细教程

    Git下载安装及设置详细教程 一.安装前准备   1. 廖雪峰老师Git教程 :推荐Git入门教程.  2. 按照自己的系统版本下载Git软件,我的操作系统:Windows7 64位,安装版本为Git ...

  2. Qt Creator 的下载与安装

    一.Qt和Qt Creator的区别 Qt是C++的一个库,或者说是开发框架,里面集成了一些库函数,提高开发效率. Qt Creator是一个IDE,就是一个平台,一个开发环境,类似的比如说VS,也可 ...

  3. CentOS 7 - 修改时区为上海时区

    1.查看时间各种状态: timedatectl Local time: 四 2014-12-25 10:52:10 CSTUniversal time: 四 2014-12-25 02:52:10 U ...

  4. spring 中的 bean 是线程安全的吗?

    spring 中的 bean 是线程安全的吗? Spring 不保证 bean 的线程安全. 默认 spring 容器中的 bean 是单例的.当单例中存在竞态条件,即有线程安全问题.如下面的例子 计 ...

  5. DOS命令_查询某个端口的占用情况并释放

    >netstat -aon | findstr “80″Proto   Local Address           Foreign Address         State         ...

  6. 【转】Go调度器原理浅析

    goroutine是golang的一大特色,或者可以说是最大的特色吧(据我了解),这篇文章主要翻译自Morsing的[这篇博客](http://morsmachine.dk/go-scheduler) ...

  7. 关于header file、static、inline、variable hides的一点感想

    前言 先看一段代码 #ifndef _INLINE_H #define _INLINE_H template<typename T> static inline T my_max(T a, ...

  8. beta版本——第四次冲刺

    第四次冲刺 (1)SCRUM部分☁️成员描述: 姓名 李星晨 完成了哪个任务 进行注册的时候若不输入手机号,提醒用户的是未输入登录名,进行更改 花了多少时间 1.2h 还剩余多少时间 1.8h 遇到什 ...

  9. python开发应用之-时间戳

    golang 获取时间戳用time.Now().Unix(),格式化时间用t.Format,解析时间用time.Parse package main import ( "fmt" ...

  10. 通过vue-cli搭建vue项目

    一.搭建环境 安装node.js 从node.js官网下载并安装node,安装过程很简单,一路“下一步”就可以了.安装完成之后,打开命令行工具(win+r,然后输入cmd),输入 node -v,如下 ...