Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
80a50999a7
|
@ -11,6 +11,7 @@ import io.metersphere.controller.request.UserRequest;
|
||||||
import io.metersphere.controller.request.member.AddMemberRequest;
|
import io.metersphere.controller.request.member.AddMemberRequest;
|
||||||
import io.metersphere.controller.request.member.EditPassWordRequest;
|
import io.metersphere.controller.request.member.EditPassWordRequest;
|
||||||
import io.metersphere.controller.request.member.QueryMemberRequest;
|
import io.metersphere.controller.request.member.QueryMemberRequest;
|
||||||
|
import io.metersphere.controller.request.member.SetAdminRequest;
|
||||||
import io.metersphere.controller.request.organization.AddOrgMemberRequest;
|
import io.metersphere.controller.request.organization.AddOrgMemberRequest;
|
||||||
import io.metersphere.controller.request.organization.QueryOrgMemberRequest;
|
import io.metersphere.controller.request.organization.QueryOrgMemberRequest;
|
||||||
import io.metersphere.dto.UserDTO;
|
import io.metersphere.dto.UserDTO;
|
||||||
|
@ -265,4 +266,10 @@ public class UserController {
|
||||||
return userService.updateUserPassword(request);
|
return userService.updateUserPassword(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/set/admin")
|
||||||
|
@RequiresRoles(RoleConstants.ADMIN)
|
||||||
|
public void setAdmin(@RequestBody SetAdminRequest request) {
|
||||||
|
userService.setAdmin(request);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package io.metersphere.controller.request.member;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SetAdminRequest {
|
||||||
|
private String id;
|
||||||
|
private String adminId;
|
||||||
|
private String password;
|
||||||
|
}
|
|
@ -4,12 +4,14 @@ import io.metersphere.base.domain.*;
|
||||||
import io.metersphere.base.mapper.*;
|
import io.metersphere.base.mapper.*;
|
||||||
import io.metersphere.base.mapper.ext.ExtUserMapper;
|
import io.metersphere.base.mapper.ext.ExtUserMapper;
|
||||||
import io.metersphere.base.mapper.ext.ExtUserRoleMapper;
|
import io.metersphere.base.mapper.ext.ExtUserRoleMapper;
|
||||||
|
import io.metersphere.commons.constants.RoleConstants;
|
||||||
import io.metersphere.commons.exception.MSException;
|
import io.metersphere.commons.exception.MSException;
|
||||||
import io.metersphere.commons.utils.CodingUtil;
|
import io.metersphere.commons.utils.CodingUtil;
|
||||||
import io.metersphere.controller.request.UserRequest;
|
import io.metersphere.controller.request.UserRequest;
|
||||||
import io.metersphere.controller.request.member.AddMemberRequest;
|
import io.metersphere.controller.request.member.AddMemberRequest;
|
||||||
import io.metersphere.controller.request.member.EditPassWordRequest;
|
import io.metersphere.controller.request.member.EditPassWordRequest;
|
||||||
import io.metersphere.controller.request.member.QueryMemberRequest;
|
import io.metersphere.controller.request.member.QueryMemberRequest;
|
||||||
|
import io.metersphere.controller.request.member.SetAdminRequest;
|
||||||
import io.metersphere.controller.request.organization.AddOrgMemberRequest;
|
import io.metersphere.controller.request.organization.AddOrgMemberRequest;
|
||||||
import io.metersphere.controller.request.organization.QueryOrgMemberRequest;
|
import io.metersphere.controller.request.organization.QueryOrgMemberRequest;
|
||||||
import io.metersphere.dto.UserDTO;
|
import io.metersphere.dto.UserDTO;
|
||||||
|
@ -133,8 +135,6 @@ public class UserService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateUser(User user) {
|
public void updateUser(User user) {
|
||||||
// MD5
|
|
||||||
user.setPassword(CodingUtil.md5(user.getPassword()));
|
|
||||||
user.setUpdateTime(System.currentTimeMillis());
|
user.setUpdateTime(System.currentTimeMillis());
|
||||||
userMapper.updateByPrimaryKeySelective(user);
|
userMapper.updateByPrimaryKeySelective(user);
|
||||||
}
|
}
|
||||||
|
@ -324,4 +324,20 @@ public class UserService {
|
||||||
return extUserMapper.updatePassword(user);
|
return extUserMapper.updatePassword(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAdmin(SetAdminRequest request) {
|
||||||
|
String adminId = request.getAdminId();
|
||||||
|
String password = request.getPassword();
|
||||||
|
if (!checkUserPassword(adminId, password)) {
|
||||||
|
MSException.throwException("verification failed!");
|
||||||
|
}
|
||||||
|
UserRole userRole = new UserRole();
|
||||||
|
userRole.setId(UUID.randomUUID().toString());
|
||||||
|
userRole.setUserId(request.getId());
|
||||||
|
// TODO 修改admin sourceId
|
||||||
|
userRole.setSourceId("adminSourceId");
|
||||||
|
userRole.setRoleId(RoleConstants.ADMIN);
|
||||||
|
userRole.setCreateTime(System.currentTimeMillis());
|
||||||
|
userRole.setUpdateTime(System.currentTimeMillis());
|
||||||
|
userRoleMapper.insertSelective(userRole);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,7 +147,7 @@
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleDelete(report) {
|
handleDelete(report) {
|
||||||
this.$alert(this.$t('load_test.delete_confirm') + report.name + "?", '', {
|
this.$alert(this.$t('report.delete_confirm') + report.name + "?", '', {
|
||||||
confirmButtonText: this.$t('commons.confirm'),
|
confirmButtonText: this.$t('commons.confirm'),
|
||||||
callback: (action) => {
|
callback: (action) => {
|
||||||
if (action === 'confirm') {
|
if (action === 'confirm') {
|
||||||
|
|
|
@ -163,8 +163,8 @@
|
||||||
this.editPasswordVisible = true;
|
this.editPasswordVisible = true;
|
||||||
},
|
},
|
||||||
updateUser(updateUserForm) {
|
updateUser(updateUserForm) {
|
||||||
this.$refs[updateUserForm].validate(valide => {
|
this.$refs[updateUserForm].validate(valid => {
|
||||||
if (valide) {
|
if (valid) {
|
||||||
this.result = this.$post(this.updatePath, this.form, response => {
|
this.result = this.$post(this.updatePath, this.form, response => {
|
||||||
this.$success(this.$t('commons.modify_success'));
|
this.$success(this.$t('commons.modify_success'));
|
||||||
localStorage.setItem(TokenKey, JSON.stringify(response.data));
|
localStorage.setItem(TokenKey, JSON.stringify(response.data));
|
||||||
|
@ -178,8 +178,8 @@
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
updatePassword(editPasswordForm) {
|
updatePassword(editPasswordForm) {
|
||||||
this.$refs[editPasswordForm].validate(valide => {
|
this.$refs[editPasswordForm].validate(valid => {
|
||||||
if (valide) {
|
if (valid) {
|
||||||
this.result = this.$post(this.updatePasswordPath, this.ruleForm, response => {
|
this.result = this.$post(this.updatePasswordPath, this.ruleForm, response => {
|
||||||
this.$success(this.$t('commons.modify_success'));
|
this.$success(this.$t('commons.modify_success'));
|
||||||
this.editPasswordVisible = false;
|
this.editPasswordVisible = false;
|
||||||
|
|
|
@ -290,8 +290,8 @@
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
updateWorkspace(updateForm) {
|
updateWorkspace(updateForm) {
|
||||||
this.$refs[updateForm].validate(valide => {
|
this.$refs[updateForm].validate(valid => {
|
||||||
if (valide) {
|
if (valid) {
|
||||||
this.result = this.$post("/workspace/special/update", this.form, () => {
|
this.result = this.$post("/workspace/special/update", this.form, () => {
|
||||||
this.$success(this.$t('commons.modify_success'));
|
this.$success(this.$t('commons.modify_success'));
|
||||||
this.dialogWsUpdateVisible = false;
|
this.dialogWsUpdateVisible = false;
|
||||||
|
|
|
@ -350,8 +350,8 @@
|
||||||
if (this.result.loading) {
|
if (this.result.loading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.$refs[createTestResourcePoolForm].validate(valide => {
|
this.$refs[createTestResourcePoolForm].validate(valid => {
|
||||||
if (valide) {
|
if (valid) {
|
||||||
let vri = this.validateResourceInfo();
|
let vri = this.validateResourceInfo();
|
||||||
if (vri.validate) {
|
if (vri.validate) {
|
||||||
this.convertSubmitResources();
|
this.convertSubmitResources();
|
||||||
|
@ -389,8 +389,8 @@
|
||||||
if (this.result.loading) {
|
if (this.result.loading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.$refs[updateTestResourcePoolForm].validate(valide => {
|
this.$refs[updateTestResourcePoolForm].validate(valid => {
|
||||||
if (valide) {
|
if (valid) {
|
||||||
let vri = this.validateResourceInfo();
|
let vri = this.validateResourceInfo();
|
||||||
if (vri.validate) {
|
if (vri.validate) {
|
||||||
this.convertSubmitResources();
|
this.convertSubmitResources();
|
||||||
|
|
|
@ -35,6 +35,8 @@
|
||||||
<template v-slot:behind>
|
<template v-slot:behind>
|
||||||
<ms-table-operator-button :tip="$t('member.edit_password')" icon="el-icon-s-tools"
|
<ms-table-operator-button :tip="$t('member.edit_password')" icon="el-icon-s-tools"
|
||||||
type="success" @exec="editPassword(scope.row)"/>
|
type="success" @exec="editPassword(scope.row)"/>
|
||||||
|
<ms-table-operator-button :tip="$t('commons.set_admin')" icon="el-icon-user-solid"
|
||||||
|
type="danger" @exec="openCheckDialog(scope.row)"/>
|
||||||
</template>
|
</template>
|
||||||
</ms-table-operator>
|
</ms-table-operator>
|
||||||
</template>
|
</template>
|
||||||
|
@ -45,6 +47,21 @@
|
||||||
:total="total"/>
|
:total="total"/>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
|
<el-dialog :title="$t('commons.verification')" :visible.sync="checkPasswordVisible" width="30%"
|
||||||
|
@close="closeCheckPassword" :destroy-on-close="true">
|
||||||
|
<el-form :model="checkPasswordForm" label-position="right" label-width="100px" size="small" :rules="rule"
|
||||||
|
ref="checkPasswordForm">
|
||||||
|
<el-form-item :label="$t('commons.password')" prop="password">
|
||||||
|
<el-input type="password" v-model="checkPasswordForm.password" autocomplete="off" show-password></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template v-slot:footer>
|
||||||
|
<ms-dialog-footer
|
||||||
|
@cancel="checkPasswordVisible = false"
|
||||||
|
@confirm="setAdmin('checkPasswordForm')"/>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<!--Create user-->
|
<!--Create user-->
|
||||||
<el-dialog :title="$t('user.create')" :visible.sync="createVisible" width="30%" @closed="handleClose"
|
<el-dialog :title="$t('user.create')" :visible.sync="createVisible" width="30%" @closed="handleClose"
|
||||||
:destroy-on-close="true">
|
:destroy-on-close="true">
|
||||||
|
@ -97,7 +114,8 @@
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<!--Changing user password in system settings-->
|
<!--Changing user password in system settings-->
|
||||||
<el-dialog :title="$t('member.edit_password')" :visible.sync="editPasswordVisible" width="30%" left>
|
<el-dialog :title="$t('member.edit_password')" :visible.sync="editPasswordVisible" width="30%" left>
|
||||||
<el-form :model="ruleForm" label-position="right" label-width="100px" size="small" :rules="rule" ref="editPasswordForm" class="demo-ruleForm">
|
<el-form :model="ruleForm" label-position="right" label-width="100px" size="small" :rules="rule"
|
||||||
|
ref="editPasswordForm" class="demo-ruleForm">
|
||||||
<el-form-item :label="$t('member.new_password')" prop="newpassword">
|
<el-form-item :label="$t('member.new_password')" prop="newpassword">
|
||||||
<el-input type="password" v-model="ruleForm.newpassword" autocomplete="off" show-password></el-input>
|
<el-input type="password" v-model="ruleForm.newpassword" autocomplete="off" show-password></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
@ -122,6 +140,7 @@
|
||||||
import MsTableOperator from "../../common/components/MsTableOperator";
|
import MsTableOperator from "../../common/components/MsTableOperator";
|
||||||
import MsDialogFooter from "../../common/components/MsDialogFooter";
|
import MsDialogFooter from "../../common/components/MsDialogFooter";
|
||||||
import MsTableOperatorButton from "../../common/components/MsTableOperatorButton";
|
import MsTableOperatorButton from "../../common/components/MsTableOperatorButton";
|
||||||
|
import {getCurrentUser} from "../../../../common/js/utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MsUser",
|
name: "MsUser",
|
||||||
|
@ -136,7 +155,8 @@
|
||||||
result: {},
|
result: {},
|
||||||
createVisible: false,
|
createVisible: false,
|
||||||
updateVisible: false,
|
updateVisible: false,
|
||||||
editPasswordVisible:false,
|
editPasswordVisible: false,
|
||||||
|
checkPasswordVisible: false,
|
||||||
multipleSelection: [],
|
multipleSelection: [],
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
pageSize: 5,
|
pageSize: 5,
|
||||||
|
@ -145,7 +165,9 @@
|
||||||
condition: {},
|
condition: {},
|
||||||
tableData: [],
|
tableData: [],
|
||||||
form: {},
|
form: {},
|
||||||
|
checkPasswordForm: {},
|
||||||
ruleForm: {},
|
ruleForm: {},
|
||||||
|
setAdminParam: {},
|
||||||
rule: {
|
rule: {
|
||||||
id: [
|
id: [
|
||||||
{required: true, message: this.$t('user.input_id'), trigger: 'blur'},
|
{required: true, message: this.$t('user.input_id'), trigger: 'blur'},
|
||||||
|
@ -181,20 +203,20 @@
|
||||||
password: [
|
password: [
|
||||||
{required: true, message: this.$t('user.input_password'), trigger: 'blur'},
|
{required: true, message: this.$t('user.input_password'), trigger: 'blur'},
|
||||||
{
|
{
|
||||||
required:true,
|
required: true,
|
||||||
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[^]{8,16}$/,
|
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[^]{8,16}$/,
|
||||||
message: this.$t('member.password_format_is_incorrect'),
|
message: this.$t('member.password_format_is_incorrect'),
|
||||||
trigger: 'blur'
|
trigger: 'blur'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
newpassword: [
|
newpassword: [
|
||||||
{required: true, message: this.$t('user.input_password'), trigger: 'blur'},
|
{required: true, message: this.$t('user.input_password'), trigger: 'blur'},
|
||||||
{
|
{
|
||||||
required:true,
|
required: true,
|
||||||
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[^]{8,16}$/,
|
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[^]{8,16}$/,
|
||||||
message: this.$t('member.password_format_is_incorrect'),
|
message: this.$t('member.password_format_is_incorrect'),
|
||||||
trigger: 'blur'
|
trigger: 'blur'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,8 +232,8 @@
|
||||||
this.updateVisible = true;
|
this.updateVisible = true;
|
||||||
this.form = Object.assign({}, row);
|
this.form = Object.assign({}, row);
|
||||||
},
|
},
|
||||||
editPassword(row){
|
editPassword(row) {
|
||||||
this.editPasswordVisible=true;
|
this.editPasswordVisible = true;
|
||||||
this.ruleForm = Object.assign({}, row);
|
this.ruleForm = Object.assign({}, row);
|
||||||
},
|
},
|
||||||
del(row) {
|
del(row) {
|
||||||
|
@ -255,15 +277,15 @@
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
editUserPassword(editPasswordForm){
|
editUserPassword(editPasswordForm){
|
||||||
this.$refs[editPasswordForm].validate(valide=>{
|
this.$refs[editPasswordForm].validate(valid=>{
|
||||||
if(valide){
|
if(valid){
|
||||||
this.result = this.$post(this.editPasswordPath, this.ruleForm, response => {
|
this.result = this.$post(this.editPasswordPath, this.ruleForm, response => {
|
||||||
this.$success(this.$t('commons.modify_success'));
|
this.$success(this.$t('commons.modify_success'));
|
||||||
this.editPasswordVisible = false;
|
this.editPasswordVisible = false;
|
||||||
this.search() ;
|
this.search();
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
}else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -288,6 +310,28 @@
|
||||||
},
|
},
|
||||||
handleSelectionChange(val) {
|
handleSelectionChange(val) {
|
||||||
this.multipleSelection = val;
|
this.multipleSelection = val;
|
||||||
|
},
|
||||||
|
closeCheckPassword() {
|
||||||
|
this.checkPasswordForm = {};
|
||||||
|
},
|
||||||
|
openCheckDialog(row) {
|
||||||
|
this.$set(this.setAdminParam, 'id', row.id);
|
||||||
|
this.checkPasswordVisible = true;
|
||||||
|
},
|
||||||
|
setAdmin(checkPasswordForm) {
|
||||||
|
let user = getCurrentUser();
|
||||||
|
this.$set(this.setAdminParam, 'adminId', user.id);
|
||||||
|
this.$set(this.setAdminParam, 'password', this.checkPasswordForm.password);
|
||||||
|
this.$refs[checkPasswordForm].validate(valid => {
|
||||||
|
if (valid) {
|
||||||
|
this.$post("/user/set/admin", this.setAdminParam, () => {
|
||||||
|
this.$success(this.$t('commons.modify_success'));
|
||||||
|
this.checkPasswordVisible = false;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ export default {
|
||||||
'project': 'Project',
|
'project': 'Project',
|
||||||
'name': 'Name',
|
'name': 'Name',
|
||||||
'description': 'Description',
|
'description': 'Description',
|
||||||
|
'clear': 'Clear',
|
||||||
'save': 'Save',
|
'save': 'Save',
|
||||||
'save_success': 'Saved successfully',
|
'save_success': 'Saved successfully',
|
||||||
'delete_success': 'Deleted successfully',
|
'delete_success': 'Deleted successfully',
|
||||||
|
@ -50,9 +51,12 @@ export default {
|
||||||
'remark': 'Remark',
|
'remark': 'Remark',
|
||||||
'delete': 'Delete',
|
'delete': 'Delete',
|
||||||
'not_filled': 'Not filled',
|
'not_filled': 'Not filled',
|
||||||
|
'please_select': 'Please select',
|
||||||
'search_by_name': 'Search by name',
|
'search_by_name': 'Search by name',
|
||||||
'personal_information': 'Personal Information',
|
'personal_information': 'Personal Information',
|
||||||
'exit_system': 'Exit System',
|
'exit_system': 'Exit System',
|
||||||
|
'verification': 'Verification',
|
||||||
|
'set_admin': 'Set Admin',
|
||||||
},
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
'create': 'Create Workspace',
|
'create': 'Create Workspace',
|
||||||
|
@ -139,6 +143,7 @@ export default {
|
||||||
'compare': 'Compare',
|
'compare': 'Compare',
|
||||||
'generation_error': 'Report generation error, cannot be viewed!',
|
'generation_error': 'Report generation error, cannot be viewed!',
|
||||||
'being_generated': 'Report is being generated...',
|
'being_generated': 'Report is being generated...',
|
||||||
|
'delete_confirm': 'Confirm delete: ',
|
||||||
},
|
},
|
||||||
load_test: {
|
load_test: {
|
||||||
'operating': 'Operating',
|
'operating': 'Operating',
|
||||||
|
@ -321,7 +326,6 @@ export default {
|
||||||
input_method: "Please select method",
|
input_method: "Please select method",
|
||||||
input_prerequisite: "Please select prerequisite",
|
input_prerequisite: "Please select prerequisite",
|
||||||
delete_confirm: "Confirm delete test case: ",
|
delete_confirm: "Confirm delete test case: ",
|
||||||
|
|
||||||
import: {
|
import: {
|
||||||
import: "Import test case",
|
import: "Import test case",
|
||||||
case_import: "Import test case",
|
case_import: "Import test case",
|
||||||
|
@ -333,7 +337,6 @@ export default {
|
||||||
upload_limit_size: "Upload file size cannot exceed 20MB!",
|
upload_limit_size: "Upload file size cannot exceed 20MB!",
|
||||||
success: "Import success!",
|
success: "Import success!",
|
||||||
},
|
},
|
||||||
|
|
||||||
export: {
|
export: {
|
||||||
export: "Export cases"
|
export: "Export cases"
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,8 @@ export default {
|
||||||
'search_by_name': '根据名称搜索',
|
'search_by_name': '根据名称搜索',
|
||||||
'personal_information': '个人信息',
|
'personal_information': '个人信息',
|
||||||
'exit_system': '退出系统',
|
'exit_system': '退出系统',
|
||||||
|
'verification': '验证',
|
||||||
|
'set_admin': '设置为管理员',
|
||||||
},
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
'create': '创建工作空间',
|
'create': '创建工作空间',
|
||||||
|
@ -141,6 +143,7 @@ export default {
|
||||||
'compare': '比较',
|
'compare': '比较',
|
||||||
'generation_error': '报告生成错误,无法查看!',
|
'generation_error': '报告生成错误,无法查看!',
|
||||||
'being_generated': '报告正在生成中...',
|
'being_generated': '报告正在生成中...',
|
||||||
|
'delete_confirm': '确认删除报告: ',
|
||||||
},
|
},
|
||||||
load_test: {
|
load_test: {
|
||||||
'operating': '操作',
|
'operating': '操作',
|
||||||
|
@ -198,7 +201,6 @@ export default {
|
||||||
'resource_pool_is_null': '资源池为空',
|
'resource_pool_is_null': '资源池为空',
|
||||||
'download_log_file': '下载完整日志文件',
|
'download_log_file': '下载完整日志文件',
|
||||||
'pressure_prediction_chart': '压力预估图',
|
'pressure_prediction_chart': '压力预估图',
|
||||||
|
|
||||||
},
|
},
|
||||||
api_test: {
|
api_test: {
|
||||||
save_and_run: "保存并执行",
|
save_and_run: "保存并执行",
|
||||||
|
|
Loading…
Reference in New Issue