Merge branch 'master' into dev

This commit is contained in:
Captain.B 2020-02-12 22:25:05 +08:00
commit 5a7c21304a
7 changed files with 287 additions and 12 deletions

View File

@ -1,12 +1,18 @@
package io.metersphere.controller; package io.metersphere.controller;
import io.metersphere.requests.testplan.FileOperationRequest;
import io.metersphere.service.FileService; import io.metersphere.service.FileService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
@RestController @RestController
@ -16,7 +22,22 @@ public class TestPlanController {
private FileService fileService; private FileService fileService;
@PostMapping("/file/upload") @PostMapping("/file/upload")
public void upload(MultipartFile file) throws IOException { public void uploadJmx(MultipartFile file) throws IOException {
fileService.upload(file.getOriginalFilename(), file); fileService.upload(file.getOriginalFilename(), file);
} }
@PostMapping("/file/delete")
public void deleteJmx(@RequestBody FileOperationRequest request) {
System.out.println(String.format("delete %s", request.getName()));
}
@PostMapping("/file/download")
public ResponseEntity<org.springframework.core.io.Resource> downloadJmx(@RequestBody FileOperationRequest fileOperationRequest, HttpServletResponse response) {
org.springframework.core.io.Resource resource = fileService.loadFileAsResource(fileOperationRequest.getName());
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/octet-stream"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileOperationRequest.getName() + "\"")
.body(resource);
}
} }

View File

@ -0,0 +1,13 @@
package io.metersphere.requests.testplan;
public class FileOperationRequest {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -1,18 +1,41 @@
package io.metersphere.service; package io.metersphere.service;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
public class FileService { public class FileService {
// 将上传的文件保存在内存方便测试
private Map<String, MultipartFile> fileMap = new ConcurrentHashMap<>();
public void upload(String name, MultipartFile file) throws IOException { public void upload(String name, MultipartFile file) throws IOException {
String result = new BufferedReader(new InputStreamReader(file.getInputStream())) String result = new BufferedReader(new InputStreamReader(file.getInputStream()))
.lines().collect(Collectors.joining("\n")); .lines().collect(Collectors.joining("\n"));
System.out.println(String.format("upload file: %s, content: \n%s", name, result)); System.out.println(String.format("upload file: %s, content: \n%s", name, result));
fileMap.put(name, file);
}
public Resource loadFileAsResource(String name) {
final MultipartFile file = fileMap.get(name);
if (file != null) {
try {
return new InputStreamResource(file.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
} }
} }

View File

@ -0,0 +1,27 @@
let timestampFormatDate = function (timestamp) {
if (!timestamp) {
return timestamp
}
let date = new Date(timestamp);
let y = date.getFullYear();
let MM = date.getMonth() + 1;
MM = MM < 10 ? ('0' + MM) : MM;
let d = date.getDate();
d = d < 10 ? ('0' + d) : d;
let h = date.getHours();
h = h < 10 ? ('0' + h) : h;
let m = date.getMinutes();
m = m < 10 ? ('0' + m) : m;
let s = date.getSeconds();
s = s < 10 ? ('0' + s) : s;
return y + '-' + MM + '-' + d + ' ' + h + ':' + m + ':' + s
};
export default timestampFormatDate

View File

@ -1,5 +1,23 @@
<template> <template>
<div class="testplan-container"> <div class="testplan-container">
<el-row>
<el-col :span="10">
<el-input placeholder="请输入名称" v-model="testplanName" class="input-with-select">
<el-select v-model="project" slot="prepend" placeholder="请选择项目">
<el-option
v-for="item in projects"
:key="item.id"
:label="item.name"
:value="item.name">
</el-option>
</el-select>
</el-input>
</el-col>
<el-button type="primary" plain @click="save">保存</el-button>
<el-button type="primary" plain @click="saveAndRun">保存并执行</el-button>
<el-button type="warning" plain @click="cancel">取消</el-button>
</el-row>
<el-tabs v-model="active" type="border-card" :stretch="true"> <el-tabs v-model="active" type="border-card" :stretch="true">
<el-tab-pane <el-tab-pane
v-for="item in tabs" v-for="item in tabs"
@ -24,8 +42,26 @@
PressureConfig, PressureConfig,
AdvancedConfig AdvancedConfig
}, },
data () { data() {
return { return {
project: '',
projects: [{
id: '选项1',
name: '黄金糕'
}, {
id: '选项2',
name: '双皮奶'
}, {
id: '选项3',
name: '蚵仔煎'
}, {
id: '选项4',
name: '龙须面'
}, {
id: '选项5',
name: '北京烤鸭'
}],
testplanName: '',
active: '0', active: '0',
tabs: [{ tabs: [{
title: '场景配置', title: '场景配置',
@ -41,6 +77,29 @@
component: 'AdvancedConfig' component: 'AdvancedConfig'
}] }]
} }
},
methods: {
save() {
window.console.log("save")
/// todo: save
this.$message({
message: '保存成功!',
type: 'success'
});
},
saveAndRun() {
window.console.log("saveAndRun")
/// todo: saveAndRun
this.$message({
message: '保存成功,开始运行!',
type: 'success'
});
},
cancel() {
this.$router.push({path: '/'})
}
} }
} }
</script> </script>
@ -50,4 +109,12 @@
float: none; float: none;
text-align: center; text-align: center;
} }
.testplan-container .el-select .el-input {
width: 130px;
}
.testplan-container .input-with-select .el-input-group__prepend {
background-color: #fff;
}
</style> </style>

