Merge branch 'master' into dev
This commit is contained in:
commit
5a7c21304a
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
|
@ -1,11 +1,29 @@
|
||||||
<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"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:label="item.title"
|
:label="item.title"
|
||||||
>
|
>
|
||||||
<component :is="active === item.id ? item.component : false"/>
|
<component :is="active === item.id ? item.component : false"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-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>
|
||||||
|
|
|
@ -1,12 +1,53 @@
|
||||||
<template>
|
<template>
|
||||||
<el-upload
|
<div>
|
||||||
accept=".jmx"
|
<el-upload
|
||||||
drag
|
accept=".jmx"
|
||||||
:action="jmxUploadPath">
|
drag
|
||||||
<i class="el-icon-upload"/>
|
:limit="1"
|
||||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
:show-file-list="false"
|
||||||
<div class="el-upload__tip" slot="tip">只能上传jmx文件</div>
|
:action="jmxUploadPath"
|
||||||
</el-upload>
|
:before-upload="beforeUpload"
|
||||||
|
:file-list="fileList">
|
||||||
|
<i class="el-icon-upload"/>
|
||||||
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||||
|
<div class="el-upload__tip" slot="tip">只能上传jmx文件</div>
|
||||||
|
</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: 按照大小显示Byte、KB、MB等
|
||||||
|
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>
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue