feat: 1.15个人信息优化

This commit is contained in:
guoyuqi 2021-11-03 18:54:21 +08:00 committed by 刘瑞斌
parent 265de173b8
commit c726b5623f
13 changed files with 510 additions and 12 deletions

View File

@ -33,4 +33,6 @@ public interface ExtProjectMapper {
Project selectProjectByResourceId(@Param("resourceId") String resourceId);
long getProjectMemberSize(@Param("projectId") String projectId);
List<Project>getProjectByUserId(@Param("userId")String userId);
}

View File

@ -240,5 +240,7 @@
SELECT count(distinct (`user`.id)) FROM user_group JOIN `user` ON user_group.user_id = `user`.id
WHERE user_group.source_id = #{projectId}
</select>
<select id="getProjectByUserId" resultType="io.metersphere.base.domain.Project">
SELECT * from project where project.id in (SELECT user_group.source_id from user_group where user_group.user_id = #{userId})
</select>
</mapper>

View File

@ -1,5 +1,6 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.base.domain.Workspace;
import io.metersphere.controller.request.WorkspaceRequest;
import io.metersphere.dto.WorkspaceDTO;
import org.apache.ibatis.annotations.Param;
@ -11,4 +12,6 @@ public interface ExtWorkspaceMapper {
List<WorkspaceDTO> getWorkspaces(@Param("request") WorkspaceRequest request);
void setDefaultMessageTask(@Param("workspaceId") String workspaceId);
List<Workspace> getWorkspaceByUserId(@Param("userId")String userId);
}

View File

@ -124,4 +124,8 @@
'4a890e41-e755-44fc-b734-d6a0ca25a65c', 0, #{workspaceId}, NULL, 1629790487682,
NULL)
</insert>
<select id="getWorkspaceByUserId" resultType="io.metersphere.base.domain.Workspace">
SELECT * from workspace where workspace.id in (SELECT user_group.source_id from user_group where user_group.user_id = #{userId})
</select>
</mapper>

View File

@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping(value = "/system")

View File

@ -25,6 +25,7 @@ import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
@RequestMapping("user")
@RestController
@ -255,4 +256,12 @@ public class UserController {
userService.batchProcessUserInfo(request);
return returnString;
}
/**
* 根据userId 获取 user 所属工作空间和所属工作项目
*/
@GetMapping("/get/ws_pj/{userId}")
public Map<Object,Object> getWSAndProjectByUserId(@PathVariable String userId) {
return userService.getWSAndProjectByUserId(userId);
}
}

View File

