This commit is contained in:
chenjianxing 2020-05-14 11:53:31 +08:00
commit 479dd28d05
36 changed files with 983 additions and 332 deletions

View File

@ -1,26 +1,14 @@
package io.metersphere.config;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.i18n.I18nManager;
import io.metersphere.i18n.Translator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class I18nConfig {
@Bean
@ConditionalOnMissingBean
public I18nManager i18nManager() {
List<String> dirs = new ArrayList<>();
dirs.add("i18n/");
return new I18nManager(dirs);
}
@Bean
@ConditionalOnMissingBean
public Translator translator() {

View File

@ -125,10 +125,9 @@ public class UserController {
@PostMapping("/update/current")
public UserDTO updateCurrentUser(@RequestBody User user) {
UserDTO userDTO = userService.getUserDTO(user.getId());
BeanUtils.copyProperties(user, userDTO);
SessionUtils.putUser(SessionUser.fromUser(userDTO));
userService.updateUser(user);
UserDTO userDTO = userService.getUserDTO(user.getId());
SessionUtils.putUser(SessionUser.fromUser(userDTO));
return SessionUtils.getUser();
}

View File

@ -1,86 +0,0 @@
package io.metersphere.i18n;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import io.metersphere.commons.utils.IOUtils;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.ResourceUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class I18nManager implements ApplicationRunner {
private static Map<String, Map<String, String>> i18nMap = new HashMap<>();
private List<String> dirs;
public I18nManager(List<String> dirs) {
this.dirs = dirs;
}
public static Map<String, Map<String, String>> getI18nMap() {
return i18nMap;
}
private static Resource[] getResources(String dir, String suffix) throws IOException {
Resource[] result = new Resource[0];
PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
if (!patternResolver.getResource(ResourceUtils.CLASSPATH_URL_PREFIX + dir).exists()) {
return result;
}
Resource[] resources = patternResolver.getResources(ResourceUtils.CLASSPATH_URL_PREFIX + dir + "*");
for (Resource resource : resources) {
if (StringUtils.endsWithIgnoreCase(resource.getFilename(), suffix)) {
result = ArrayUtils.add(result, resource);
}
}
return result;
}
private void init() {
try {
for (Lang lang : Lang.values()) {
Resource[] resources = new Resource[0];
String i18nKey = lang.getDesc().toLowerCase();
for (String dir : dirs) {
resources = ArrayUtils.addAll(resources, getResources(dir, i18nKey + ".json"));
}
for (Resource resource : resources) {
if (resource.exists()) {
try (InputStream inputStream = resource.getInputStream()) {
String fileContent = IOUtils.toString(inputStream, Charset.defaultCharset());
Map<String, String> langMap = JSON.parseObject(fileContent, new TypeReference<HashMap<String, String>>() {
});
i18nMap.computeIfAbsent(i18nKey, k -> new HashMap<>());
i18nMap.get(i18nKey).putAll(langMap);
} catch (Exception e) {
e.printStackTrace();
LogUtil.error("failed to load resource: " + resource.getURI());
}
}
}
}
} catch (Exception e) {
LogUtil.error("failed to load i18n.", e);
}
}
/**
* 国际化配置初始化
*/
@Override
public void run(ApplicationArguments args) {
init();
}
}

View File

@ -0,0 +1,36 @@
test_case_exist=該項目下已存在用例:
error_lang_invalid=語言參數錯誤
load_test_already_exists=測試名稱不能重複
project_name_is_null=項目名稱不能為空
project_name_already_exists=項目名稱已存在
workspace_name_is_null=工作空間名不能為空
workspace_name_already_exists=工作空間名已存在
workspace_does_not_belong_to_user=當前工作空間不屬於當前用戶
organization_does_not_belong_to_user=當前組織不屬於當前用戶
file_cannot_be_null=文件不能為空!
edit_load_test_not_found=無法編輯測試,未找到測試:
run_load_test_not_found=無法運行測試,未找到測試:
run_load_test_file_not_found=無法運行測試無法獲取測試文件元信息測試ID
run_load_test_file_content_not_found=無法運行測試無法獲取測試文件內容測試ID
run_load_test_file_init_error=無法運行測試初始化運行環境失敗測試ID
load_test_is_running=測試正在運行, 請等待
node_deep_limit=節點深度不超過5層
no_nodes_message=沒有節點信息
duplicate_node_ip=節點 IP 重複
only_one_k8s=只能添加一個 K8s
organization_id_is_null=組織 ID 不能為空
max_thread_insufficient=並髮用戶數超額
cannot_edit_load_test_running=不能修改正在運行的測試
test_not_found=測試不存在:
test_not_running=測試未運行
before_delete_plan=該計劃下存在關聯測試用例,請先取消關聯!
user_email_already_exists=用戶郵箱已存在
user_name_is_null=用戶名不能為空
user_email_is_null=用戶郵箱不能為空
password_is_null=密碼不能為空
workspace_not_exists=工作空間不存在
#api
api_load_script_error=讀取腳本失敗
user_id_already_exists=用戶id已存在
password_modification_failed=密碼修改失敗
cannot_delete_current_user=無法刪除當前登錄用戶

View File

@ -7,8 +7,11 @@
<ms-top-menus/>
</el-col>
<el-col :span="12">
<el-col :span="12" class="align-right">
<!-- float right -->
<ms-user/>
<ms-language-switch/>
<ms-header-org-ws/>
</el-col>
</el-row>
@ -20,6 +23,8 @@
import MsTopMenus from "./components/common/head/HeaderTopMenus";
import MsView from "./components/common/router/View";
import MsUser from "./components/common/head/HeaderUser";
import MsHeaderOrgWs from "./components/common/head/HeaderOrgWs";
import MsLanguageSwitch from "./components/common/head/LanguageSwitch";
export default {
name: 'app',
@ -40,7 +45,7 @@
window.location.href = "/login"
});
},
components: {MsUser, MsView, MsTopMenus},
components: {MsLanguageSwitch, MsUser, MsView, MsTopMenus, MsHeaderOrgWs},
methods: {}
}
</script>
@ -85,5 +90,9 @@
padding-right: 15px;
text-decoration: none;
}
.align-right {
float: right;
}
</style>

View File

@ -13,11 +13,11 @@
width="150"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="description"
:label="$t('commons.description')"
show-overflow-tooltip>
</el-table-column>
<!-- <el-table-column-->
<!-- prop="description"-->
<!-- :label="$t('commons.description')"-->
<!-- show-overflow-tooltip>-->
<!-- </el-table-column>-->
<el-table-column
width="250"
:label="$t('commons.create_time')">

View File

@ -13,11 +13,11 @@
width="150"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="description"
:label="$t('commons.description')"
show-overflow-tooltip>
</el-table-column>
<!-- <el-table-column-->
<!-- prop="description"-->
<!-- :label="$t('commons.description')"-->
<!-- show-overflow-tooltip>-->
<!-- </el-table-column>-->
<el-table-column
prop="projectName"
:label="$t('load_test.project_name')"

