fix: 解决冲突

This commit is contained in:
chenjianxing 2020-12-16 17:04:45 +08:00
commit 992b367f53
35 changed files with 463 additions and 1867 deletions

View File

@ -7,6 +7,7 @@ import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.automation.ApiScenarioRequest;
import io.metersphere.api.dto.automation.ReferenceDTO;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
import io.metersphere.api.service.ApiDefinitionService;
import io.metersphere.base.domain.ApiDefinition;
import io.metersphere.commons.constants.RoleConstants;
@ -93,7 +94,7 @@ public class ApiDefinitionController {
@PostMapping(value = "/import", consumes = {"multipart/form-data"})
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public String testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) {
public ApiDefinitionImport testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) {
return apiDefinitionService.apiTestImport(file, request);
}

View File

@ -13,5 +13,7 @@ public class ApiTestImportRequest {
private String projectId;
private String platform;
private Boolean useEnvironment;
// 来自场景的导入不需要存储
private boolean saved = true;
private String swaggerUrl;
}

View File

@ -32,7 +32,7 @@ public class MsParser extends ApiImportAbstractParser {
if (testObject.get("projectName") != null) {
return parseMsFormat(testStr, request);
} else {
return parsePluginFormat(testObject);
return parsePluginFormat(testObject, request.isSaved());
}
}
@ -53,14 +53,16 @@ public class MsParser extends ApiImportAbstractParser {
return apiDefinitionImport;
}
private ApiDefinitionImport parsePluginFormat(JSONObject testObject) {
private ApiDefinitionImport parsePluginFormat(JSONObject testObject, boolean isSaved) {
List<ApiDefinitionResult> results = new ArrayList<>();
ApiDefinitionImport apiImport = new ApiDefinitionImport();
apiImport.setProtocol(RequestType.HTTP);
apiImport.setData(results);
testObject.keySet().forEach(tag -> {
ApiModule module = apiModuleService.getNewModule(tag, this.projectId, 1);
if (isSaved) {
createModule(module);
}
JSONObject requests = testObject.getJSONObject(tag);
requests.keySet().forEach(requestName -> {

View File

@ -30,17 +30,17 @@ public class PostmanParser extends ApiImportAbstractParser {
List<PostmanKeyValue> variables = postmanCollection.getVariable();
ApiDefinitionImport apiImport = new ApiDefinitionImport();
List<ApiDefinitionResult> results = new ArrayList<>();
parseItem(postmanCollection.getItem(), variables, results, buildModule(postmanCollection.getInfo().getName(), null));
parseItem(postmanCollection.getItem(), variables, results, buildModule(postmanCollection.getInfo().getName(), null, request.isSaved()), request.isSaved());
apiImport.setData(results);
return apiImport;
}
private void parseItem(List<PostmanItem> items, List<PostmanKeyValue> variables, List<ApiDefinitionResult> results, ApiModule parentModule) {
private void parseItem(List<PostmanItem> items, List<PostmanKeyValue> variables, List<ApiDefinitionResult> results, ApiModule parentModule, boolean isSaved) {
for (PostmanItem item : items) {
List<PostmanItem> childItems = item.getItem();
if (childItems != null) {
ApiModule module = buildModule(item.getName(), parentModule);
parseItem(childItems, variables, results, module);
ApiModule module = buildModule(item.getName(), parentModule, isSaved);
parseItem(childItems, variables, results, module, isSaved);
} else {
ApiDefinitionResult request = parsePostman(item);
if (request != null) {
@ -53,7 +53,7 @@ public class PostmanParser extends ApiImportAbstractParser {
}
}
private ApiModule buildModule(String name, ApiModule parentModule) {
private ApiModule buildModule(String name, ApiModule parentModule, boolean isSaved) {
apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class);
ApiModule module;
if (parentModule != null) {
@ -62,7 +62,9 @@ public class PostmanParser extends ApiImportAbstractParser {
} else {
module = apiModuleService.getNewModule(name, this.projectId, 1);
}
if (isSaved) {
createModule(module);
}
return module;
}

View File

@ -39,11 +39,11 @@ public class Swagger2Parser extends ApiImportAbstractParser {
}
ApiDefinitionImport definitionImport = new ApiDefinitionImport();
this.projectId = request.getProjectId();
definitionImport.setData(parseRequests(swagger));
definitionImport.setData(parseRequests(swagger, request.isSaved()));
return definitionImport;
}
private List<ApiDefinitionResult> parseRequests(Swagger swagger) {
private List<ApiDefinitionResult> parseRequests(Swagger swagger, boolean isSaved) {
Map<String, Path> paths = swagger.getPaths();
Set<String> pathNames = paths.keySet();
@ -62,7 +62,7 @@ public class Swagger2Parser extends ApiImportAbstractParser {
parseParameters(operation, request);
apiDefinition.setRequest(JSON.toJSONString(request));
apiDefinition.setResponse(JSON.toJSONString(parseResponse(operation.getResponses())));
buildModule(apiDefinition, operation);
buildModule(apiDefinition, operation, isSaved);
results.add(apiDefinition);
}
}
@ -71,13 +71,15 @@ public class Swagger2Parser extends ApiImportAbstractParser {
return results;
}
private void buildModule(ApiDefinitionResult apiDefinition, Operation operation) {
private void buildModule(ApiDefinitionResult apiDefinition, Operation operation, boolean isSaved) {
List<String> tags = operation.getTags();
if (tags != null) {
tags.forEach(tag -> {
apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class);
ApiModule module = apiModuleService.getNewModule(tag, this.projectId, 1);
if (isSaved) {
createModule(module);
}
apiDefinition.setModuleId(module.getId());
});
}

View File

@ -4,7 +4,6 @@ import com.alibaba.fastjson.JSON;
import io.metersphere.api.jmeter.TestResult;
import io.metersphere.base.domain.ApiDefinitionExecResult;
import io.metersphere.base.mapper.ApiDefinitionExecResultMapper;
import io.metersphere.base.mapper.ext.ExtApiDefinitionExecResultMapper;
import io.metersphere.commons.utils.SessionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -18,14 +17,9 @@ import java.util.UUID;
public class ApiDefinitionExecResultService {
@Resource
private ApiDefinitionExecResultMapper apiDefinitionExecResultMapper;
@Resource
private ExtApiDefinitionExecResultMapper extApiDefinitionExecResultMapper;
public void saveApiResult(TestResult result) {
result.getScenarios().get(0).getRequestResults().forEach(item -> {
// 清理原始资源每个执行 保留一条结果
extApiDefinitionExecResultMapper.deleteByResourceId(item.getName());
ApiDefinitionExecResult saveResult = new ApiDefinitionExecResult();
saveResult.setId(UUID.randomUUID().toString());
saveResult.setUserId(Objects.requireNonNull(SessionUtils.getUser()).getId());

View File

@ -326,7 +326,7 @@ public class ApiDefinitionService {
* @return
*/
public APIReportResult getDbResult(String testId) {
ApiDefinitionExecResult result = extApiDefinitionExecResultMapper.selectByResourceId(testId);
ApiDefinitionExecResult result = extApiDefinitionExecResultMapper.selectMaxResultByResourceId(testId);
if (result == null) {
return null;
}
@ -336,7 +336,7 @@ public class ApiDefinitionService {
}
public String apiTestImport(MultipartFile file, ApiTestImportRequest request) {
public ApiDefinitionImport apiTestImport(MultipartFile file, ApiTestImportRequest request) {
ApiImportParser apiImportParser = ApiImportParserFactory.getApiImportParser(request.getPlatform());
ApiDefinitionImport apiImport = null;
try {
@ -345,8 +345,10 @@ public class ApiDefinitionService {
LogUtil.error(e.getMessage(), e);
MSException.throwException(Translator.get("parse_data_error"));
}
if (request.isSaved()) {
importApiTest(request, apiImport);
return "SUCCESS";
}
return apiImport;
}
private void importApiTest(ApiTestImportRequest importRequest, ApiDefinitionImport apiImport) {

View File

@ -6,7 +6,6 @@ public interface ExtApiDefinitionExecResultMapper {
void deleteByResourceId(String id);
ApiDefinitionExecResult selectByResourceId(String resourceId);
ApiDefinitionExecResult selectMaxResultByResourceId(String resourceId);
}

View File

@ -5,8 +5,8 @@
delete from api_definition_exec_result where resource_id = #{id,jdbcType=VARCHAR}
</delete>
<select id="selectByResourceId" parameterType="java.lang.String" resultType="io.metersphere.base.domain.ApiDefinitionExecResult">
<select id="selectMaxResultByResourceId" parameterType="java.lang.String" resultType="io.metersphere.base.domain.ApiDefinitionExecResult">
select * from api_definition_exec_result
where resource_id = #{resourceId,jdbcType=VARCHAR}
where resource_id = #{resourceId,jdbcType=VARCHAR} ORDER BY update_time DESC LIMIT 1
</select>
</mapper>

View File

@ -94,12 +94,24 @@
when 'error' then '未通过'
ELSE '未执行' end as status ,
CONCAT(FORMAT(SUM(IF(t2.`status` = 'success', 1, 0))/ COUNT(t1.id)*100, 2), '%') passRate
from api_test_case t1 left join api_definition_exec_result t2 on t1.id = t2.resource_id
from api_test_case t1 left join (
select
a.status, a.id, a.resource_id
from
api_definition_exec_result a
left join (
select
max(start_time) start_time , id, resource_id
from
api_definition_exec_result
group by
resource_id ) as b on a.id = b.id
where
a.start_time = b.start_time)as t2 on t1.id = t2.resource_id
group by t1.api_definition_id having t1.api_definition_id in
<foreach collection="ids" item="v" separator="," open="(" close=")">
#{v}
</foreach>
order by t2.end_time desc;
</select>
<sql id="combine">

View File

@ -146,15 +146,45 @@
</sql>
<select id="list" resultType="io.metersphere.api.dto.definition.ApiTestCaseResult">
select atc.id, atc.project_id,
atc.name,atc.priority,atc.api_definition_id,T1.name as createUser ,T2.name as updateUser,
atc.description,atc.request,atc.response,atc.create_user_id,
atc.create_time,atc.update_user_id, atc.update_time,ader.status execResult
from api_test_case atc left join user T1 on atc.create_user_id = T1.id left join user T2 on
atc.update_user_id = T2.id left join api_definition_exec_result ader on atc.id = ader.resource_id
select
atc.id,
atc.project_id,
atc.name,
atc.priority,
atc.api_definition_id,
u1.name as createUser ,
u2.name as updateUser,
atc.description,
atc.request,
atc.response,
atc.create_user_id,
atc.create_time,
atc.update_user_id,
atc.update_time,
ader.status execResult
from
api_test_case atc
left join user u1 on
atc.create_user_id = u1.id
left join user u2 on
atc.update_user_id = u2.id
left join (
select
a.status, a.id, a.resource_id
from
api_definition_exec_result a
left join (
select
max(start_time) start_time , id, resource_id
from
api_definition_exec_result
group by
resource_id ) as b on a.id = b.id
where
a.start_time = b.start_time) as ader
on atc.id = ader.resource_id
<where>
<if test="request.name != null and request.name!=''">
and atc.name like CONCAT('%', #{request.name},'%')
</if>
<if test="request.id != null and request.id!=''">

View File

@ -84,9 +84,4 @@ public class ProjectController {
projectService.updateProject(Project);
}
@PostMapping("/search")
public List<ProjectDTO> searchProject(@RequestBody ProjectRequest projectRequest) {
projectRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
return projectService.getProjectList(projectRequest);
}
}

View File

@ -1,45 +0,0 @@
FROM alpine:latest
LABEL maintainer="support@fit2cloud.com"
ENV JMETER_VERSION "5.3"
ENV KAFKA_BACKEND_LISTENER_VERSION "1.0.4"
#定义时区参数
ENV TZ=Asia/Shanghai
RUN apk update && \
apk upgrade && \
apk add --update openjdk8 wget tar bash && \
wget https://mirrors.tuna.tsinghua.edu.cn/apache/jmeter/binaries/apache-jmeter-${JMETER_VERSION}.tgz && \
wget https://jmeter-plugins.org/files/packages/jpgc-casutg-2.9.zip && \
wget https://jmeter-plugins.org/files/packages/jpgc-tst-2.5.zip && \
wget https://github.com/metersphere/jmeter-backend-listener-kafka/releases/download/v${KAFKA_BACKEND_LISTENER_VERSION}/jmeter.backendlistener.kafka-${KAFKA_BACKEND_LISTENER_VERSION}.jar && \
wget https://github.com/metersphere/jmeter-plugins-for-apache-dubbo/releases/download/2.7.7/jmeter-plugins-dubbo-2.7.7-jar-with-dependencies.jar && \
wget -q "http://search.maven.org/remotecontent?filepath=mysql/mysql-connector-java/5.1.49/mysql-connector-java-5.1.49.jar" -O mysql-connector-java.jar && \
wget -q "http://search.maven.org/remotecontent?filepath=com/oracle/database/jdbc/ojdbc8/19.7.0.0/ojdbc8-19.7.0.0.jar" -O ojdbc8.jar && \
wget -q "http://search.maven.org/remotecontent?filepath=org/postgresql/postgresql/42.2.14/postgresql-42.2.14.jar" -O postgresql.jar && \
wget -q "http://search.maven.org/remotecontent?filepath=com/microsoft/sqlserver/mssql-jdbc/7.4.1.jre8/mssql-jdbc-7.4.1.jre8.jar" -O mssql-jdbc.jar && \
mkdir -p /opt/jmeter && \
tar -zxf apache-jmeter-${JMETER_VERSION}.tgz -C /opt/jmeter/ --strip-components=1 && \
unzip -o jpgc-casutg-2.9.zip -d /tmp/ && mv /tmp/lib/ext/jmeter-plugins-casutg-2.9.jar /opt/jmeter/lib/ext && \
unzip -o jpgc-tst-2.5.zip -d /tmp/ && mv /tmp/lib/ext/jmeter-plugins-tst-2.5.jar /opt/jmeter/lib/ext && \
mv jmeter.backendlistener.kafka-${KAFKA_BACKEND_LISTENER_VERSION}.jar /opt/jmeter/lib/ext && \
mv jmeter-plugins-dubbo-2.7.7-jar-with-dependencies.jar /opt/jmeter/lib/ext && \
mv mysql-connector-java.jar /opt/jmeter/lib/ext && \
mv ojdbc8.jar /opt/jmeter/lib/ext && \
mv postgresql.jar /opt/jmeter/lib/ext && \
mv mssql-jdbc.jar /opt/jmeter/lib/ext && \
rm -rf apache-jmeter-${JMETER_VERSION}.tgz && \
rm -rf jpgc-casutg-2.9.zip && \
rm -rf jpgc-tst-2.5.zip && \
rm -rf jmeter.backendlistener.kafka-${KAFKA_BACKEND_LISTENER_VERSION}.jar && \
rm -rf jmeter-plugins-dubbo-2.7.7-jar-with-dependencies.jar && \
rm -rf /var/cache/apk/* && \
wget -O /usr/bin/tpl https://github.com/schneidexe/tpl/releases/download/v0.5.0/tpl-linux-amd64 && \
chmod +x /usr/bin/tpl && \
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo "$TZ" > /etc/timezone
ENV JMETER_HOME /opt/jmeter
ENV PATH $PATH:$JMETER_HOME/bin:/usr/lib/jvm/java-1.8-openjdk/bin
ADD log4j2.xml $JMETER_HOME/bin/log4j2.xml
ADD jmeter.properties $JMETER_HOME/bin/jmeter.properties

File diff suppressed because it is too large Load Diff

View File

@ -1,116 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<Configuration status="WARN" packages="org.apache.jmeter.gui.logging">
<Appenders>
<File name="jmeter-log" fileName="${sys:jmeter.logfile:-jmeter.log}" append="false">
<PatternLayout>
<pattern>%d %p %c{1.}: %m%n</pattern>
</PatternLayout>
</File>
<GuiLogEvent name="gui-log-event">
<PatternLayout>
<pattern>%d %p %c{1.}: %m%n</pattern>
</PatternLayout>
</GuiLogEvent>
<Kafka name="Kafka" topic="${env:LOG_TOPIC}">
<PatternLayout pattern="${env:REPORT_ID} ${env:RESOURCE_ID} %date %message"/>
<Property name="bootstrap.servers">${env:BOOTSTRAP_SERVERS}</Property>
</Kafka>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="jmeter-log" />
<AppenderRef ref="gui-log-event" />
<AppenderRef ref="Kafka" />
</Root>
<Logger name="org.apache.kafka" level="INFO" />
<Logger name="org.apache.jmeter.junit" level="debug" />
<!--
<Logger name="org.apache.jmeter.control" level="debug" />
<Logger name="org.apache.jmeter.testbeans" level="debug" />
<Logger name="org.apache.jmeter.engine" level="debug" />
<Logger name="org.apache.jmeter.threads" level="debug" />
<Logger name="org.apache.jmeter.gui" level="warn" />
<Logger name="org.apache.jmeter.testelement" level="debug" />
<Logger name="org.apache.jmeter.util" level="warn" />
<Logger name="org.apache.jmeter.protocol.http" level="debug" />
-->
<!-- # For CookieManager, AuthManager etc: -->
<!--
<Logger name="org.apache.jmeter.protocol.http.control" level="debug" />
<Logger name="org.apache.jmeter.protocol.ftp" level="warn" />
<Logger name="org.apache.jmeter.protocol.jdbc" level="debug" />
<Logger name="org.apache.jmeter.protocol.java" level="warn" />
<Logger name="org.apache.jmeter.testelements.property" level="debug" />
-->
<Logger name="org.apache.jorphan" level="info" />
<!--
# Apache HttpClient logging examples
-->
<!-- # Enable header wire + context logging - Best for Debugging -->
<!--
<Logger name="org.apache.http" level="debug" />
<Logger name="org.apache.http.wire" level="error" />
-->
<!-- # Enable full wire + context logging -->
<!-- <Logger name="org.apache.http" level="debug" /> -->
<!-- # Enable context logging for connection management -->
<!-- <Logger name="org.apache.http.impl.conn" level="debug" /> -->
<!-- # Enable context logging for connection management / request execution -->
<!--
<Logger name="org.apache.http.impl.conn" level="debug" />
<Logger name="org.apache.http.impl.client" level="debug" />
<Logger name="org.apache.http.client" level="debug" />
-->
<!--
# Reporting logging configuration examples
-->
<!-- # If you want to debug reporting, uncomment this line -->
<!-- <Logger name="org.apache.jmeter.report" level="debug" /> -->
<!--
# More user specific logging configuration examples.
-->
<!-- <Logger name="org.apache.jorphan.reflect" level="debug" /> -->
<!--
# Warning: Enabling the next debug line causes javax.net.ssl.SSLException: Received fatal alert: unexpected_message
for certain sites when used with the default HTTP Sampler
-->
<!--
<Logger name="org.apache.jmeter.util.HttpSSLProtocolSocketFactory" level="debug" />
<Logger name="org.apache.jmeter.util.JsseSSLManager" level="debug" />
-->
<!--
# Enable Proxy request debug
-->
<!-- <Logger name="org.apache.jmeter.protocol.http.proxy.HttpRequestHdr" level="debug" /> -->
</Loggers>
</Configuration>

View File

@ -1,14 +0,0 @@
FROM registry.fit2cloud.com/metersphere/jmeter-base:0.0.1
LABEL maintainer="support@fit2cloud.com"
EXPOSE 60000
ENV SSL_DISABLED true
ENV TESTS_DIR /test
ADD run-test.sh /run-test.sh
RUN chmod +x /run-test.sh \
&& mkdir /test \
&& mkdir /jmeter-log
WORKDIR /jmeter-log/
ENTRYPOINT /run-test.sh

View File

@ -1,3 +0,0 @@
for file in ${TESTS_DIR}/*.jmx; do
jmeter -n -t ${file} -Jserver.rmi.ssl.disable=${SSL_DISABLED}
done

View File

@ -17,15 +17,21 @@
<el-col :span="5">
<el-tooltip effect="dark" :content="request.responseResult.responseCode" placement="bottom" :open-delay="800">
<div class="url" style="color: #5daf34">{{ request.responseResult.responseCode }}</div>
<div style="color: #5daf34" v-if="request.success">{{ request.responseResult.responseCode }}</div>
<div style="color: #FE6F71" v-else>{{ request.responseResult.responseCode }}</div>
</el-tooltip>
</el-col>
<el-col :span="3">
<span v-if="request.success">
{{request.responseResult.responseTime}} ms
</span>
<span style="color: #FE6F71" v-else>
{{request.responseResult.responseTime}} ms
</span>
</el-col>
<el-col :span="2">
<div class="success">
<div>
<el-tag size="mini" type="success" v-if="request.success">
{{ $t('api_report.success') }}
</el-tag>

View File

@ -8,33 +8,10 @@
</div>
</el-col>
<el-col :span="10">
<div class="name">{{request.name}}</div>
<el-tooltip effect="dark" :content="request.url" placement="bottom" :open-delay="800">
<div class="url">{{request.url}}</div>
</el-tooltip>
</el-col>
<el-col :span="4">
{{request.startTime | timestampFormatDate(true) }}
</el-col>
<el-col :span="2">
<div class="time">
{{request.responseResult.responseTime}}
</div>
</el-col>
<el-col :span="2">
{{request.error}}
</el-col>
<el-col :span="2">
{{assertion}}
</el-col>
<el-col :span="2">
<el-tag size="mini" type="success" v-if="request.success">
{{$t('api_report.success')}}
</el-tag>
<el-tag size="mini" type="danger" v-else>
{{$t('api_report.fail')}}
</el-tag>
</el-col>
</el-row>
</div>
<el-collapse-transition>
@ -52,7 +29,6 @@
</el-tab-pane>
</el-tabs>
<div v-else>
<ms-request-metric :request="request"/>
<ms-request-text v-if="isCodeEditAlive" :request="request"/>
<br>
<ms-response-text :request-type="requestType" v-if="isCodeEditAlive" :response="request.responseResult"/>

View File

@ -32,6 +32,7 @@
methods: {
setFiles(item, bodyUploadFiles, obj) {
if (item.body) {
if (item.body.kvs) {
item.body.kvs.forEach(param => {
if (param.files) {
param.files.forEach(item => {
@ -47,6 +48,8 @@
});
}
});
}
if (item.body.binary) {
item.body.binary.forEach(param => {
if (param.files) {
param.files.forEach(item => {
@ -63,6 +66,7 @@
}
});
}
}
},
recursiveFile(arr, bodyUploadFiles, obj) {
arr.forEach(item => {

View File

@ -113,7 +113,7 @@
</el-form>
<!-- 场景步骤-->
<div v-loading="isReloadData">
<div v-loading="loading">
<p class="tip">{{$t('api_test.automation.scenario_step')}} </p>
<el-row>
<el-col :span="21">
@ -157,13 +157,13 @@
</el-row>
</div>
<!-- 场景步骤内容 -->
<div style="margin-top: 10px" v-loading="isReloadData">
<div style="margin-top: 10px" v-loading="loading">
<el-tree node-key="resourceId" :props="props" :data="scenarioDefinition"
:default-expanded-keys="expandedNode"
:expand-on-click-node="false"
@node-expand="nodeExpand"
@node-collapse="nodeCollapse"
:allow-drop="allowDrop" @node-drag-end="allowDrag" @node-click="nodeClick" v-if="!isReloadData" draggable>
:allow-drop="allowDrop" @node-drag-end="allowDrag" @node-click="nodeClick" v-if="!loading" draggable>
<span class="custom-tree-node father" slot-scope="{ node, data}" style="width: 96%">
<template>
<!-- 场景 -->
@ -267,6 +267,8 @@
<!--场景公共参数-->
<ms-scenario-parameters :currentScenario="currentScenario" @addParameters="addParameters" ref="scenarioParameters"/>
<!--外部导入-->
<api-import ref="apiImport" :saved="false" @refresh="apiImport"/>
</div>
</el-card>
</template>
@ -293,6 +295,7 @@
import MsApiScenarioComponent from "./ApiScenarioComponent";
import MsApiReportDetail from "../report/ApiReportDetail";
import MsScenarioParameters from "./ScenarioParameters";
import ApiImport from "../../definition/components/import/ApiImport";
export default {
name: "EditApiScenario",
@ -301,13 +304,21 @@
currentScenario: {},
},
components: {
ApiEnvironmentConfig, MsScenarioParameters,
MsApiReportDetail, MsAddTag, MsRun,
MsApiScenarioComponent, MsImportApiScenario,
MsJsr233Processor, MsConstantTimer,
MsIfController, MsApiAssertions,
MsApiExtract, MsApiDefinition,
MsApiComponent, MsApiCustomize
ApiEnvironmentConfig,
MsScenarioParameters,
MsApiReportDetail,
MsAddTag, MsRun,
MsApiScenarioComponent,
MsImportApiScenario,
MsJsr233Processor,
MsConstantTimer,
MsIfController,
MsApiAssertions,
MsApiExtract,
MsApiDefinition,
MsApiComponent,
MsApiCustomize,
ApiImport,
},
data() {
return {
@ -333,7 +344,7 @@
options: API_STATUS,
levels: PRIORITY,
scenario: {},
isReloadData: false,
loading: false,
apiListVisible: false,
customizeVisible: false,
scenarioVisible: false,
@ -402,6 +413,7 @@
this.scenarioVisible = true;
break;
default:
this.$refs.apiImport.open();
break;
}
this.sort();
@ -553,9 +565,9 @@
this.reload();
},
reload() {
this.isReloadData = true
this.loading = true
this.$nextTick(() => {
this.isReloadData = false
this.loading = false
})
},
runDebug() {
@ -622,6 +634,7 @@
},
setFiles(item, bodyUploadFiles, obj) {
if (item.body) {
if (item.body.kvs) {
item.body.kvs.forEach(param => {
if (param.files) {
param.files.forEach(item => {
@ -637,6 +650,8 @@
});
}
});
}
if (item.body.binary) {
item.body.binary.forEach(param => {
if (param.files) {
param.files.forEach(item => {
@ -653,6 +668,7 @@
}
});
}
}
},
recursiveFile(arr, bodyUploadFiles, obj) {
arr.forEach(item => {
@ -728,7 +744,7 @@
},
runRefresh() {
this.debugVisible = true;
this.isReloadData = false;
this.loading = false;
},
showScenarioParameters() {
this.$refs.scenarioParameters.open(this.currentScenario.variables);
@ -736,6 +752,15 @@
addParameters(data) {
this.currentScenario.variables = data;
this.reload();
},
apiImport(importData) {
if (importData && importData.data) {
importData.data.forEach(item => {
this.setApiParameter(item, "API", "Copy");
})
this.sort();
this.reload();
}
}
}
}

View File

@ -237,7 +237,7 @@
if (!this.$refs.apiList[0].tableData) {
return;
}
let obj = {protocol: this.currentProtocol, data: this.$refs.apiList[0].tableData}
let obj = {projectName: getCurrentProjectID(), protocol: this.currentProtocol, data: this.$refs.apiList[0].tableData}
downloadFile("导出API.json", JSON.stringify(obj));
},
refresh(data) {

View File

@ -0,0 +1,63 @@
<template>
<div>
<el-dialog
:title="$t('commons.batch_add')"
:visible.sync="dialogVisible"
width="60%"
class="batch-edit-dialog"
:destroy-on-close="true"
@close="handleClose">
<div>
<div>格式参数名,必填,参数值,备注 Accept-Encoding,必填,utf-8,编码</div>
<div style="height: 200px">
<ms-code-edit :enable-format="false" mode="text" :data.sync="parameters" theme="eclipse" :modes="['text']"
ref="codeEdit"/>
</div>
</div>
<template v-slot:footer>
<ms-dialog-footer
@cancel="dialogVisible = false"
@confirm="confirm()"/>
</template>
</el-dialog>
</div>
</template>
<script>
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
export default {
name: "BatchAddParameter",
components: {
MsDialogFooter,
MsCodeEdit
},
props: {},
data() {
return {
dialogVisible: false,
parameters: "",
}
},
methods: {
open() {
this.dialogVisible = true;
listenGoBack(this.handleClose);
},
handleClose() {
this.parameters = "";
removeGoBackListener(this.handleClose);
},
confirm() {
this.dialogVisible = false;
this.$emit("batchSave", this.parameters);
}
}
}
</script>
<style scoped>
</style>

View File

@ -25,6 +25,10 @@
{{ $t('api_test.definition.request.body_binary') }}
</el-radio>
</el-radio-group>
<el-row v-if="body.type == 'Form Data' || body.type == 'WWW_FORM'">
<el-link class="ms-el-link" @click="batchAdd"> {{$t("commons.batch_add")}}</el-link>
</el-row>
<ms-api-variable :is-read-only="isReadOnly"
:parameters="body.kvs"
:isShowEnable="isShowEnable"
@ -55,12 +59,14 @@
type="body"
v-if="body.type == 'BINARY'"/>
<batch-add-parameter @batchSave="batchSave" ref="batchAddParameter"/>
</div>
</template>
<script>
import MsApiKeyValue from "../ApiKeyValue";
import {BODY_FORMAT, BODY_TYPE, KeyValue} from "../../model/ApiTestModel";
import {BODY_TYPE, KeyValue} from "../../model/ApiTestModel";
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
import MsJsonCodeEdit from "../../../../common/components/MsJsonCodeEdit";
@ -68,6 +74,7 @@
import MsApiVariable from "../ApiVariable";
import MsApiBinaryVariable from "./ApiBinaryVariable";
import MsApiFromUrlVariable from "./ApiFromUrlVariable";
import BatchAddParameter from "../basis/BatchAddParameter";
export default {
name: "MsApiBody",
@ -78,7 +85,8 @@
MsApiKeyValue,
MsApiBinaryVariable,
MsApiFromUrlVariable,
MsJsonCodeEdit
MsJsonCodeEdit,
BatchAddParameter
},
props: {
body: {},
@ -147,9 +155,29 @@
},
jsonError(e) {
this.$error(e);
},
batchAdd() {
this.$refs.batchAddParameter.open();
},
batchSave(data) {
if (data) {
let params = data.split("\n");
let keyValues = [];
params.forEach(item => {
let line = item.split(/|,/);
let required = false;
if (line[1] === '必填' || line[1] === 'true') {
required = true;
}
keyValues.push(new KeyValue({name: line[0], required: required, value: line[2], description: line[3], type: "text", valid: false, file: false, encode: true, enable: true, contentType: "text/plain"}));
})
keyValues.forEach(item => {
this.body.kvs.unshift(item);
})
}
},
},
created() {
if (!this.body.type) {
this.body.type = BODY_TYPE.FORM_DATA;
@ -187,4 +215,8 @@
margin-top: 15px;
}
.ms-el-link {
float: right;
margin-right: 45px;
}
</style>

View File

@ -70,6 +70,12 @@
export default {
name: "ApiImport",
components: {MsDialogFooter},
props: {
saved: {
type: Boolean,
default: true,
}
},
data() {
return {
visible: false,
@ -148,7 +154,7 @@
},
uploadValidate(file, fileList) {
let suffix = file.name.substring(file.name.lastIndexOf('.') + 1);
if (!this.selectedPlatform.suffixes.has(suffix)) {
if (this.selectedPlatform.suffixes && !this.selectedPlatform.suffixes.has(suffix)) {
this.$warning(this.$t('api_test.api_import.suffixFormatErr'));
return false;
}
@ -170,7 +176,7 @@
let res = response.data;
this.$success(this.$t('test_track.case.import.success'));
this.visible = false;
this.$emit('refresh');
this.$emit('refresh', res);
});
} else {
return false;
@ -181,8 +187,11 @@
let param = {};
Object.assign(param, this.formData);
param.platform = this.selectedPlatformValue;
param.saved = this.saved;
if (this.currentModule) {
param.moduleId = this.currentModule.id;
param.modulePath = this.currentModule.path;
}
param.projectId = getCurrentProjectID();
if (!this.swaggerUrlEable) {
param.swaggerUrl = undefined;

View File

@ -13,7 +13,6 @@
</div>
</span>
</el-tooltip>
<ms-api-key-value :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :suggestions="headerSuggestions" :items="headers"/>
</el-tab-pane>
@ -25,7 +24,9 @@
<div class="el-step__icon-inner">{{request.arguments.length-1}}</div>
</div></span>
</el-tooltip>
<el-row>
<el-link class="ms-el-link" @click="batchAdd"> {{$t("commons.batch_add")}}</el-link>
</el-row>
<ms-api-variable :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :parameters="request.arguments"/>
</el-tab-pane>
@ -39,6 +40,9 @@
</div>
</span>
</el-tooltip>
<el-row>
<el-link class="ms-el-link" @click="batchAdd"> {{$t("commons.batch_add")}}</el-link>
</el-row>
<ms-api-variable :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :parameters="request.rest"/>
</el-tab-pane>
@ -71,6 +75,9 @@
<!--提取规则-->
<ms-api-extract :is-read-only="isReadOnly" @copyRow="copyRow" @remove="remove" v-if="row.type==='Extract'" :extract="row"/>
</div>
<batch-add-parameter @batchSave="batchSave" ref="batchAddParameter"/>
</div>
</el-col>
<!--操作按钮-->
@ -98,15 +105,24 @@
import {createComponent} from "../../jmeter/components";
import MsApiAssertions from "../../assertion/ApiAssertions";
import MsApiExtract from "../../extract/ApiExtract";
import {Assertions, Body, Extract} from "../../../model/ApiTestModel";
import {Assertions, Body, Extract, KeyValue} from "../../../model/ApiTestModel";
import {getUUID} from "@/common/js/utils";
import BatchAddParameter from "../../basis/BatchAddParameter";
export default {
name: "MsApiHttpRequestForm",
components: {
MsJsr233Processor,
MsApiAdvancedConfig,
MsApiVariable, ApiRequestMethodSelect, MsApiExtract, MsApiAuthConfig, MsApiBody, MsApiKeyValue, MsApiAssertions
BatchAddParameter,
MsApiVariable,
ApiRequestMethodSelect,
MsApiExtract,
MsApiAuthConfig,
MsApiBody,
MsApiKeyValue,
MsApiAssertions
},
props: {
request: {},
@ -149,7 +165,7 @@
},
headerSuggestions: REQUEST_HEADERS,
isReloadData: false,
isBodyShow: true
isBodyShow: true,
}
},
@ -216,6 +232,35 @@
this.$nextTick(() => {
this.isBodyShow = true;
});
},
batchAdd() {
this.$refs.batchAddParameter.open();
},
batchSave(data) {
if (data) {
let params = data.split("\n");
let keyValues = [];
params.forEach(item => {
let line = item.split(/|,/);
let required = false;
if (line[1] === '必填' || line[1] === 'true') {
required = true;
}
keyValues.push(new KeyValue({name: line[0], required: required, value: line[2], description: line[3], type: "text", valid: false, file: false, encode: true, enable: true, contentType: "text/plain"}));
})
keyValues.forEach(item => {
switch (this.activeName) {
case "parameters":
this.request.arguments.unshift(item);
break;
case "rest":
this.request.rest.unshift(item);
break;
default:
break;
}
})
}
}
}
}
@ -273,4 +318,8 @@
border: #E6EEF2;
}
.ms-el-link {
float: right;
margin-right: 45px;
}
</style>

View File

@ -1,25 +1,10 @@
<template>
<div id="menu-bar" v-if="isRouterAlive">
<el-row type="flex">
<project-change :project-name="currentProject"/>
<el-col :span="14">
<el-menu class="header-menu" :unique-opened="true" mode="horizontal" router :default-active='$route.path'>
<el-submenu :class="{'deactivation':!isProjectActivation}"
v-permission="['test_manager','test_user','test_viewer']" index="3">
<template v-slot:title>
<span style="display: inline-block;width: 150px;white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" :title="currentProject">
{{ $t('commons.project') }}: {{currentProject}}
</span>
</template>
<search-list ref="projectRecent" :options="projectRecent" :current-project.sync="currentProject"/>
<el-divider class="menu-divider"/>
<el-menu-item :index="'/setting/project/create'">
<font-awesome-icon :icon="['fa', 'plus']"/>
<span style="padding-left: 7px;">{{ $t("project.create") }}</span>
</el-menu-item>
<ms-show-all :index="'/setting/project/all'"/>
</el-submenu>
<el-menu-item :index="'/api/home'">
{{ $t("i18n.home") }}
</el-menu-item>
@ -75,22 +60,13 @@ import MsCreateButton from "../../common/head/CreateButton";
import MsCreateTest from "../../common/head/CreateTest";
import {ApiEvent, LIST_CHANGE} from "@/business/components/common/head/ListEvent";
import SearchList from "@/business/components/common/head/SearchList";
import ProjectChange from "@/business/components/common/head/ProjectSwitch";
export default {
name: "MsApiHeaderMenus",
components: {SearchList, MsCreateTest, MsCreateButton, MsShowAll, MsRecentList},
components: {SearchList, MsCreateTest, MsCreateButton, MsShowAll, MsRecentList, ProjectChange},
data() {
return {
projectRecent: {
title: this.$t('project.recent'),
url: "/project/recent/5",
index: function (item) {
return '/api/test/list/' + item.id;
},
router: function (item) {
return {name: 'ApiTestList', params: {projectId: item.id, projectName: item.name}}
}
},
testRecent: {
title: this.$t('load_test.recent'),
url: "/api/recent/5",
@ -115,19 +91,10 @@ export default {
currentProject: ''
}
},
// watch: {
// '$route'(to) {
// this.init();
// },
// },
methods: {
registerEvents() {
ApiEvent.$on(LIST_CHANGE, () => {
// todo refs
if (!this.$refs.projectRecent) {
return;
}
this.$refs.projectRecent.recent();
// // todo refs
this.$refs.testRecent.recent();
this.$refs.reportRecent.recent();
});
@ -138,17 +105,6 @@ export default {
this.isRouterAlive = true;
});
},
// init() {
// let path = this.$route.path;
// if (path.indexOf("/api/test/list") >= 0 && !!this.$route.params.projectId) {
// this.apiTestProjectPath = path;
// //
// this.isProjectActivation = false;
// this.reload();
// } else {
// this.isProjectActivation = true;
// }
// },
},
mounted() {
this.registerEvents();

View File

@ -1,28 +0,0 @@
<template>
<span>
<el-submenu index="10">
<template v-slot:title>操作</template>
<el-input
placeholder="请输入内容"
prefix-icon="el-icon-search"
v-model="input2">
</el-input>
</el-submenu>
</span>
</template>
<script>
export default {
name: "ProjectMenu",
data() {
return {
input2: '1'
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,54 @@
<template>
<el-menu class="header-menu" :unique-opened="true" mode="horizontal" default-active="1" router>
<!-- 不激活项目路由-->
<el-menu-item index="1" v-show="false">Placeholder</el-menu-item>
<el-submenu v-permission="['test_manager','test_user','test_viewer']" index="2" popper-class="submenu">
<template v-slot:title>
<span class="project-name" :title="currentProject">
{{ $t('commons.project') }}: {{currentProject}}
</span>
</template>
<search-list :current-project.sync="currentProject"/>
<el-divider/>
<el-menu-item :index="'/setting/project/create'">
<font-awesome-icon :icon="['fa', 'plus']"/>
<span style="padding-left: 7px;">{{ $t("project.create") }}</span>
</el-menu-item>
<el-menu-item :index="'/setting/project/all'">
<font-awesome-icon :icon="['fa', 'list-ul']"/>
<span style="padding-left: 7px;">{{ $t('commons.show_all') }}</span>
</el-menu-item>
</el-submenu>
</el-menu>
</template>
<script>
import SearchList from "@/business/components/common/head/SearchList";
export default {
name: "ProjectSwitch",
props: {
projectName: String
},
components: {SearchList},
data() {
return {
currentProject: this.projectName
}
}
}
</script>
<style scoped>
.project-name {
display: inline-block;
width: 130px;
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
}
.el-divider--horizontal {
margin: 0;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div v-loading="result.loading" class="search-list">
<div v-loading="result.loading">
<el-input placeholder="搜索项目"
prefix-icon="el-icon-search"
v-model="searchString"
@ -76,13 +76,6 @@ export default {
})
}
},
search() {
if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
this.result = this.$post("/project/search", {name: this.searchString},response => {
this.items = response.data;
})
}
},
query(queryString) {
this.items = queryString ? this.searchArray.filter(this.createFilter(queryString)) : this.searchArray;
},
@ -98,13 +91,10 @@ export default {
}
this.$post("/user/update/current", {id: this.userId, lastProjectId: projectId}, () => {
localStorage.setItem(PROJECT_ID, projectId);
if (this.$route.path.indexOf('/track/review/view/') >= 0) {
this.$router.replace('/track/review/all');
} else if (this.$route.path.indexOf('/track/plan/view/') >= 0) {
this.$router.replace('/track/plan/all');
} else {
window.location.reload();
}
let path = this.$route.matched[0].path ? this.$route.matched[0].path : '/';
this.$router.push(path).then(() => {
window.location.reload()
}).catch(err => err);
this.changeProjectName(projectId);
});
},
@ -135,7 +125,7 @@ export default {
.title {
display: inline-block;
padding-left: 20px;
padding-left: 15px;
max-width: 200px;
white-space: nowrap;
overflow: hidden;

View File

@ -1,25 +1,9 @@
<template>
<div id="menu-bar">
<el-row type="flex">
<el-col :span="10">
<project-change :project-name="currentProject"/>
<el-col :span="12">
<el-menu class="header-menu" :unique-opened="true" mode="horizontal" router :default-active='$route.path'>
<el-submenu v-permission="['test_manager','test_user','test_viewer']"
index="3" popper-class="submenu">
<template v-slot:title>
<span style="display: inline-block;width: 150px;white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" :title="currentProject">
{{ $t('commons.project') }}: {{currentProject}}
</span>
</template>
<search-list ref="projectRecent" :options="projectRecent" :current-project.sync="currentProject"/>
<el-divider/>
<el-menu-item :index="'/setting/project/create'">
<font-awesome-icon :icon="['fa', 'plus']"/>
<span style="padding-left: 7px;">{{ $t("project.create") }}</span>
</el-menu-item>
<ms-show-all :index="'/setting/project/all'"/>
</el-submenu>
<el-menu-item :index="'/performance/home'">
{{ $t("i18n.home") }}
</el-menu-item>
@ -61,10 +45,12 @@ import MsCreateButton from "../../common/head/CreateButton";
import MsShowAll from "../../common/head/ShowAll";
import {LIST_CHANGE, PerformanceEvent} from "@/business/components/common/head/ListEvent";
import SearchList from "@/business/components/common/head/SearchList";
import ProjectChange from "@/business/components/common/head/ProjectSwitch";
export default {
name: "PerformanceHeaderMenus",
components: {
ProjectChange,
SearchList,
MsCreateButton,
MsShowAll,
@ -73,16 +59,6 @@ export default {
},
data() {
return {
projectRecent: {
title: this.$t('project.recent'),
url: "/project/recent/5",
index(item) {
return '/performance/test/' + item.id;
},
router(item) {
return {name: 'perPlan', params: {projectId: item.id, projectName: item.name}}
}
},
testRecent: {
title: this.$t('load_test.recent'),
url: "/performance/recent/5",
@ -108,11 +84,7 @@ export default {
methods: {
registerEvents() {
PerformanceEvent.$on(LIST_CHANGE, () => {
// todo refs
if (!this.$refs.projectRecent) {
return;
}
this.$refs.projectRecent.recent();
// // todo refs
this.$refs.testRecent.recent();
this.$refs.reportRecent.recent();
});

View File

@ -2,25 +2,10 @@
<div id="menu-bar" v-if="isRouterAlive">
<el-row type="flex">
<el-col :span="16">
<project-change :project-name="currentProject"/>
<el-col :span="14">
<el-menu class="header-menu" :unique-opened="true" mode="horizontal" router
:default-active='$route.path'>
<el-submenu :class="{'deactivation':!isProjectActivation}"
v-permission="['test_manager','test_user','test_viewer']" index="3" popper-class="submenu">
<template v-slot:title>
<span style="display: inline-block;width: 150px;white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" :title="currentProject">
{{ $t('commons.project') }}: {{currentProject}}
</span>
</template>
<search-list ref="projectRecent" :options="projectRecent" :current-project.sync="currentProject"/>
<el-divider/>
<el-menu-item :index="'/setting/project/create'">
<font-awesome-icon :icon="['fa', 'plus']"/>
<span style="padding-left: 7px;">{{ $t("project.create") }}</span>
</el-menu-item>
<ms-show-all :index="'/setting/project/all'"/>
</el-submenu>
<el-menu-item :index="'/track/home'">
{{ $t("i18n.home") }}
</el-menu-item>
@ -70,10 +55,11 @@ import MsRecentList from "../../common/head/RecentList";
import MsCreateButton from "../../common/head/CreateButton";
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
import SearchList from "@/business/components/common/head/SearchList";
import ProjectChange from "@/business/components/common/head/ProjectSwitch";
export default {
name: "TrackHeaderMenus",
components: {SearchList, MsShowAll, MsRecentList, MsCreateButton},
components: {ProjectChange, SearchList, MsShowAll, MsRecentList, MsCreateButton},
data() {
return {
testPlanViewPath: '',
@ -83,16 +69,6 @@ export default {
testCaseProjectPath: '',
isProjectActivation: true,
currentProject: '',
projectRecent: {
title: this.$t('project.recent'),
url: "/project/recent/5",
index: function (item) {
return '/track/case/' + item.id;
},
router: function (item) {
return {name: 'testCase', params: {projectId: item.id, projectName: item.name}}
}
},
caseRecent: {
title: this.$t('test_track.recent_case'),
url: "/test/case/recent/5",
@ -140,14 +116,6 @@ export default {
},
init() {
let path = this.$route.path;
// if (path.indexOf("/track/case") >= 0 && !!this.$route.params.projectId) {
// this.testCaseProjectPath = path;
// //
// this.isProjectActivation = false;
// this.reload();
// } else {
// this.isProjectActivation = true;
// }
if (path.indexOf("/track/plan/view") >= 0) {
this.testPlanViewPath = path;
this.reload();
@ -163,11 +131,7 @@ export default {
},
registerEvents() {
TrackEvent.$on(LIST_CHANGE, () => {
// todo refs
if (!this.$refs.projectRecent) {
return;
}
this.$refs.projectRecent.recent();
// // todo refs
this.$refs.planRecent.recent();
this.$refs.caseRecent.recent();
});
@ -199,4 +163,11 @@ export default {
border-bottom: white !important;
}
/*.project-change {*/
/* height: 40px;*/
/* line-height: 40px;*/
/* color: inherit;*/
/* margin-left: 20px;*/
/*}*/
</style>

View File

@ -124,6 +124,7 @@ export default {
already_exists: 'The name already exists',
modifier: 'Modifier',
validate: "Validate",
batch_add: "Batch add",
date: {
select_date: 'Select date',
start_date: 'Start date',

View File

@ -124,6 +124,7 @@ export default {
already_exists: '名称不能重复',
modifier: '修改人',
validate: "校验",
batch_add: "批量添加",
date: {
select_date: '选择日期',
start_date: '开始日期',
@ -589,7 +590,7 @@ export default {
select_table: "选择可见数据",
select_all: "选择全部数据"
},
report_name_info: '请输入报名称',
report_name_info: '请输入报名称',
save_case_info: '请先保存用例',
reference_deleted: '引用已删除',
},

View File

@ -124,6 +124,7 @@ export default {
already_exists: '名稱不能重復',
modifier: '修改人',
validate: "校驗",
batch_add: "批量添加",
date: {
select_date: '選擇日期',
start_date: '開始日期',
@ -589,7 +590,7 @@ export default {
select_table: "選擇可見數據",
select_all: "選擇全部數據"
},
report_name_info: '請輸入報名稱',
report_name_info: '請輸入報名稱',
save_case_info: '請先保存用例',
reference_deleted: '引用已删除',
},