@ -8,6 +8,7 @@ import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtProjectMapper;
import io.metersphere.base.mapper.ext.ExtUserGroupMapper;
import io.metersphere.base.mapper.ext.ExtUserMapper;
import io.metersphere.base.mapper.ext.ExtWorkspaceMapper;
import io.metersphere.commons.constants.*;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.SessionUser;
@ -90,6 +91,8 @@ public class UserService {
private ProjectMapper projectMapper;
@Resource
private ExtProjectMapper extProjectMapper;
@Resource
private ExtWorkspaceMapper extWorkspaceMapper;
public List<UserDetail> queryTypeByIds(List<String> userIds) {
return extUserMapper.queryTypeByIds(userIds);
@ -1203,4 +1206,18 @@ public class UserService {
public long getUserSize() {
return userMapper.countByExample(new UserExample());
}
/**
* 根据userId 获取 user 所属工作空间和所属工作项目
* @param userId
*/
public Map<Object,Object> getWSAndProjectByUserId(String userId){
Map<Object,Object>map = new HashMap<>(2);
List<Project> projects = extProjectMapper.getProjectByUserId(userId);
List<Workspace> workspaces = extWorkspaceMapper.getWorkspaceByUserId(userId);
map.put("project",projects);
map.put("workspace",workspaces);
return map;
}
}

View File

@ -14,6 +14,10 @@
</template>
<about-us ref="aboutUs"/>
<el-dialog :close-on-click-modal="false" width="80%"
:visible.sync="resVisible" class="api-import" destroy-on-close @close="closeDialog">
<ms-person-router @closeDialog = "closeDialog"/>
</el-dialog>
</el-dropdown>
</template>
@ -22,14 +26,17 @@ import {getCurrentUser} from "@/common/js/utils";
import AboutUs from "./AboutUs";
import {logout} from "@/network/user";
import MsPersonRouter from "@/business/components/settings/components/PersonRouter"
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const auth = requireComponent.keys().length > 0 ? requireComponent("./auth/Auth.vue") : {};
export default {
name: "MsUser",
components: {AboutUs},
components: {AboutUs,MsPersonRouter},
data() {
return {
resVisible:false,
}
},
computed: {
@ -45,7 +52,8 @@ export default {
switch (command) {
case "personal":
// TODO
this.$router.push('/setting/personsetting').catch(error => error);
// this.$router.push('/setting/personsetting').catch(error => error);
this.resVisible = true;
break;
case "logout":
this.logout();
@ -70,8 +78,11 @@ export default {
} else {
window.location.href = "/#/api/home";
}
},
closeDialog(){
this.resVisible = false;
}
}
}
</script>

View File

@ -30,7 +30,7 @@
</el-submenu>
</el-submenu>
<el-submenu index="5">
<!-- <el-submenu index="5">
<template v-slot:title>
<font-awesome-icon class="icon" :icon="['far', 'user']" size="lg"/>
<span>{{ $t('commons.personal_info') }}</span>
@ -38,7 +38,7 @@
<el-menu-item v-for="menu in persons" :key="menu.index" :index="menu.index" class="setting-item">
{{ $t(menu.title) }}
</el-menu-item>
</el-submenu>
</el-submenu>-->
</el-menu>
@ -72,7 +72,7 @@ export default {
systems: getMenus('system'),
organizations: getMenus('organization'),
workspaces: getMenus('workspace'),
persons: getMenus('person'),
/* persons: getMenus('person'),*/
project: getMenus('project'),
workspaceTemplate: getMenus('workspaceTemplate'),
systemPermission: [

View File

@ -0,0 +1,139 @@
<template>
<div>
<el-tabs v-model="activeIndex" >
<el-tab-pane v-for="menu in persons" :key = "menu.title" :name="menu.title" :label="$t(menu.title)" class="setting-item"></el-tab-pane>
<el-tab-pane name="change_password" label="修改密码" class="setting-item"></el-tab-pane>
<el-tab-pane name="third_account" label="第三方平台账号" class="setting-item"></el-tab-pane>
</el-tabs>
<ms-main-container>
<ms-person-from-setting v-if="activeIndex==='commons.personal_setting'" @getPlatformInfo = "getPlatformInfo" @cancel = "cancel"/>
<ms-api-keys v-if="activeIndex==='commons.api_keys'"/>
<password-info v-if="activeIndex==='change_password'" :rule-form = "ruleForm"></password-info>
<el-form v-if="activeIndex==='third_account'">
<jira-user-info @auth="handleAuth" v-if="hasJira" :data="currentPlatformInfo"/>
<tapd-user-info @auth="handleAuth" v-if="hasTapd" :data="currentPlatformInfo"/>
<zentao-user-info @auth="handleAuth" v-if="hasZentao" :data="currentPlatformInfo"/>
<azure-devops-user-info @auth="handleAuth" v-if="hasAzure" :data="currentPlatformInfo"/>
<el-form-item>
<el-button @click="cancel">{{$t('commons.cancel')}}</el-button>
<el-button type="primary" @click="updateUser('updateUserForm')" @keydown.enter.native.prevent>{{$t('commons.confirm')}}</el-button>
</el-form-item>
</el-form>
</ms-main-container>
</div>
</template>
<script>
import Setting from "@/business/components/settings/router";
import MsPersonFromSetting from "@/business/components/settings/personal/PersonFromSetting";
import MsApiKeys from "@/business/components/settings/personal/ApiKeys";
import MsMainContainer from "@/business/components/common/components/MsMainContainer";
import PasswordInfo from "@/business/components/settings/personal/PasswordInfo";
import {getCurrentWorkspaceId} from "@/common/js/utils";
import ZentaoUserInfo from "@/business/components/settings/personal/ZentaoUserInfo";
import TapdUserInfo from "@/business/components/settings/personal/TapdUserInfo";
import JiraUserInfo from "@/business/components/settings/personal/JiraUserInfo";
import AzureDevopsUserInfo from "@/business/components/settings/personal/AzureDevopsUserInfo";
import {getIntegrationService} from "@/network/organization";
import {TokenKey} from "@/common/js/constants";
export default {
name: "MsPersonRouter",
components: {MsMainContainer,MsPersonFromSetting,MsApiKeys,PasswordInfo,ZentaoUserInfo, TapdUserInfo, JiraUserInfo, AzureDevopsUserInfo},
data(){
let getMenus = function (group) {
let menus = [];
Setting.children.forEach(child => {
if (child.meta[group] === true) {
let menu = {index: Setting.path + "/" + child.path};
menu.title = child.meta.title;
menu.roles = child.meta.roles;
menu.permissions = child.meta.permissions;
menu.valid = child.meta.valid;
menus.push(menu);
}
});
return menus;
};
return{
persons: getMenus('person'),
activeIndex: 'commons.personal_setting',
ruleForm:{},
hasJira: false,
hasTapd: false,
hasZentao: false,
hasAzure: false,
updatePath: '/user/update/current',
form: {platformInfo: {}},
currentPlatformInfo: {
jiraAccount: '',
jiraPassword: '',
tapdUserName: '',
zentaoUserName: '',
zentaoPassword: '',
azureDevopsPat: ''
},
result:{}
}
},
methods:{
handleAuth(type) {
let param = {...this.currentPlatformInfo};
param.workspaceId = getCurrentWorkspaceId();
param.platform = type;
this.$parent.result = this.$post("issues/user/auth", param, () => {
this.$success(this.$t('organization.integration.verified'));
});
},
getPlatformInfo(row) {
if (row.platformInfo) {
this.form = row;
this.form.platformInfo = JSON.parse(row.platformInfo);
} else {
this.form.platformInfo = {};
}
let orgId = getCurrentWorkspaceId();
if (!this.form.platformInfo[orgId]) {
this.form.platformInfo[orgId] = {};
}
this.currentPlatformInfo = this.form.platformInfo[orgId];
this.result = getIntegrationService((data) => {
let platforms = data.map(d => d.platform);
if (platforms.indexOf("Tapd") !== -1) {
this.hasTapd = true;
}
if (platforms.indexOf("Jira") !== -1) {
this.hasJira = true;
}
if (platforms.indexOf("Zentao") !== -1) {
this.hasZentao = true;
}
if (platforms.indexOf("AzureDevops") !== -1) {
this.hasAzure = true;
}
});
},
cancel() {
this.$emit('closeDialog', false);
},
updateUser(updateUserForm) {
let param = {};
Object.assign(param, this.form);
param.platformInfo = JSON.stringify(this.form.platformInfo);
this.result = this.$post(this.updatePath, param, response => {
this.$success(this.$t('commons.modify_success'));
localStorage.setItem(TokenKey, JSON.stringify(response.data));
this.$emit('closeDialog', false);
this.reload();
});
},
}
}
</script>
<style scoped>
</style>

View File

@ -31,7 +31,14 @@
</el-table-column>
<el-table-column prop="secretKey" label="Secret Key">
<template v-slot:default="scope">
<el-link type="primary" @click="showSecretKey(scope.row)">{{ $t('commons.show') }}</el-link>
<el-link type="primary" @click="showSecretKey(scope.row)" v-if="!apiKeysVisible">{{ $t('commons.show') }}</el-link>
<div v-if="apiKeysVisible" class="variable-combine">
<div class="variable">{{scope.row.secretKey}}</div>
<el-tooltip :content="$t('api_test.copied')" manual v-model="scope.row.visible2" placement="top"
:visible-arrow="false">
<i class="el-icon-copy-document copy" @click="copy(scope.row, 'secretKey', 'visible2')"/>
</el-tooltip>
</div>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('commons.status')">
@ -59,7 +66,7 @@
</el-table-column>
</el-table>
</el-card>
<el-dialog title="Secret Key" :visible.sync="apiKeysVisible">
<!-- <el-dialog title="Secret Key" :visible.sync="apiKeysVisible">
<div class="variable">
{{ currentRow.secretKey }}
<el-tooltip :content="$t('api_test.copied')" manual v-model="currentRow.visible2" placement="top"
@ -67,7 +74,7 @@
<i class="el-icon-copy-document copy" @click="copy(currentRow, 'secretKey', 'visible2')"/>
</el-tooltip>
</div>
</el-dialog>
</el-dialog>-->
</div>
</template>
@ -92,7 +99,7 @@ export default {
}
},
activated() {
created() {
this.search();
},
@ -144,7 +151,9 @@ export default {
},
showSecretKey(row) {
this.apiKeysVisible = true;
this.currentRow = row;
setTimeout(() => {
this.apiKeysVisible = false;
}, 5000);
},
copy(row, key, visible) {
let input = document.createElement("input");

View File

@ -0,0 +1,84 @@
<template>
<el-form :model="ruleForm" :rules="rules" ref="editPasswordForm" label-width="100px" class="demo-ruleForm">
<el-form-item :label="$t('member.old_password')" prop="password" style="margin-bottom: 29px">
<el-input v-model="ruleForm.password" autocomplete="off" show-password/>
</el-form-item>
<el-form-item :label="$t('member.new_password')" prop="newpassword">
<el-input v-model="ruleForm.newpassword" autocomplete="off" show-password/>
</el-form-item>
<el-form-item :label="$t('member.repeat_password')" prop="repeatPassword">
<el-input v-model="ruleForm.repeatPassword" autocomplete="off" show-password/>
</el-form-item>
<el-form-item>
<el-button @click="cancel">{{$t('commons.cancel')}}</el-button>
<el-button type="primary" @click="updatePassword('editPasswordForm')" @keydown.enter.native.prevent>{{$t('commons.confirm')}}</el-button>
</el-form-item>
</el-form>
</template>
<script>
import {logout} from "@/network/user";
export default {
name:'PasswordInfo',
data(){
return{
result:{},
updatePasswordPath: '/user/update/password',
rules: {
password: [
{required: true, message: this.$t('user.input_password'), trigger: 'blur'},
],
newpassword: [
{required: true, message: this.$t('user.input_password'), trigger: 'blur'},
{
required: true,
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[^]{8,30}$/,
message: this.$t('member.password_format_is_incorrect'),
trigger: 'blur'
},
],
repeatPassword: [
{required: true, message: this.$t('user.input_password'), trigger: 'blur'},
{
required: true,
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[^]{8,30}$/,
message: this.$t('member.password_format_is_incorrect'),
trigger: 'blur'
},
]
}
}
},
props:{
ruleForm:{}
},
methods:{
cancel() {
return
},
confirm() {
this.$emit("confirm");
},
updatePassword(editPasswordForm) {
this.$refs[editPasswordForm].validate(valid => {
if (valid) {
if (this.ruleForm.newpassword !== this.ruleForm.repeatPassword) {
this.$warning(this.$t('member.inconsistent_passwords'));
return;
}
this.result = this.$post(this.updatePasswordPath, this.ruleForm, response => {
this.$success(this.$t('commons.modify_success'));
logout();
});
} else {
return false;
}
});
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,217 @@
<template>
<div v-loading="result.loading">
<el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule"
ref="updateUserForm">
<el-form-item label="ID" prop="id">
<el-input v-model="form.id" autocomplete="off" :disabled="true"/>
</el-form-item>
<el-form-item :label="$t('commons.username')" prop="name">
<el-input v-model="form.name" autocomplete="off"/>
</el-form-item>
<el-form-item :label="$t('commons.email')" prop="email">
<el-input v-model="form.email" autocomplete="off" :disabled="!isLocalUser"/>
</el-form-item>
<el-form-item :label="$t('commons.phone')" prop="phone">
<el-input v-model="form.phone" autocomplete="off"/>
</el-form-item>
<el-form-item label="所属工作空间" v-if="workspaceList.length>0">
<span v-for="(item,index) in workspaceList" :key = item.id >
<span>{{item.name}}</span><span v-if="index<workspaceList.length-1"> | </span>
</span>
</el-form-item>
<el-form-item label="所属工作项目" v-if ="projectList.length>0">
<span v-for="(item,index) in projectList" :key = item.id >
<span>{{item.name}}</span><span v-if="index<projectList.length-1"> | </span>
</span>
</el-form-item>
<el-form-item>
<el-button @click="cancel">{{$t('commons.cancel')}}</el-button>
<el-button type="primary" @click="updateUser('updateUserForm')" @keydown.enter.native.prevent>{{$t('commons.confirm')}}</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import {PROJECT_ID, TokenKey, WORKSPACE_ID} from "@/common/js/constants";
import MsDialogFooter from "../../common/components/MsDialogFooter";
import {
fullScreenLoading, getCurrentProjectID,
getCurrentUser,
getCurrentWorkspaceId,
listenGoBack,
removeGoBackListener, saveLocalStorage, stopFullScreenLoading
} from "@/common/js/utils";
import MsTableOperatorButton from "../../common/components/MsTableOperatorButton";
import {EMAIL_REGEX, PHONE_REGEX} from "@/common/js/regex";
import JiraUserInfo from "@/business/components/settings/personal/JiraUserInfo";
import TapdUserInfo from "@/business/components/settings/personal/TapdUserInfo";
import {getIntegrationService} from "@/network/organization";
import ZentaoUserInfo from "@/business/components/settings/personal/ZentaoUserInfo";
import AzureDevopsUserInfo from "@/business/components/settings/personal/AzureDevopsUserInfo";
import {logout} from "@/network/user";
export default {
name: "MsPersonFromSetting",
components: {ZentaoUserInfo, TapdUserInfo, JiraUserInfo, AzureDevopsUserInfo, MsDialogFooter, MsTableOperatorButton},
inject: [
'reload',
'reloadTopMenus'
],
data() {
return {
result: {},
isLocalUser: false,
updatePath: '/user/update/current',
form: {platformInfo: {}},
ruleForm: {},
rule: {
name: [
{required: true, message: this.$t('member.input_name'), trigger: 'blur'},
{min: 2, max: 20, message: this.$t('commons.input_limit', [2, 20]), trigger: 'blur'},
{
required: true,
pattern: /^[\u4e00-\u9fa5_a-zA-Z0-9.·-]+$/,
message: this.$t('member.special_characters_are_not_supported'),
trigger: 'blur'
}
],
phone: [
{
pattern: PHONE_REGEX,
message: this.$t('member.mobile_number_format_is_incorrect'),
trigger: 'blur'
}
],
email: [
{required: true, message: this.$t('member.input_email'), trigger: 'blur'},
{
required: true,
pattern: EMAIL_REGEX,
message: this.$t('member.email_format_is_incorrect'),
trigger: 'blur'
}
],
},
workspaceList:[],
projectList:[]
};
},
created() {
this.initTableData();
},
methods: {
currentUser: () => {
return getCurrentUser();
},
cancel() {
},
updateUser(updateUserForm) {
this.$refs[updateUserForm].validate(valid => {
if (valid) {
let param = {};
this.$emit('getPlatformInfo', this.form);
Object.assign(param, this.form);
param.platformInfo = JSON.stringify(this.form.platformInfo);
this.result = this.$post(this.updatePath, param, response => {
this.$success(this.$t('commons.modify_success'));
localStorage.setItem(TokenKey, JSON.stringify(response.data));
this.initTableData();
this.reload();
});
} else {
return false;
}
});
},
initTableData() {
this.result = this.$get("/user/info/" + encodeURIComponent(this.currentUser().id), response => {
let data = response.data;
this.isLocalUser = response.data.source === 'LOCAL';
let dataList = [];
dataList[0] = data;
this.form = data;
this.$emit('getPlatformInfo', data);
this.getWsAndPj();
});
},
getWsAndPj(){
this.$get("/user/get/ws_pj/" + encodeURIComponent(this.currentUser().id), response => {
let data = response.data;
this.workspaceList = data.workspace;
this.projectList = data.project
console.log("查看当前用户所属项目和空间")
console.log(response)
});
},
handleClose() {
this.form = {};
this.ruleForm = {};
removeGoBackListener(this.handleClose);
},
getRedirectUrl(user) {
if (!user.lastProjectId || !user.lastWorkspaceId) {
// /
// /
return "/";
}
let redirectUrl = sessionStorage.getItem('redirectUrl');
if (redirectUrl.startsWith("/")) {
redirectUrl = redirectUrl.substring(1);
}
redirectUrl = redirectUrl.split("/")[0];
return '/' + redirectUrl + '/';
},
changeWorkspace(workspace){
let workspaceId = workspace.id;
if (!workspaceId || getCurrentWorkspaceId() === workspaceId) {
return false;
}
this.$emit('cancel', false);
const loading = fullScreenLoading(this);
this.$post("/user/switch/source/ws/" + workspaceId, {}, response => {
saveLocalStorage(response);
sessionStorage.setItem(WORKSPACE_ID, workspaceId);
sessionStorage.setItem(PROJECT_ID, response.data.lastProjectId);
this.$router.push(this.getRedirectUrl(response.data)).then(() => {
this.reloadTopMenus(stopFullScreenLoading(loading));
this.reload();
}).catch(err => err);
});
},
changeProject(project){
let projectId = project.id;
let currentProjectId = getCurrentProjectID();
if (projectId === currentProjectId) {
return;
}
const loading = fullScreenLoading(this);
this.$post("/user/update/current", {id: this.currentUser().id, lastProjectId: projectId }, (response) => {
saveLocalStorage(response);
this.currentProjectId = projectId;
this.$EventBus.$emit('projectChange');
// sessionprojectId
sessionStorage.setItem(PROJECT_ID, projectId);
//
this.reload();
stopFullScreenLoading(loading, 1500);
// this.changeProjectName(projectId);
}, () => {
stopFullScreenLoading(loading, 1500);
});
}
}
};
</script>
<style scoped>
.ws-pj-class{
cursor: pointer;
}
</style>