View File

@ -6,10 +6,12 @@
<div class="kv-row" v-for="(item, index) in items" :key="index">
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
<el-col>
<el-input v-model="item.name" placeholder="Key" size="small" maxlength="100" @change="change"/>
<el-input v-model="item.name" size="small" maxlength="100" @change="change"
:placeholder="$t('api_test.key')"/>
</el-col>
<el-col>
<el-input v-model="item.value" placeholder="Value" size="small" maxlength="100" @change="change"/>
<el-input v-model="item.value" size="small" maxlength="100" @change="change"
:placeholder="$t('api_test.value')"/>
</el-col>
<el-col class="kv-delete">
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"

View File

@ -39,8 +39,8 @@
<script>
import MsApiCollapseItem from "./ApiCollapseItem";
import MsApiCollapse from "./ApiCollapse";
import MsApiCollapseItem from "./collapse/ApiCollapseItem";
import MsApiCollapse from "./collapse/ApiCollapse";
import MsApiRequestConfig from "./ApiRequestConfig";
import MsApiRequestForm from "./ApiRequestForm";
import MsApiScenarioForm from "./ApiScenarioForm";

View File

@ -8,24 +8,25 @@
<!-- <el-input :placeholder="$t('api_test.scenario.base_url_description')" v-model="scenario.url" maxlength="100"/>-->
<!-- </el-form-item>-->
<!-- <el-tabs v-model="activeName">-->
<!-- <el-tab-pane :label="$t('api_test.scenario.parameters')" name="parameters">-->
<!-- <ms-api-key-value :items="scenario.parameters" :description="$t('api_test.scenario.kv_description')"/>-->
<!-- </el-tab-pane>-->
<!-- <el-tab-pane :label="$t('api_test.scenario.headers')" name="headers">-->
<!-- <ms-api-key-value :items="scenario.headers" :description="$t('api_test.scenario.kv_description')"/>-->
<!-- </el-tab-pane>-->
<!-- </el-tabs>-->
<el-tabs v-model="activeName">
<el-tab-pane :label="$t('api_test.scenario.variables')" name="parameters">
<ms-api-scenario-variables :items="scenario.variables" :description="$t('api_test.scenario.kv_description')"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.scenario.headers')" name="headers">
<ms-api-key-value :items="scenario.headers" :description="$t('api_test.scenario.kv_description')"/>
</el-tab-pane>
</el-tabs>
</el-form>
</template>
<script>
import MsApiKeyValue from "./ApiKeyValue";
import {Scenario} from "../model/ScenarioModel";
import MsApiScenarioVariables from "./ApiScenarioVariables";
export default {
name: "MsApiScenarioForm",
components: {MsApiKeyValue},
components: {MsApiScenarioVariables, MsApiKeyValue},
props: {
scenario: Scenario
},

View File

@ -0,0 +1,86 @@
<template>
<div>
<span class="kv-description" v-if="description">
{{description}}
</span>
<div class="kv-row" v-for="(item, index) in items" :key="index">
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
<el-col>
<ms-api-variable-input v-model="item.name" size="small" maxlength="100" @change="change"
:placeholder="$t('api_test.variable_name')"/>
</el-col>
<el-col>
<el-input v-model="item.value" size="small" maxlength="100" @change="change"
:placeholder="$t('api_test.value')"/>
</el-col>
<el-col class="kv-delete">
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
:disabled="isDisable(index)"/>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import {KeyValue} from "../model/ScenarioModel";
import MsApiVariableInput from "./ApiVariableInput";
export default {
name: "MsApiScenarioVariables",
components: {MsApiVariableInput},
props: {
description: String,
items: Array
},
methods: {
remove: function (index) {
this.items.splice(index, 1);
this.$emit('change', this.items);
},
change: function () {
let isNeedCreate = true;
let removeIndex = -1;
this.items.forEach((item, index) => {
if (!item.name && !item.value) {
//
if (index !== this.items.length - 1) {
removeIndex = index;
}
//
isNeedCreate = false;
}
});
if (isNeedCreate) {
this.items.push(new KeyValue());
}
this.$emit('change', this.items);
// TODO key
},
isDisable: function (index) {
return this.items.length - 1 === index;
}
},
created() {
if (this.items.length === 0) {
this.items.push(new KeyValue());
}
}
}
</script>
<style scoped>
.kv-description {
font-size: 13px;
}
.kv-row {
margin-top: 10px;
}
.kv-delete {
width: 60px;
}
</style>

View File

@ -0,0 +1,89 @@
<template>
<div class="variable-input">
<el-input :value="value" v-bind="$attrs" :size="size" @change="change" @input="input"/>
<div class="variable-combine" v-if="value">
<div class="variable">{{variable}}</div>
<el-tooltip :content="$t('api_test.copied')" manual v-model="visible" placement="top" :visible-arrow="false">
<i class="el-icon-copy-document copy" @click="copy"/>
</el-tooltip>
</div>
</div>
</template>
<script>
export default {
name: "MsApiVariableInput",
props: ['value', 'size'],
data() {
return {
visible: false
}
},
methods: {
copy() {
let input = document.createElement("input");
document.body.appendChild(input);
input.value = this.variable;
input.select();
if (input.setSelectionRange) {
input.setSelectionRange(0, input.value.length);
}
document.execCommand("copy");
document.body.removeChild(input);
this.visible = true;
setTimeout(() => {
this.visible = false;
}, 1000);
},
change(value) {
this.$emit('change', value);
},
input(value) {
this.$emit('input', value);
}
},
computed: {
variable() {
return "${" + this.value + "}";
}
}
}
</script>
<style scoped>
.variable-input {
position: relative;
}
.variable-combine {
color: #7F7F7F;
max-width: 80px;
line-height: 32px;
position: absolute;
top: 0;
right: 25px;
margin-right: -20px;
display: flex;
align-items: center;
}
.variable-combine .variable {
display: inline-block;
max-width: 60px;
margin-right: 10px;
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.variable-combine .copy {
font-size: 14px;
cursor: pointer;
color: #1E90FF;
}
</style>

View File

@ -2,17 +2,8 @@
<div>
<el-row :gutter="10" type="flex" justify="space-between" align="middle">
<el-col :span="10">
<div class="variable">
<el-input v-model="common.variable" maxlength="60" size="small" @input="change"
:placeholder="$t('api_test.request.extract.variable_name')"/>
<div class="variable-combine" v-if="common.variable && edit">
<div class="value">{{common.value}}</div>
<el-tooltip :content="$t('api_test.request.extract.copied')" manual v-model="visible" placement="top"
:visible-arrow="false">
<i class="el-icon-copy-document copy" @click="copy"></i>
</el-tooltip>
</div>
</div>
<ms-api-variable-input v-model="common.variable" size="small" maxlength="60" @change="change"
:placeholder="$t('api_test.variable_name')"/>
</el-col>
<el-col>
<el-input v-model="common.expression" maxlength="255" size="small" :placeholder="expression"/>
@ -27,10 +18,11 @@
<script>
import {EXTRACT_TYPE, ExtractCommon} from "../../model/ScenarioModel";
import MsApiVariableInput from "../ApiVariableInput";
export default {
name: "MsApiExtractCommon",
components: {MsApiVariableInput},
props: {
extractType: {
type: String,

View File

@ -371,10 +371,10 @@ export class Arguments extends DefaultTestElement {
let collectionProp = this.collectionProp('Arguments.arguments');
this.args.forEach(arg => {
let elementProp = collectionProp.elementProp(arg.name, 'HTTPArgument');
elementProp.boolProp('HTTPArgument.always_encode', arg.encode || true);
let elementProp = collectionProp.elementProp(arg.name, 'Argument');
elementProp.stringProp('Argument.name', arg.name);
elementProp.stringProp('Argument.value', arg.value);
elementProp.stringProp('Argument.desc', arg.desc);
elementProp.stringProp('Argument.metadata', arg.metadata || "=");
});
}

View File

@ -12,7 +12,7 @@ import {
ResponseCodeAssertion,
ResponseDataAssertion,
ResponseHeadersAssertion,
BackendListener, RegexExtractor, JSONPostProcessor, XPath2Extractor
BackendListener, RegexExtractor, JSONPostProcessor, XPath2Extractor, Arguments
} from "./JMX";
export const uuid = function () {
@ -124,12 +124,12 @@ export class Scenario extends BaseConfig {
this.id = uuid();
this.name = null;
this.url = null;
this.parameters = [];
this.variables = [];
this.headers = [];
this.requests = [];
this.set(options);
this.sets({parameters: KeyValue, headers: KeyValue, requests: Request}, options);
this.sets({variables: KeyValue, headers: KeyValue, requests: Request}, options);
}
initOptions(options) {
@ -397,6 +397,10 @@ class JMXGenerator {
test.scenarioDefinition.forEach(scenario => {
let threadGroup = new ThreadGroup(scenario.name + SPLIT + scenario.id);
this.addScenarioVariables(threadGroup, scenario);
this.addScenarioHeaders(threadGroup, scenario);
scenario.requests.forEach(request => {
if (!request.isValid()) return;
@ -434,6 +438,22 @@ class JMXGenerator {
this.jmeterTestPlan.put(testPlan);
}
addScenarioVariables(threadGroup, scenario) {
let args = scenario.variables.filter(this.filter)
if (args.length > 0) {
let name = scenario.name + " Variables"
threadGroup.put(new Arguments(name, args));
}
}
addScenarioHeaders(threadGroup, scenario) {
let headers = scenario.headers.filter(this.filter)
if (headers.length > 0) {
let name = scenario.name + " Headers"
threadGroup.put(new HeaderManager(name, headers));
}
}
addRequestHeader(httpSamplerProxy, request) {
let name = request.name + " Headers";
let headers = request.headers.filter(this.filter);

View File

@ -1,8 +1,8 @@
<template>
<div class="dialog-footer">
<el-button @click="cancel"> </el-button>
<el-button type="primary" @click="confirm" @keydown.enter.native.prevent> </el-button>
<el-button @click="cancel">{{$t('commons.cancel')}}</el-button>
<el-button type="primary" @click="confirm" @keydown.enter.native.prevent>{{$t('commons.confirm')}}</el-button>
</div>
</template>

View File

@ -4,7 +4,7 @@
:key="index"
:effect="effect"
:type="type"
:content="role.name"/>
:content="$t('role.' + role.id)"/>
</div>
</template>

View File

@ -0,0 +1,132 @@
<template>
<el-menu :unique-opened="true" mode="horizontal" router
class="header-user-menu align-right"
background-color="#2c2a48"
text-color="#fff">
<el-submenu index="1" popper-class="submenu"
v-permission="['org_admin', 'test_manager', 'test_user', 'test_viewer']">
<template v-slot:title>{{$t('commons.organization')}}{{currentOrganizationName}}</template>
<label v-for="(item,index) in organizationList" :key="index">
<el-menu-item @click="changeOrg(item)">{{item.name}}
<i class="el-icon-check"
v-if="item.id === currentUserInfo.lastOrganizationId"></i>
</el-menu-item>
</label>
</el-submenu>
<el-submenu index="2" popper-class="submenu" v-permission="['test_manager', 'test_user', 'test_viewer']">
<template v-slot:title>{{$t('commons.workspace')}}{{currentWorkspaceName}}</template>
<label v-for="(item,index) in workspaceList" :key="index">
<el-menu-item @click="changeWs(item)">
{{item.name}}
<i class="el-icon-check" v-if="item.id === currentUserInfo.lastWorkspaceId"></i>
</el-menu-item>
</label>
</el-submenu>
</el-menu>
</template>
<script>
import {
ROLE_ORG_ADMIN,
ROLE_TEST_MANAGER,
ROLE_TEST_USER,
ROLE_TEST_VIEWER,
WORKSPACE_ID
} from '../../../../common/js/constants';
import {getCurrentUser, hasRoles, saveLocalStorage} from "../../../../common/js/utils";
export default {
name: "MsHeaderOrgWs",
created() {
this.initMenuData();
this.getCurrentUserInfo();
},
data() {
return {
organizationList: [
{name: this.$t('organization.none')},
],
workspaceList: [
{name: this.$t('workspace.none')},
],
currentUserInfo: {},
currentUserId: getCurrentUser().id,
workspaceIds: [],
currentOrganizationName: this.$t('organization.select'),
currentWorkspaceName: this.$t('workspace.select')
}
},
computed: {
currentUser: () => {
return getCurrentUser();
}
},
methods: {
initMenuData() {
if (hasRoles(ROLE_ORG_ADMIN, ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
this.$get("/organization/list/userorg/" + this.currentUserId, response => {
let data = response.data;
this.organizationList = data;
let org = data.filter(r => r.id === this.currentUser.lastOrganizationId);
if (org.length > 0) {
this.currentOrganizationName = org[0].name;
}
});
}
if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
if (!this.currentUser.lastOrganizationId) {
return false;
}
this.$get("/workspace/list/orgworkspace/", response => {
let data = response.data;
if (data.length === 0) {
this.workspaceList = [{name: this.$t('workspace.none')}]
} else {
this.workspaceList = data;
let workspace = data.filter(r => r.id === this.currentUser.lastWorkspaceId);
if (workspace.length > 0) {
this.currentWorkspaceName = workspace[0].name;
localStorage.setItem(WORKSPACE_ID, workspace[0].id);
}
}
})
}
},
getCurrentUserInfo() {
this.$get("/user/info/" + this.currentUserId, response => {
this.currentUserInfo = response.data;
})
},
changeOrg(data) {
let orgId = data.id;
if (!orgId) {
return false;
}
this.$post("/user/switch/source/org/" + orgId, {}, response => {
saveLocalStorage(response);
this.$router.push('/');
window.location.reload();
});
},
changeWs(data) {
let workspaceId = data.id;
if (!workspaceId) {
return false;
}
this.$post("/user/switch/source/ws/" + workspaceId, {}, response => {
saveLocalStorage(response);
localStorage.setItem("workspace_id", workspaceId);
this.$router.push('/');
window.location.reload();
})
}
}
}
</script>
<style scoped>
.el-icon-check {
color: #44b349;
margin-left: 10px;
}
</style>

View File

@ -18,7 +18,7 @@
v-permission="['test_manager','test_user','test_viewer']">
{{$t('commons.performance')}}
</el-menu-item>
<el-menu-item index="/setting/personsetting" onselectstart="return false">
<el-menu-item index="/setting" onselectstart="return false">
{{$t('commons.system_setting')}}
</el-menu-item>
</el-menu>

View File

@ -1,79 +1,23 @@
<template>
<el-row type="flex" justify="end">
<el-col :span="20">
<el-menu :unique-opened="true" mode="horizontal" router
class="header-user-menu align-right"
background-color="#2c2a48"
text-color="#fff">
<el-submenu index="1" popper-class="submenu"
v-permission="['org_admin', 'test_manager', 'test_user', 'test_viewer']">
<template v-slot:title>{{$t('commons.organization')}}{{currentOrganizationName}}</template>
<label v-for="(item,index) in organizationList" :key="index">
<el-menu-item @click="changeOrg(item)">{{item.name}}
<i class="el-icon-check"
v-if="item.id === currentUserInfo.lastOrganizationId"></i>
</el-menu-item>
</label>
</el-submenu>
<el-submenu index="2" popper-class="submenu" v-permission="['test_manager', 'test_user', 'test_viewer']">
<template v-slot:title>{{$t('commons.workspace')}}{{currentWorkspaceName}}</template>
<label v-for="(item,index) in workspaceList" :key="index">
<el-menu-item @click="changeWs(item)">
{{item.name}}
<i class="el-icon-check" v-if="item.id === currentUserInfo.lastWorkspaceId"></i>
</el-menu-item>
</label>
</el-submenu>
</el-menu>
</el-col>
<el-col :span="4">
<el-dropdown size="medium" @command="handleCommand" class="align-right">
<span class="dropdown-link">
{{currentUser.name}}<i class="el-icon-caret-bottom el-icon--right"/>
</span>
<template v-slot:dropdown>
<el-dropdown-menu>
<el-dropdown-item command="personal">{{$t('commons.personal_information')}}</el-dropdown-item>
<el-dropdown-item command="logout">{{$t('commons.exit_system')}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
</el-row>
<el-dropdown size="medium" @command="handleCommand" class="align-right">
<span class="dropdown-link">
{{currentUser.name}}<i class="el-icon-caret-bottom el-icon--right"/>
</span>
<template v-slot:dropdown>
<el-dropdown-menu>
<el-dropdown-item command="personal">{{$t('commons.personal_information')}}</el-dropdown-item>
<el-dropdown-item command="logout">{{$t('commons.exit_system')}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script>
import {
ROLE_ORG_ADMIN,
ROLE_TEST_MANAGER,
ROLE_TEST_USER,
ROLE_TEST_VIEWER,
TokenKey,
WORKSPACE_ID
} from '../../../../common/js/constants';
import {getCurrentUser, hasRoles, saveLocalStorage} from "../../../../common/js/utils";
import {TokenKey} from '../../../../common/js/constants';
import {getCurrentUser} from "../../../../common/js/utils";
export default {
name: "MsUser",
created() {
this.initMenuData();
this.getCurrentUserInfo();
},
data() {
return {
organizationList: [
{name: this.$t('organization.none')},
],
workspaceList: [
{name: this.$t('workspace.none')},
],
currentUserInfo: {},
currentUserId: getCurrentUser().id,
workspaceIds: [],
currentOrganizationName: this.$t('organization.select'),
currentWorkspaceName: this.$t('workspace.select')
}
},
computed: {
currentUser: () => {
return getCurrentUser();
@ -95,64 +39,6 @@
default:
break;
}
},
initMenuData() {
if (hasRoles(ROLE_ORG_ADMIN, ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
this.$get("/organization/list/userorg/" + this.currentUserId, response => {
let data = response.data;
this.organizationList = data;
let org = data.filter(r => r.id === this.currentUser.lastOrganizationId);
if (org.length > 0) {
this.currentOrganizationName = org[0].name;
}
});
}
if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
if (!this.currentUser.lastOrganizationId) {
return false;
}
this.$get("/workspace/list/orgworkspace/", response => {
let data = response.data;
if (data.length === 0) {
this.workspaceList = [{name: this.$t('workspace.none')}]
} else {
this.workspaceList = data;
let workspace = data.filter(r => r.id === this.currentUser.lastWorkspaceId);
if (workspace.length > 0) {
this.currentWorkspaceName = workspace[0].name;
localStorage.setItem(WORKSPACE_ID, workspace[0].id);
}
}
})
}
},
getCurrentUserInfo() {
this.$get("/user/info/" + this.currentUserId, response => {
this.currentUserInfo = response.data;
})
},
changeOrg(data) {
let orgId = data.id;
if (!orgId) {
return false;
}
this.$post("/user/switch/source/org/" + orgId, {}, response => {
saveLocalStorage(response);
this.$router.push('/');
window.location.reload();
});
},
changeWs(data) {
let workspaceId = data.id;
if (!workspaceId) {
return false;
}
this.$post("/user/switch/source/ws/" + workspaceId, {}, response => {
saveLocalStorage(response);
localStorage.setItem("workspace_id", workspaceId);
this.$router.push('/');
window.location.reload();
})
}
}
}
@ -166,11 +52,6 @@
line-height: 40px;
}
.el-icon-check {
color: #44b349;
margin-left: 10px;
}
.align-right {
float: right;
}

View File

@ -0,0 +1,96 @@
<template>
<el-menu :unique-opened="true" class="header-user-menu align-right"
mode="horizontal"
background-color="#2c2a48"
text-color="#fff"
active-text-color="#fff"
>
<el-submenu index="1">
<template slot="title">
<font-awesome-icon class="icon global" :icon="['fas', 'globe']"/>
<span>{{language}}</span>
</template>
<el-menu-item @click="changeLanguage('zh_CN')">
简体中文<i class="el-icon-check" v-if="currentUserInfo.language==='zh_CN' || !currentUserInfo.language"/>
</el-menu-item>
<el-menu-item @click="changeLanguage('zh_TW')">
繁體中文<i class="el-icon-check" v-if="currentUserInfo.language==='zh_TW'"/>
</el-menu-item>
<el-menu-item @click="changeLanguage('en_US')">
English<i class="el-icon-check" v-if="currentUserInfo.language==='en_US'"/>
</el-menu-item>
</el-submenu>
</el-menu>
</template>
<script>
import {TokenKey, ZH_CN, ZH_TW, EN_US} from '../../../../common/js/constants';
import {getCurrentUser} from "../../../../common/js/utils";
export default {
name: "MsLanguageSwitch",
data() {
return {
currentUserInfo: {},
language: ''
};
},
created() {
let lang = this.currentUser().language;
this.currentUserInfo = this.currentUser();
if (!lang) {
lang = 'zh_CN';
}
this.$setLang(lang);
switch (lang) {
case ZH_CN:
this.language = '简体中文';
break;
case ZH_TW:
this.language = '繁體中文';
break;
case EN_US:
this.language = 'English';
break;
default:
this.language = '简体中文';
break;
}
},
methods: {
currentUser: () => {
return getCurrentUser();
},
changeLanguage(language) {
let user = {
id: this.currentUser().id,
language: language
};
this.result = this.$post("/user/update/current", user, response => {
localStorage.setItem(TokenKey, JSON.stringify(response.data));
window.location.reload();
});
}
}
}
</script>
<style scoped>
.el-icon-check {
color: #44b349;
margin-left: 10px;
}
.align-right {
float: right;
}
.icon {
width: 24px;
}
.global {
color: #fff;
}
</style>

View File

@ -3,7 +3,7 @@
<el-card class="table-card">
<template v-slot:header>
<ms-table-header :condition.sync="condition" @search="initTableData" @create="create"
:create-tip="btnTips" :title="$t('commons.member')"/>
:create-tip="$t('member.create')" :title="$t('commons.member')"/>
</template>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="name" :label="$t('commons.username')"/>
@ -111,7 +111,6 @@
data() {
return {
result: {},
btnTips: this.$t('member.create'),
createVisible: false,
updateVisible: false,
form: {},

View File

@ -3,7 +3,7 @@
<el-card class="table-card" v-loading="result.loading">
<template v-slot:header>
<ms-table-header :condition.sync="condition" @search="list" @create="create"
:create-tip="btnTips" :title="$t('commons.workspace')"/>
:create-tip="$t('workspace.create')" :title="$t('commons.workspace')"/>
</template>
<el-table :data="items" style="width: 100%">
<el-table-column prop="name" :label="$t('commons.name')"/>
@ -43,7 +43,7 @@
<!-- dialog of workspace member -->
<el-dialog :visible.sync="dialogWsMemberVisible" width="70%" :destroy-on-close="true" @close="closeMemberFunc">
<ms-table-header :condition.sync="dialogCondition" @create="addMember" @search="dialogSearch"
:create-tip="dialogBtnTips" :title="$t('commons.member')"/>
:create-tip="$t('member.create')" :title="$t('commons.member')"/>
<!-- organization member table -->
<el-table :data="memberLineData" style="width: 100%;margin-top: 5px;">
<el-table-column prop="name" :label="$t('commons.username')"/>
@ -366,9 +366,6 @@
dialogWsMemberVisible: false,
dialogWsMemberAddVisible: false,
dialogWsMemberUpdateVisible: false,
btnTips: this.$t('workspace.create'),
dialogBtnTips: this.$t('member.create'),
addTips: this.$t('member.create'),
condition: {},
dialogCondition: {},
items: [],

View File

@ -4,7 +4,7 @@
<el-card class="table-card">
<template v-slot:header>
<ms-table-header :condition.sync="condition" @search="initTableData" @create="create"
:create-tip="btnTips" :title="$t('commons.organization')"/>
:create-tip="$t('organization.create')" :title="$t('commons.organization')"/>
</template>
<!-- system menu organization table-->
<el-table :data="tableData" style="width: 100%">
@ -30,7 +30,7 @@
<!-- dialog of organization member -->
<el-dialog :visible.sync="dialogOrgMemberVisible" width="70%" :destroy-on-close="true" @close="closeMemberFunc">
<ms-table-header :condition.sync="dialogCondition" @create="addMember" @search="dialogSearch"
:create-tip="dialogBtnTips" :title="$t('commons.member')"/>
:create-tip="$t('member.create')" :title="$t('commons.member')"/>
<!-- organization member table -->
<el-table :data="memberLineData" style="width: 100%;margin-top:5px;">
<el-table-column prop="name" :label="$t('commons.username')"/>
@ -201,8 +201,6 @@
dialogPageSize: 5,
dialogTotal: 0,
currentRow: {},
btnTips: this.$t('organization.create'),
dialogBtnTips: this.$t('member.create'),
condition: {},
dialogCondition: {},
tableData: [],

View File

@ -3,7 +3,7 @@
<el-card class="table-card" v-loading="result.loading">
<template v-slot:header>
<ms-table-header :condition.sync="condition" @search="list" @create="create"
:create-tip="btnTips" :title="$t('commons.workspace')"/>
:create-tip="$t('workspace.create')" :title="$t('commons.workspace')"/>
</template>
<!-- workspace table -->
<el-table :data="items" style="width: 100%">
@ -87,7 +87,7 @@
<!-- dialog of workspace member -->
<el-dialog :visible.sync="dialogWsMemberVisible" width="70%" :destroy-on-close="true" @close="closeWsMemberDialog">
<ms-table-header :condition.sync="dialogCondition" @create="addMember" @search="dialogSearch"
:create-tip="dialogBtnTips" :title="$t('commons.member')"/>
:create-tip="$t('member.create')" :title="$t('commons.member')"/>
<!-- organization member table -->
<el-table :data="memberLineData" style="width: 100%;margin-top: 5px;">
<el-table-column prop="name" :label="$t('commons.username')"/>
@ -420,9 +420,6 @@
dialogWsMemberVisible: false,
dialogWsMemberAddVisible: false,
dialogWsMemberUpdateVisible: false,
btnTips: this.$t('workspace.create'),
dialogBtnTips: this.$t('member.create'),
addTips: this.$t('member.create'),
condition: {},
dialogCondition: {},
items: [],

View File

@ -3,7 +3,7 @@
<el-card class="table-card" v-loading="result.loading">
<template v-slot:header>
<ms-table-header :condition.sync="condition" @search="search" @create="create"
:create-tip="btnTips" :title="$t('commons.test_resource_pool')"/>
:create-tip="$t('test_resource_pool.create_resource_pool')" :title="$t('commons.test_resource_pool')"/>
</template>
<el-table :data="items" style="width: 100%">
<el-table-column prop="name" :label="$t('commons.name')"/>
@ -229,9 +229,6 @@
createVisible: false,
infoList: [],
updateVisible: false,
btnTips: this.$t('test_resource_pool.create_resource_pool'),
btnTipsAdd: this.$t("commons.add"),
btnTipsDel: this.$t("commons.delete"),
queryPath: "testresourcepool/list",
condition: {},
items: [],

View File

@ -4,7 +4,7 @@
<el-card class="table-card">
<template v-slot:header>
<ms-table-header :condition.sync="condition" @search="search" @create="create"
:create-tip="btnTips" :title="$t('commons.member')"/>
:create-tip="$t('user.create')" :title="$t('commons.member')"/>
</template>
<el-table :data="tableData" style="width: 100%">
@ -161,7 +161,6 @@
currentPage: 1,
pageSize: 5,
total: 0,
btnTips: this.$t('user.create'),
condition: {},
tableData: [],
form: {},

View File

@ -3,7 +3,7 @@
<el-card class="table-card">
<template v-slot:header>
<ms-table-header :condition.sync="condition" @search="initTableData" @create="create"
:create-tip="btnTips" :title="$t('commons.member')"/>
:create-tip="$t('member.create')" :title="$t('commons.member')"/>
</template>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="name" :label="$t('commons.username')"/>
@ -111,7 +111,6 @@
return {
result: {},
form: {},
btnTips: this.$t('member.create'),
createVisible: false,
updateVisible: false,
queryPath: "/user/ws/member/list",

View File

@ -32,3 +32,7 @@
.header-top-menus.el-menu--horizontal > li.is-active {
background: #595591 !important;
}
.el-menu.el-menu--horizontal {
border-bottom: none;
}

View File

@ -13,3 +13,7 @@ export const REFRESH_SESSION_USER_URL = 'user/refresh';
export const WORKSPACE = 'workspace';
export const ORGANIZATION = 'organization';
export const DEFAULT = 'default';
export const ZH_CN = 'zh_CN';
export const ZH_TW = 'zh_TW';
export const EN_US = 'en_US';

View File

@ -72,7 +72,7 @@ export default {
'select': 'Select Workspace',
},
organization: {
'create': 'Create',
'create': 'Create Organization',
'modify': 'Modify',
'delete_confirm': 'Are you sure you want to delete this workspace?',
'input_name': 'Please enter a organization name',
@ -124,6 +124,11 @@ export default {
},
role: {
'please_choose_role': 'Please Choose Role',
'admin': 'Admin',
'org_admin': 'Org_Admin',
'test_manager': 'Test_Manager',
'test_user': 'Test_User',
'test_viewer': 'Test_Viewer',
},
report: {
'recent': 'Recent Report',
@ -209,15 +214,19 @@ export default {
reset: "Rest",
input_name: "Please enter the test name",
select_project: "Select project",
variable_name: "Variable name",
copied: "copied",
key: "Key",
value: "Value",
scenario: {
config: "Scenario Config",
input_name: "Please enter the scenario name",
name: "Scenario Name",
base_url: "Base URL",
base_url_description: "Base URL as URL prefix for all requests",
parameters: "Parameters",
variables: "Variables",
headers: "Headers",
kv_description: "Will be used for requests where the item is not set",
kv_description: "Variables are available for all requests",
},
request: {
input_name: "Please enter the request name",
@ -225,7 +234,7 @@ export default {
method: "Method",
url: "URL",
url_description: "etc: https://fit2cloud.com",
parameters: "Parameters",
parameters: "Query parameters",
parameters_desc: "Parameters will be appended to the URL e.g. https://fit2cloud.com?Name=Value&Name2=Value2",
headers: "Headers",
body: "Body",
@ -246,18 +255,16 @@ export default {
end_with: "End With",
value: "Value",
expression: "Expression",
response_in_time: "Response Time",
response_in_time: "Response in time",
},
extract: {
label: "Extract from response",
select_type: "Choose type",
description: "Extract data from the response and store it in variables. Use the variables in subsequent requests.",
regex: "Regex",
variable_name: "Variable name",
regex_expression: "Regular expression",
json_path_expression: "JSONPath expression",
xpath_expression: "XPath expression",
copied: "Copied"
}
}
},

View File

@ -124,6 +124,11 @@ export default {
},
role: {
'please_choose_role': '请选择角色',
'admin': '系统管理员',
'org_admin': '组织管理员',
'test_manager': '测试经理',
'test_user': '测试人员',
'test_viewer': 'Viewer'
},
report: {
'recent': '最近的报告',
@ -209,15 +214,19 @@ export default {
reset: "重置",
input_name: "请输入测试名称",
select_project: "请选择项目",
variable_name: "变量名",
copied: "已拷贝",
key: "键",
value: "值",
scenario: {
config: "场景配置",
input_name: "请输入场景名称",
name: "场景名称",
base_url: "基础URL",
base_url_description: "基础URL作为所有请求的URL前缀",
parameters: "请求变量",
variables: "自定义变量",
headers: "请求头",
kv_description: "将用于未设置该项的请求",
kv_description: "所有请求可以使用自定义变量",
},
request: {
input_name: "请输入请求名称",
@ -253,11 +262,9 @@ export default {
select_type: "请选择类型",
description: "从响应结果中提取数据并将其存储在变量中,在后续请求中使用变量。",
regex: "正则",
variable_name: "变量名",
regex_expression: "Perl型正则表达式",
json_path_expression: "JSONPath表达式",
xpath_expression: "XPath表达式",
copied: "已拷贝"
}
}
},

View File

@ -1,35 +1,428 @@
export default {
commons: {
'workspace': '工作空间',
'organization': '组织',
'name': '名称',
'workspace': '工作空間',
'organization': '組織',
'setting': '設置',
'project': '項目',
'name': '名稱',
'description': '描述',
'clear': '清空',
'save': '保存',
'save_success': '保存成功',
'delete_success': '删除成功',
'confirm': '确定',
'delete_success': '刪除成功',
'modify_success': '修改成功',
'delete_cancel': '已取消刪除',
'confirm': '確定',
'cancel': '取消',
'prompt': '提示',
'operating': '操作',
'input_limit': '長度在 {0} 到 {1} 個字符',
'login': '登錄',
'welcome': '歡迎回來請輸入用戶名和密碼登錄MeterSphere',
'username': '用戶名',
'password': '密碼',
'input_username': '請輸入用戶名',
'input_password': '請輸入密碼',
'test': '測試',
'create_time': '創建時間',
'update_time': '更新時間',
'add': '添加',
'member': '成員',
'email': '郵箱',
'phone': '電話',
'role': '角色',
'personal_info': '個人信息',
'status': '狀態',
'show_all': '顯示全部',
'report': '報告',
'user': '用戶',
'system': '系統',
'personal_setting': '個人設置',
'test_resource_pool': '測試資源池',
'system_setting': '系統設置',
'api': '接口測試',
'performance': '性能測試',
'functional': '功能測試',
'input_content': '請輸入內容',
'create': '新建',
'edit': '編輯',
'copy': '複製',
'refresh': '刷新',
'remark': '備註',
'delete': '刪除',
'not_filled': '未填寫',
'please_select': '請選擇',
'search_by_name': '根據名稱搜索',
'personal_information': '個人信息',
'exit_system': '退出系統',
'verification': '驗證',
'set_admin': '設置為管理員',
},
workspace: {
'create': '创建工作空间',
'delete_confirm': '这个工作空间确定要删除吗?',
'add': '添加工作空间',
'input_name': '请输入工作空间名称',
'search_by_name': '根据名称搜索',
'organization_name': '所属组织',
'please_choose_organization': '请选择组织',
'create': '創建工作空間',
'update': '修改工作空間',
'delete_confirm': '刪除工作空間會關聯刪除該工作空間下的資源,確定要刪除嗎?',
'add': '添加工作空間',
'input_name': '請輸入工作空間名稱',
'search_by_name': '根據名稱搜索',
'organization_name': '所屬組織',
'please_choose_organization': '請選擇組織',
'please_select_a_workspace_first': '請先選擇工作空間! ',
'none': '無工作空間',
'select': '選擇工作空間',
},
organization: {
'create': '创建组织',
'modify': '修改组织',
'search_by_name': '根据名称搜索',
'create': '創建組織',
'modify': '修改組織',
'delete_confirm': '刪除組織會關聯刪除該組織下的資源,確定要刪除嗎?',
'input_name': '請輸入組織名稱',
'select_organization': '請選擇組織',
'search_by_name': '根據名稱搜索',
'special_characters_are_not_supported': '不支持特殊字符',
'none': '無組織',
'select': '選擇組織',
},
project: {
'recent': '最近的项目'
'recent': '最近的項目',
'create': '創建項目',
'edit': '編輯項目',
'delete_confirm': '這個項目確定要刪除嗎?',
'search_by_name': '根據名稱搜索',
'input_name': '請輸入項目名稱',
'owning_workspace': '所屬工作空間',
'please_choose_workspace': '請選擇工作空間',
},
member: {
'create': '添加成員',
'modify': '修改成員',
'delete_confirm': '這個用戶確定要刪除嗎?',
'please_choose_member': '請選擇成員',
'search_by_name': '根據名稱搜索',
'modify_personal_info': '修改個人信息',
'edit_password': '修改密碼',
'edit_information': '編輯信息',
'input_name': '請輸入名稱',
'input_email': '請輸入郵箱',
'special_characters_are_not_supported': '不支持特殊字符',
'mobile_number_format_is_incorrect': '手機號碼格式不正確',
'email_format_is_incorrect': '郵箱格式不正確',
'password_format_is_incorrect': '密碼格式不正確(至少8-16個字符至少1個大寫字母1個小寫字母和1個數字)',
'old_password': '舊密碼',
'new_password': '新密碼',
},
user: {
'create': '創建用戶',
'modify': '修改用戶',
'input_name': '請輸入用戶名',
'input_id': '請輸入ID',
'input_email': '請輸入郵箱',
'input_password': '請輸入密碼',
'special_characters_are_not_supported': '不支持特殊字符',
'mobile_number_format_is_incorrect': '手機號碼格式不正確',
'email_format_is_incorrect': '郵箱格式不正確',
'delete_confirm': '這個用戶確定要刪除嗎?',
},
role: {
'please_choose_role': '請選擇角色',
'admin': '系統管理員',
'org_admin': '組織管理員',
'test_manager': '測試經理',
'test_user': '測試人員',
'test_viewer': 'Viewer'
},
report: {
'recent': '最近的報告',
'search_by_name': '根據名稱搜索',
'test_name': '所屬測試',
'test_overview': '測試概覽',
'test_request_statistics': '請求統計',
'test_error_log': '錯誤記錄',
'test_log_details': '日誌詳情',
'test_details': '測試詳情',
'test_duration': '持續時間:{0} 分鐘 {1} 秒',
'test_start_time': '開始時間',
'test_end_time': '結束時間',
'test_stop_now': '立即停止',
'test_execute_again': '再次執行',
'export': '導出',
'compare': '比較',
'generation_error': '報告生成錯誤,無法查看!',
'being_generated': '報告正在生成中...',
'delete_confirm': '確認刪除報告: ',
},
load_test: {
'operating': '操作',
'recent': '最近的測試',
'search_by_name': '根據名稱搜索',
'project_name': '所屬項目',
'delete_confirm': '確認刪除測試: ',
'input_name': '請輸入名稱',
'select_project': '請選擇項目',
'save_and_run': '保存並執行',
'basic_config': '場景配置',
'pressure_config': '壓力配置',
'advanced_config': '高級配置',
'runtime_config': '運行配置',
'is_running': '正在運行! ',
'test_name_is_null': '測試名稱不能為空! ',
'project_is_null': '項目不能為空! ',
'jmx_is_null': '只能包含一個JMX文件 ',
'file_name': '文件名',
'file_size': '文件大小',
'file_type': '文件類型',
'file_status': '文件狀態',
'last_modify_time': '修改時間',
'upload_tips': '將文件拖到此處,或<em>點擊上傳</em>',
'upload_type': '只能上傳JMX/CSV文件',
'related_file_not_found': "未找到關聯的測試文件!",
'delete_file_confirm': '確認刪除文件: ',
'delete_file': "請先刪除已存在的文件!",
'thread_num': '並髮用戶數:',
'input_thread_num': '請輸入線程數',
'duration': '壓測時長(分鐘):',
'input_duration': '請輸入時長',
'rps_limit': 'RPS上限',
'input_rps_limit': '請輸入限制',
'ramp_up_time_within': '在',
'ramp_up_time_minutes': '分鐘內,分',
'ramp_up_time_times': '次增加並髮用戶',
'advanced_config_error': '高級配置校驗失敗',
'domain_bind': '域名綁定',
'domain': '域名',
'enable': '是否啟用',
'ip': 'IP地址',
'params': '自定義屬性',
'param_name': '屬性名',
'param_value': '屬性值',
'domain_is_duplicate': '域名不能重複',
'param_is_duplicate': '參數名不能重複',
'domain_ip_is_empty': '域名和IP不能為空',
'param_name_value_is_empty': '參數名和參數值不能為空',
'connect_timeout': '建立連接超時時間',
'custom_http_code': '自定義 HTTP 響應成功狀態碼',
'separated_by_commas': '按逗號分隔',
'create': '創建測試',
'select_resource_pool': '請選擇資源池',
'resource_pool_is_null': '資源池為空',
'download_log_file': '下載完整日誌文件',
'pressure_prediction_chart': '壓力預估圖',
},
api_test: {
save_and_run: "保存並執行",
run: "執行",
running: "正在執行",
reset: "重置",
input_name: "請輸入測試名稱",
select_project: "請選擇項目",
variable_name: "變量名",
copied: "已拷貝",
key: "鍵",
value: "值",
scenario: {
config: "場景配置",
input_name: "請輸入場景名稱",
name: "場景名稱",
base_url: "基礎URL",
base_url_description: "基礎URL作為所有請求的URL前綴",
variables: "自定義變量",
headers: "請求頭",
kv_description: "所有請求可以使用自定義變量",
},
request: {
input_name: "請輸入請求名稱",
name: "請求名稱",
method: "請求方法",
url: "請求URL",
url_description: "例如: https://fit2cloud.com",
parameters: "請求參數",
parameters_desc: "參數追加到URL例如https://fit2cloud.com/entries?key1=Value1&Key2=Value2",
headers: "請求頭",
body: "請求內容",
body_kv: "鍵值對",
body_text: "文本",
assertions: {
label: "斷言",
text: "文本",
regex: "正則",
response_time: "響應時間",
select_type: "請選擇類型",
select_subject: "請選擇對象",
select_condition: "請選擇條件",
contains: "包含",
not_contains: "不包含",
equals: "等於",
start_with: "以...開始",
end_with: "以...結束",
value: "值",
expression: "Perl型正則表達式",
response_in_time: "響應時間在...毫秒以內",
},
extract: {
label: "提取",
select_type: "請選擇類型",
description: "從響應結果中提取數據並將其存儲在變量中,在後續請求中使用變量。",
regex: "正則",
regex_expression: "Perl型正則表達式",
json_path_expression: "JSONPath表達式",
xpath_expression: "XPath表達式",
}
}
},
api_report: {
request: "請求",
request_body: "請求內容",
request_headers: "請求頭",
request_cookie: "Cookie",
response: "響應",
delete_confirm: '確認刪除報告: ',
scenario_name: "場景名稱",
response_time: "響應時間(ms)",
latency: "網絡延遲",
request_size: "請求大小",
response_size: "響應大小",
response_code: "狀態碼",
response_message: "響應報文",
error: "錯誤",
assertions: "斷言",
assertions_pass: "成功斷言",
assertions_name: "斷言名稱",
assertions_message: "斷言信息",
assertions_is_success: "是否成功",
result: "結果",
success: "成功",
fail: "失敗",
},
test_track: {
test_track: "測試跟踪",
confirm: "確 定",
cancel: "取 消",
project: "項目",
save: "保 存",
return: "返 回",
length_less_than: "長度必須小於",
recent_plan: "最近的計劃",
recent_case: "最近的用例",
case: {
test_case: "測試用例",
move: "移動用例",
case_list: "用例列表",
create_case: "創建用例",
edit_case: "編輯用例",
view_case: "查看用例",
no_project: "該工作空間下無項目,請先創建項目",
priority: "優先級",
type: "類型",
method: "測試方式",
auto: "自動",
manual: "手動",
create: "新建用例",
case_type: "用例類型",
name: "用例名稱",
module: "所屬模塊",
maintainer: "維護人",
steps: "執行步驟",
number: "編號",
prerequisite: "前置條件",
step_desc: "步驟描述",
expected_results: "預期結果",
input_name: "請輸入名稱",
input_module: "請選擇模塊",
input_maintainer: "請選擇維護人",
input_priority: "請選擇優先級",
input_type: "請選擇用例類型",
input_method: "請選擇測試方式",
input_prerequisite: "請輸入前置條件",
delete_confirm: "確認刪除測試用例: ",
import: {
import: "導入用例",
case_import: "導入測試用例",
download_template: "下載模版",
click_upload: "點擊上傳",
upload_limit: "只能上傳xls/xlsx文件且不超過20M",
upload_limit_count: "一次只能上傳一個文件",
upload_limit_format: "上傳文件只能是 xls、xlsx格式!",
upload_limit_size: "上傳文件大小不能超過 20MB!",
success: "導入成功!",
},
export: {
export: "導出用例"
}
},
plan: {
test_plan: "測試計劃",
create_plan: "創建測試計劃",
edit_plan: "編輯測試計劃",
plan_name: "計劃名稱",
plan_project: "所屬項目",
plan_stage: "測試階段",
plan_status: "當前狀態",
smoke_test: "冒煙測試",
functional_test: "功能測試",
regression_test: "回歸測試",
integration_testing: "集成測試",
system_test: "系統測試",
version_validation: "版本驗證",
plan_principal: "負責人",
input_plan_name: "請輸入測試計劃名稱",
input_plan_principal: "請選擇負責人",
input_plan_project: "請選擇所屬項目",
input_plan_stage: "請選擇測試階段",
plan_status_prepare: "未開始",
plan_status_running: "進行中",
plan_status_completed: "已完成",
plan_delete_confirm: "確認刪除測試計劃: ",
},
module: {
search: "搜索模塊",
rename: "重命名",
add_submodule: "添加子模塊",
add_module: "添加模塊",
name: "模塊名稱",
delete_confirm: "確認刪除模塊: ",
delete_all_resource: "以及模塊下所有子模塊和測試用例",
},
plan_view: {
plan: "計劃",
relevance_test_case: "關聯測試用例",
executor: "執行人",
execute_result: "執行結果",
pass: "通過",
failure: "失敗",
blocking: "阻塞",
skip: "跳過",
actual_result: "實際結果",
step_result: "步驟執行結果",
my_case: "我的用例",
all_case: "全部用例",
pre_case: "上一條用例",
next_case: "下一條用例",
change_execution_results: "更改執行結果",
change_executor: "更改執行人",
select_executor: "請選擇執行人",
select_execute_result: "選擇執行結果",
cancel_relevance: "取消關聯",
confirm_cancel_relevance: "確認取消關聯",
select_manipulate: "請選擇需要操作的數據",
}
},
test_resource_pool: {
'type': '類型',
'enable_disable': '啟用/禁用',
'search_by_name': '根據名稱搜索',
'create_resource_pool': '創建資源池',
'update_resource_pool': '修改資源池',
'select_pool_type': '選擇資源類型',
'max_threads': '最大並發數',
'input_pool_name': '請輸入資源池名稱',
'pool_name_valid': '資源池名稱不支持特殊字符',
'cannot_remove_all_node': '不能刪除所有獨立節點',
'cannot_empty': '資源池不能為空',
'fill_the_data': '請完善數據',
'delete_prompt': '此操作將永久刪除該資源池, 是否繼續?',
'status_change_success': '狀態修改成功!',
'status_change_failed': '狀態修改失敗, 校驗不通過!',
},
i18n: {
'home': '首页',
'home': '首頁'
}
};

View File

@ -104,8 +104,13 @@
submit(form) {
this.$refs[form].validate((valid) => {
if (valid) {
this.$post("signin", this.form, (response) => {
this.$post("signin", this.form, response => {
saveLocalStorage(response);
let language = response.data.language;
if (!language) {
language = 'zh_CN';
}
this.$setLang(language);
window.location.href = "/"
});
} else {