View File

@ -1,12 +1,53 @@
<template> <template>
<div>
<el-upload <el-upload
accept=".jmx" accept=".jmx"
drag drag
:action="jmxUploadPath"> :limit="1"
:show-file-list="false"
:action="jmxUploadPath"
:before-upload="beforeUpload"
:file-list="fileList">
<i class="el-icon-upload"/> <i class="el-icon-upload"/>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div> <div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">只能上传jmx文件</div> <div class="el-upload__tip" slot="tip">只能上传jmx文件</div>
</el-upload> </el-upload>
<el-table
:data="tableData"
style="width: 100%">
<el-table-column
prop="name"
label="文件名">
</el-table-column>
<el-table-column
prop="size"
label="文件大小">
</el-table-column>
<el-table-column
prop="type"
label="文件类型">
</el-table-column>
<el-table-column
label="修改时间">
<template slot-scope="scope">
<i class="el-icon-time"/>
<span style="margin-left: 10px">{{ scope.row.lastModified | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
prop="status"
label="文件状态">
</el-table-column>
<el-table-column
label="操作">
<template slot-scope="scope">
<el-button @click="handleDownload(scope.row)" type="text" size="small">下载</el-button>
<el-button @click="handleDelete(scope.row, scope.$index)" type="text" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template> </template>
<script> <script>
@ -14,9 +55,88 @@
data() { data() {
return { return {
jmxUploadPath: '/testplan/file/upload', jmxUploadPath: '/testplan/file/upload',
jmxDownloadPath: '/testplan/file/download',
jmxDeletePath: '/testplan/file/delete',
fileList: [],
tableData: [],
}; };
}, },
methods: { methods: {
beforeUpload(file) {
window.console.log(file);
if (!this.fileValidator(file)) {
/// todo:
return false;
} }
this.tableData.push({
name: file.name,
size: file.size + 'Byte', /// todo: ByteKBMB
type: 'JMX',
lastModified: file.lastModified,
status: 'todo',
});
return true;
},
handleDownload(file) {
let data = {
name: file.name
};
this.$post(this.jmxDownloadPath, data).then(response => {
if (response) {
const content = response.data;
const blob = new Blob([content]);
if ("download" in document.createElement("a")) {
// IE
// chrome/firefox
let aTag = document.createElement('a');
aTag.download = file.name;
aTag.href = URL.createObjectURL(blob)
aTag.click();
URL.revokeObjectURL(aTag.href)
} else {
// IE10+
navigator.msSaveBlob(blob, this.filename)
}
}
}).catch((response) => {
this.$message.error(response.message);
});
},
handleDelete(file, index) {
this.$alert('确认删除文件: ' + file.name + "", '', {
confirmButtonText: '确定',
callback: () => {
this._handleDelete(file, index);
}
});
},
_handleDelete(file, index) {
let data = {
name: file.name
};
this.$post(this.jmxDeletePath, data).then(response => {
if (response.data.success) {
this.fileList.splice(index, 1);
this.tableData.splice(index, 1);
this.$message({
message: '删除成功!',
type: 'success'
});
} else {
this.$message.error(response.message);
}
});
},
fileValidator(file) {
/// todo:
return file.size > 0;
},
},
} }
</script> </script>

View File

@ -8,6 +8,7 @@ import App from './App.vue';
import router from "./components/router/router"; import router from "./components/router/router";
import store from './store' import store from './store'
import i18n from "../i18n/i18n"; import i18n from "../i18n/i18n";
import timestampFormatDate from "./components/common/filter/TimestampFormatDateFilter";
Vue.config.productionTip = false; Vue.config.productionTip = false;
Vue.use(icon); Vue.use(icon);
@ -17,6 +18,9 @@ Vue.use(ElementUI, {
Vue.use(filters); Vue.use(filters);
Vue.use(ajax); Vue.use(ajax);
// filter
Vue.filter('timestampFormatDate', timestampFormatDate);
new Vue({ new Vue({
el: '#app', el: '#app',
router, router,