From c6fbd0327644559baf534467ae1cc8a853212e7d Mon Sep 17 00:00:00 2001 From: jiang <893224616@qq.com> Date: 星期一, 18 七月 2022 17:09:35 +0800 Subject: [PATCH] 增加用户管理功能。管理员可以添加删除用户、修改用户密码、重置pushkey --- web_src/src/components/Login.vue | 2 web_src/src/components/dialog/changePasswordForAdmin.vue | 121 +++++++++++ web_src/src/components/UserManager.vue | 236 +++++++++++++++++++++ web_src/src/components/dialog/addUser.vue | 159 ++++++++++++++ src/main/java/com/genersoft/iot/vmp/storager/dao/UserMapper.java | 7 web_src/src/router/index.js | 6 web_src/src/layout/UiHeader.vue | 3 src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java | 16 + src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java | 64 +++++ src/main/java/com/genersoft/iot/vmp/service/IUserService.java | 5 10 files changed, 617 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/genersoft/iot/vmp/service/IUserService.java b/src/main/java/com/genersoft/iot/vmp/service/IUserService.java index e362605..616fd1a 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/IUserService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IUserService.java @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.service; import com.genersoft.iot.vmp.storager.dao.dto.User; +import com.github.pagehelper.PageInfo; import java.util.List; @@ -21,4 +22,8 @@ int updateUsers(User user); boolean checkPushAuthority(String callId, String sign); + + PageInfo<User> getUsers(int page, int count); + + int resetPushKey(int id); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java index 01d91a5..46d9ad5 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java @@ -3,6 +3,8 @@ import com.genersoft.iot.vmp.service.IUserService; import com.genersoft.iot.vmp.storager.dao.UserMapper; import com.genersoft.iot.vmp.storager.dao.dto.User; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -11,7 +13,7 @@ @Service public class UserServiceImpl implements IUserService { - + @Autowired private UserMapper userMapper; @@ -64,4 +66,16 @@ return userMapper.checkPushAuthorityByCallIdAndSign(callId, sign).size() > 0; } } + + @Override + public PageInfo<User> getUsers(int page, int count) { + PageHelper.startPage(page, count); + List<User> users = userMapper.getUsers(); + return new PageInfo<>(users); + } + + @Override + public int resetPushKey(int id) { + return userMapper.resetPushKey(id); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/UserMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/UserMapper.java index 5ed0a57..850e4d4 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/UserMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/UserMapper.java @@ -55,4 +55,11 @@ @Select("select * from user where md5(pushKey) = '${sign}'") List<User> checkPushAuthorityByCallId(String sign); + + @Select("select u.idu.username,u.pushKey,u.roleId, r.id as roleID, r.name as roleName, r.authority as roleAuthority , r.createTime as roleCreateTime , r.updateTime as roleUpdateTime FROM user u join user_role r on u.roleId=r.id") + @ResultMap(value="roleMap") + List<User> getUsers(); + + @Delete("update user set pushKey=MD5(NOW()+#{id}) where id=#{id}") + int resetPushKey(int id); } diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java index 152122d..ca6fc84 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java @@ -8,6 +8,7 @@ import com.genersoft.iot.vmp.storager.dao.dto.User; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; +import com.github.pagehelper.PageInfo; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; @@ -177,4 +178,67 @@ result.setData(allUsers); return new ResponseEntity<>(result, HttpStatus.OK); } + + /** + * 鍒嗛〉鏌ヨ鐢ㄦ埛 + * + * @param page 褰撳墠椤� + * @param count 姣忛〉鏌ヨ鏁伴噺 + * @return 鍒嗛〉鐢ㄦ埛鍒楄〃 + */ + @ApiOperation("鍒嗛〉鏌ヨ鐢ㄦ埛") + @ApiImplicitParams({ + @ApiImplicitParam(name = "page", value = "褰撳墠椤�", required = true, dataTypeClass = Integer.class), + @ApiImplicitParam(name = "count", value = "姣忛〉鏌ヨ鏁伴噺", required = true, dataTypeClass = Integer.class), + }) + @GetMapping("/users") + public PageInfo<User> users(int page, int count) { + return userService.getUsers(page, count); + } + + @ApiOperation("閲嶇疆pushkey") + @ApiImplicitParams({ + @ApiImplicitParam(name = "id", required = true, value = "鐢ㄦ埛Id", dataTypeClass = Integer.class), + }) + @RequestMapping("/resetPushKey") + public ResponseEntity<WVPResult<String>> resetPushKey(@RequestParam Integer id) { + // 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛id + int currenRoleId = SecurityUtils.getUserInfo().getRole().getId(); + WVPResult<String> result = new WVPResult<>(); + if (currenRoleId != 1) { + // 鍙敤瑙掕壊id涓�0鎵嶅彲浠ュ垹闄ゅ拰娣诲姞鐢ㄦ埛 + result.setCode(-1); + result.setMsg("鐢ㄦ埛鏃犳潈闄�"); + return new ResponseEntity<>(result, HttpStatus.FORBIDDEN); + } + int resetPushKeyResult = userService.resetPushKey(id); + + result.setCode(resetPushKeyResult > 0 ? 0 : -1); + result.setMsg(resetPushKeyResult > 0 ? "success" : "fail"); + return new ResponseEntity<>(result, HttpStatus.OK); + } + + @ApiOperation("绠$悊鍛樹慨鏀规櫘閫氱敤鎴峰瘑鐮�") + @ApiImplicitParams({ + @ApiImplicitParam(name = "adminId", required = true, value = "绠$悊鍛榠d", dataTypeClass = String.class), + @ApiImplicitParam(name = "userId", required = true, value = "鐢ㄦ埛id", dataTypeClass = String.class), + @ApiImplicitParam(name = "password", required = true, value = "鏂板瘑鐮侊紙鏈猰d5鍔犲瘑鐨勫瘑鐮侊級", dataTypeClass = String.class), + }) + @PostMapping("/changePasswordForAdmin") + public String changePasswordForAdmin(@RequestParam int userId, @RequestParam String password) { + // 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛id + LoginUser userInfo = SecurityUtils.getUserInfo(); + if (userInfo == null) { + return "fail"; + } + Role role = userInfo.getRole(); + if (role != null && role.getId() == 1) { + boolean result = userService.changePassword(userId, DigestUtils.md5DigestAsHex(password.getBytes())); + if (result) { + return "success"; + } + } + + return "fail"; + } } diff --git a/web_src/src/components/Login.vue b/web_src/src/components/Login.vue index d823659..4ade744 100644 --- a/web_src/src/components/Login.vue +++ b/web_src/src/components/Login.vue @@ -86,7 +86,7 @@ }).then(function (res) { console.log(JSON.stringify(res)); if (res.data.code == 0 && res.data.msg == "success") { - that.$cookies.set("session", {"username": that.username}) ; + that.$cookies.set("session", {"username": that.username,"roleId":res.data.data.role.id}) ; //鐧诲綍鎴愬姛鍚� that.cancelEnterkeyDefaultAction(); that.$router.push('/'); diff --git a/web_src/src/components/UserManager.vue b/web_src/src/components/UserManager.vue new file mode 100644 index 0000000..10faf6d --- /dev/null +++ b/web_src/src/components/UserManager.vue @@ -0,0 +1,236 @@ +<template> + + <div id="app" style="width: 100%"> + <div class="page-header"> + + <div class="page-title">鐢ㄦ埛鍒楄〃</div> + <div class="page-header-btn"> + <el-button icon="el-icon-plus" size="mini" style="margin-right: 1rem;" type="primary" @click="addUser"> + 娣诲姞鐢ㄦ埛 + </el-button> + + </div> + </div> + <!--鐢ㄦ埛鍒楄〃--> + <el-table :data="userList" style="width: 100%;font-size: 12px;" :height="winHeight" + header-row-class-name="table-header"> + <el-table-column prop="username" label="鐢ㄦ埛鍚�" min-width="160"/> + <el-table-column prop="pushKey" label="pushkey" min-width="160"/> + <el-table-column prop="role.name" label="绫诲瀷" min-width="160"/> + <el-table-column label="鎿嶄綔" min-width="450" fixed="right"> + <template slot-scope="scope"> + <el-button size="medium" icon="el-icon-edit" type="text" @click="edit(scope.row)">淇敼瀵嗙爜</el-button> + <el-divider direction="vertical"></el-divider> + <el-button size="medium" icon="el-icon-refresh" type="text" @click="resetPushKey(scope.row)">閲嶇疆pushkey</el-button> + <el-divider direction="vertical"></el-divider> + <el-button size="medium" icon="el-icon-delete" type="text" @click="deleteUser(scope.row)" + style="color: #f56c6c">鍒犻櫎 + </el-button> + </template> + </el-table-column> + </el-table> + <changePasswordForAdmin ref="changePasswordForAdmin"></changePasswordForAdmin> + <addUser ref="addUser"></addUser> + <el-pagination + style="float: right" + @size-change="handleSizeChange" + @current-change="currentChange" + :current-page="currentPage" + :page-size="count" + :page-sizes="[15, 25, 35, 50]" + layout="total, sizes, prev, pager, next" + :total="total"> + </el-pagination> + </div> +</template> + +<script> +import uiHeader from '../layout/UiHeader.vue' +import changePasswordForAdmin from './dialog/changePasswordForAdmin.vue' +import addUser from '../components/dialog/addUser.vue' + +export default { + name: 'userManager', + components: { + uiHeader, + changePasswordForAdmin, + addUser + }, + data() { + return { + userList: [], //璁惧鍒楄〃 + currentUser: {}, //褰撳墠鎿嶄綔璁惧瀵硅薄 + + videoComponentList: [], + updateLooper: 0, //鏁版嵁鍒锋柊杞鏍囧織 + currentUserLenth: 0, + winHeight: window.innerHeight - 200, + currentPage: 1, + count: 15, + total: 0, + getUserListLoading: false + }; + }, + mounted() { + this.initData(); + this.updateLooper = setInterval(this.initData, 10000); + }, + destroyed() { + this.$destroy('videojs'); + clearTimeout(this.updateLooper); + }, + methods: { + initData: function () { + this.getUserList(); + }, + currentChange: function (val) { + this.currentPage = val; + this.getUserList(); + }, + handleSizeChange: function (val) { + this.count = val; + this.getUserList(); + }, + getUserList: function () { + let that = this; + this.getUserListLoading = true; + this.$axios({ + method: 'get', + url: `/api/user/users`, + params: { + page: that.currentPage, + count: that.count + } + }).then(function (res) { + that.total = res.data.total; + that.userList = res.data.list; + that.getUserListLoading = false; + }).catch(function (error) { + that.getUserListLoading = false; + }); + + }, + edit: function (row) { + this.$refs.changePasswordForAdmin.openDialog(row, () => { + this.$refs.changePasswordForAdmin.close(); + this.$message({ + showClose: true, + message: "瀵嗙爜淇敼鎴愬姛", + type: "success", + }); + setTimeout(this.getDeviceList, 200) + + }) + }, + deleteUser: function (row) { + let msg = "纭畾鍒犻櫎姝ょ敤鎴凤紵" + if (row.online !== 0) { + msg = "<strong>纭畾鍒犻櫎姝ょ敤鎴凤紵</strong>" + } + this.$confirm(msg, '鎻愮ず', { + dangerouslyUseHTMLString: true, + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + center: true, + type: 'warning' + }).then(() => { + this.$axios({ + method: 'delete', + url: `/api/user/delete?id=${row.id}` + }).then((res) => { + this.getUserList(); + }).catch((error) => { + console.error(error); + }); + }).catch(() => { + + }); + + + }, + resetPushKey: function (row) { + let msg = "纭畾閲嶇疆pushkey锛�" + if (row.online !== 0) { + msg = "<strong>纭畾閲嶇疆pushkey锛�</strong>" + } + this.$confirm(msg, '鎻愮ず', { + dangerouslyUseHTMLString: true, + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + center: true, + type: 'warning' + }).then(() => { + this.$axios({ + method: 'get', + url: `/api/user/resetPushKey?id=${row.id}` + }).then((res) => { + this.getUserList(); + }).catch((error) => { + console.error(error); + }); + }).catch(() => { + + }); + + + }, + addUser: function () { + this.$refs.addUser.openDialog() + } + } +} +</script> +<style> +.videoList { + display: flex; + flex-wrap: wrap; + align-content: flex-start; +} + +.video-item { + position: relative; + width: 15rem; + height: 10rem; + margin-right: 1rem; + background-color: #000000; +} + +.video-item-img { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + width: 100%; + height: 100%; +} + +.video-item-img:after { + content: ""; + display: inline-block; + position: absolute; + z-index: 2; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + width: 3rem; + height: 3rem; + background-image: url("../assets/loading.png"); + background-size: cover; + background-color: #000000; +} + +.video-item-title { + position: absolute; + bottom: 0; + color: #000000; + background-color: #ffffff; + line-height: 1.5rem; + padding: 0.3rem; + width: 14.4rem; +} + +</style> diff --git a/web_src/src/components/dialog/addUser.vue b/web_src/src/components/dialog/addUser.vue new file mode 100644 index 0000000..403eceb --- /dev/null +++ b/web_src/src/components/dialog/addUser.vue @@ -0,0 +1,159 @@ +<template> + <div id="addUser" v-loading="isLoging"> + <el-dialog + title="娣诲姞鐢ㄦ埛" + width="40%" + top="2rem" + :close-on-click-modal="false" + :visible.sync="showDialog" + :destroy-on-close="true" + @close="close()" + > + <div id="shared" style="margin-right: 20px;"> + <el-form ref="passwordForm" :rules="rules" status-icon label-width="80px"> + <el-form-item label="鐢ㄦ埛鍚�" prop="username"> + <el-input v-model="username" autocomplete="off"></el-input> + </el-form-item> + <el-form-item label="鐢ㄦ埛绫诲瀷" prop="roleId"> + <el-select v-model="roleId" placeholder="璇烽�夋嫨"> + <el-option + v-for="item in options" + :key="item.id" + :label="item.name" + :value="item.id"> + </el-option> + </el-select> + </el-form-item> + <el-form-item label="瀵嗙爜" prop="password"> + <el-input v-model="password" autocomplete="off"></el-input> + </el-form-item> + <el-form-item label="纭瀵嗙爜" prop="confirmPassword"> + <el-input v-model="confirmPassword" autocomplete="off"></el-input> + </el-form-item> + + <el-form-item> + <div style="float: right;"> + <el-button type="primary" @click="onSubmit">淇濆瓨</el-button> + <el-button @click="close">鍙栨秷</el-button> + </div> + </el-form-item> + </el-form> + </div> + </el-dialog> + </div> +</template> + +<script> + +export default { + name: "addUser", + props: {}, + computed: {}, + created() { + this.getAllRole(); + }, + data() { + let validatePass1 = (rule, value, callback) => { + if (value === '') { + callback(new Error('璇疯緭鍏ユ柊瀵嗙爜')); + } else { + if (this.confirmPassword !== '') { + this.$refs.passwordForm.validateField('confirmPassword'); + } + callback(); + } + }; + let validatePass2 = (rule, value, callback) => { + if (this.confirmPassword === '') { + callback(new Error('璇峰啀娆¤緭鍏ュ瘑鐮�')); + } else if (this.confirmPassword !== this.password) { + callback(new Error('涓ゆ杈撳叆瀵嗙爜涓嶄竴鑷�!')); + } else { + callback(); + } + }; + return { + value:"", + options: [], + loading: false, + username: null, + password: null, + roleId: null, + confirmPassword: null, + listChangeCallback: null, + showDialog: false, + isLoging: false, + rules: { + newPassword: [{required: true, validator: validatePass1, trigger: "blur"}, { + pattern: /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[~!@#$%^&*()_+`\-={}:";'<>?,.\/]).{8,20}$/, + message: "瀵嗙爜闀垮害鍦�8-20浣嶄箣闂�,鐢卞瓧姣�+鏁板瓧+鐗规畩瀛楃缁勬垚", + },], + confirmPassword: [{required: true, validator: validatePass2, trigger: "blur"}], + }, + }; + }, + methods: { + openDialog: function (callback) { + this.listChangeCallback = callback; + this.showDialog = true; + }, + onSubmit: function () { + this.$axios({ + method: 'post', + url: "/api/user/add", + params: { + username: this.username, + password: this.password, + roleId: this.roleId + } + }).then((res) => { + if (res.data.code === 0) { + this.$message({ + showClose: true, + message: '娣诲姞鎴愬姛', + type: 'success', + + }); + this.showDialog = false; + this.listChangeCallback() + + } else { + this.$message({ + showClose: true, + message: res.data.msg, + type: 'error' + }); + } + }).catch((error) => { + console.error(error) + }); + }, + close: function () { + this.showDialog = false; + this.password = null; + this.confirmPassword = null; + this.username = null; + this.roleId = null; + }, + getAllRole:function () { + + this.$axios({ + method: 'get', + url: "/api/role/all" + }).then((res) => { + this.loading = true; + console.info(res) + res.data + console.info(res.data.code) + if (res.data.code === 0) { + console.info(res.data.data) + this.options=res.data.data + + } + }).catch((error) => { + console.error(error) + }); + } + }, +}; +</script> diff --git a/web_src/src/components/dialog/changePasswordForAdmin.vue b/web_src/src/components/dialog/changePasswordForAdmin.vue new file mode 100644 index 0000000..0e0ae22 --- /dev/null +++ b/web_src/src/components/dialog/changePasswordForAdmin.vue @@ -0,0 +1,121 @@ +<template> + <div id="changePassword" v-loading="isLoging"> + <el-dialog + title="淇敼瀵嗙爜" + width="40%" + top="2rem" + :close-on-click-modal="false" + :visible.sync="showDialog" + :destroy-on-close="true" + @close="close()" + > + <div id="shared" style="margin-right: 20px;"> + <el-form ref="passwordForm" :rules="rules" status-icon label-width="80px"> + <el-form-item label="鏂板瘑鐮�" prop="newPassword" > + <el-input v-model="newPassword" autocomplete="off"></el-input> + </el-form-item> + <el-form-item label="纭瀵嗙爜" prop="confirmPassword"> + <el-input v-model="confirmPassword" autocomplete="off"></el-input> + </el-form-item> + + <el-form-item> + <div style="float: right;"> + <el-button type="primary" @click="onSubmit">淇濆瓨</el-button> + <el-button @click="close">鍙栨秷</el-button> + </div> + </el-form-item> + </el-form> + </div> + </el-dialog> + </div> +</template> + +<script> +export default { + name: "changePasswordForAdmin", + props: {}, + computed: {}, + created() {}, + data() { + let validatePass1 = (rule, value, callback) => { + if (value === '') { + callback(new Error('璇疯緭鍏ユ柊瀵嗙爜')); + } else { + if (this.confirmPassword !== '') { + this.$refs.passwordForm.validateField('confirmPassword'); + } + callback(); + } + }; + let validatePass2 = (rule, value, callback) => { + if (this.confirmPassword === '') { + callback(new Error('璇峰啀娆¤緭鍏ュ瘑鐮�')); + } else if (this.confirmPassword !== this.newPassword) { + callback(new Error('涓ゆ杈撳叆瀵嗙爜涓嶄竴鑷�!')); + } else { + callback(); + } + }; + return { + newPassword: null, + confirmPassword: null, + userId: null, + showDialog: false, + isLoging: false, + listChangeCallback: null, + form: {}, + rules: { + newPassword: [{ required: true, validator: validatePass1, trigger: "blur" }, { + pattern: /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[~!@#$%^&*()_+`\-={}:";'<>?,.\/]).{8,20}$/, + message: "瀵嗙爜闀垮害鍦�8-20浣嶄箣闂�,鐢卞瓧姣�+鏁板瓧+鐗规畩瀛楃缁勬垚", + },], + confirmPassword: [{ required: true, validator: validatePass2, trigger: "blur" }], + }, + }; + }, + methods: { + openDialog: function (row, callback) { + console.log(row) + this.showDialog = true; + this.listChangeCallback = callback; + if (row != null) { + this.form = row; + } + }, + onSubmit: function () { + this.$axios({ + method: 'post', + url:"/api/user/changePasswordForAdmin", + params: { + password: this.newPassword, + userId: this.form.id, + } + }).then((res)=> { + if (res.data === "success"){ + this.$message({ + showClose: true, + message: '淇敼鎴愬姛', + type: 'success' + }); + this.showDialog = false; + }else { + this.$message({ + showClose: true, + message: '淇敼瀵嗙爜澶辫触锛屾槸鍚﹀凡鐧诲綍锛堟帴鍙i壌鏉冨叧闂棤娉曚慨鏀瑰瘑鐮侊級', + type: 'error' + }); + } + }).catch((error)=> { + console.error(error) + }); + }, + close: function () { + this.showDialog = false; + this.newPassword = null; + this.confirmPassword = null; + this.userId=null; + this.adminId=null; + }, + }, +}; +</script> diff --git a/web_src/src/layout/UiHeader.vue b/web_src/src/layout/UiHeader.vue index 3c933f1..42d617e 100644 --- a/web_src/src/layout/UiHeader.vue +++ b/web_src/src/layout/UiHeader.vue @@ -13,6 +13,7 @@ <el-menu-item index="/cloudRecord">浜戠褰曞儚</el-menu-item> <el-menu-item index="/mediaServerManger">鑺傜偣绠$悊</el-menu-item> <el-menu-item index="/parentPlatformList/15/1">鍥芥爣绾ц仈</el-menu-item> + <el-menu-item v-if="editUser" index="/userManager">鐢ㄦ埛绠$悊</el-menu-item> <!-- <el-submenu index="/setting">--> <!-- <template slot="title">绯荤粺璁剧疆</template>--> @@ -47,9 +48,11 @@ alarmNotify: false, sseSource: null, activeIndex: this.$route.path, + editUser: this.$cookies.get("session").roleId==1 }; }, created() { + console.log(this.$cookies.get("session")) if (this.$route.path.startsWith("/channelList")) { this.activeIndex = "/deviceList" } diff --git a/web_src/src/router/index.js b/web_src/src/router/index.js index 2c9c6f3..7651a72 100644 --- a/web_src/src/router/index.js +++ b/web_src/src/router/index.js @@ -17,6 +17,7 @@ import media from '../components/setting/Media.vue' import live from '../components/live.vue' import deviceTree from '../components/common/DeviceTree.vue' +import userManager from '../components/UserManager.vue' import wasmPlayer from '../components/common/jessibuca.vue' import rtcPlayer from '../components/dialog/rtcPlayer.vue' @@ -103,6 +104,11 @@ name: 'map', component: map, }, + { + path: '/userManager', + name: 'userManager', + component: userManager, + } ] }, { -- Gitblit v1.8.0