├── .env ├── .env.dev ├── .env.int ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── api │ ├── Const.js │ ├── DatabaseType.js │ ├── Document.js │ ├── DocumentDescription.js │ ├── DocumentDiscussion.js │ ├── DocumentTemplate.js │ ├── Group.js │ ├── Login.js │ ├── MockData.js │ ├── OAuthApp.js │ ├── OperationLog.js │ ├── Project.js │ ├── Search.js │ ├── System.js │ ├── User.js │ └── UserProject.js ├── assets │ ├── app │ │ ├── github.svg │ │ ├── gitlab.svg │ │ └── wework.svg │ ├── common │ │ └── jar.svg │ ├── database │ │ ├── Hive.svg │ │ ├── MariaDB.svg │ │ ├── MySQL.svg │ │ ├── Oracle.svg │ │ ├── PostgreSQL.svg │ │ ├── Sqlserver.svg │ │ ├── default.svg │ │ └── dm8.png │ ├── icon │ │ ├── doc-table.svg │ │ └── user-group.svg │ └── logo.png ├── components │ ├── AppNav.vue │ ├── Avatar.vue │ ├── Breadcrumb.vue │ ├── DatabaseIcon.vue │ ├── Oauth2AppType.vue │ └── document │ │ ├── Diagram.vue │ │ ├── DocumentDiffTag.vue │ │ ├── DocumentDiscussion.vue │ │ └── DocumentList.vue ├── layouts │ └── Layout.vue ├── main.js ├── router │ ├── breadcurmb.js │ └── index.js ├── store │ └── index.js ├── utils │ ├── DatabaseFieldFormatter.js │ ├── DialogWidthCalculator.js │ ├── auth.js │ └── fetch.js └── views │ ├── Document.vue │ ├── GroupDashboard.vue │ ├── GroupList.vue │ ├── Login.vue │ ├── OAuth2Login.vue │ ├── SysDatabaseType.vue │ ├── SysDocumentTemplateProperty.vue │ ├── SysEmailEdit.vue │ ├── SysLog.vue │ ├── SysOauth2.vue │ ├── UserList.vue │ └── UserProfile.vue └── vue.config.js /.env: -------------------------------------------------------------------------------- 1 | port = 3000 -------------------------------------------------------------------------------- /.env.dev: -------------------------------------------------------------------------------- 1 | ENV = 'dev' 2 | port = 3000 3 | VUE_APP_API_URL = 'http://localhost:8080' -------------------------------------------------------------------------------- /.env.int: -------------------------------------------------------------------------------- 1 | ENV = 'int' 2 | port = 3000 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # databasir-frontend 3 | 4 | ## Project setup 5 | ``` 6 | npm install 7 | ``` 8 | 9 | ### Compiles and hot-reloads for development 10 | ``` 11 | npm run serve 12 | ``` 13 | 14 | ### Compiles and minifies for production 15 | ``` 16 | npm run build 17 | ``` 18 | 19 | ### Lints and fixes files 20 | ``` 21 | npm run lint 22 | ``` 23 | 24 | ### Customize configuration 25 | See [Configuration Reference](https://cli.vuejs.org/config/). 26 | 27 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "databasir", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve --mode dev", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "build-int": "vue-cli-service build --mode int", 10 | "serve-int": "vue-cli-service serve --mode int" 11 | }, 12 | "dependencies": { 13 | "@antv/layout": "^0.2.3", 14 | "@antv/x6": "^1.32.2", 15 | "@antv/x6-vue-shape": "^1.4.0", 16 | "@element-plus/icons": "0.0.11", 17 | "@highlightjs/vue-plugin": "^2.1.0", 18 | "@soerenmartius/vue3-clipboard": "^0.1.2", 19 | "axios": "^0.24.0", 20 | "core-js": "^3.22.5", 21 | "element-plus": "^2.2.0", 22 | "highlight.js": "^11.5.1", 23 | "js-cookie": "^3.0.1", 24 | "simple-code-editor": "^1.2.2", 25 | "vue": "^3.2.34", 26 | "vue-router": "^4.0.15", 27 | "vuex": "^4.0.2", 28 | "vxe-table": "^4.2.4-beta.2", 29 | "xe-utils": "^3.5.4" 30 | }, 31 | "devDependencies": { 32 | "@babel/eslint-parser": "^7.5.4", 33 | "@vue/cli-plugin-babel": "^5.0.4", 34 | "@vue/cli-plugin-eslint": "^5.0.4", 35 | "@vue/cli-service": "^5.0.4", 36 | "@vue/compiler-sfc": "^3.2.34", 37 | "eslint": "^8.15.0", 38 | "eslint-plugin-vue": "^7.0.0" 39 | }, 40 | "eslintConfig": { 41 | "root": true, 42 | "env": { 43 | "node": true 44 | }, 45 | "extends": [ 46 | "plugin:vue/vue3-essential", 47 | "eslint:recommended" 48 | ], 49 | "parserOptions": { 50 | "parser": "@babel/eslint-parser" 51 | }, 52 | "rules": {} 53 | }, 54 | "browserslist": [ 55 | "> 1%", 56 | "last 2 versions", 57 | "not dead" 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vran-dev/databasir-frontend/3ea6be4ebb0848b1110640c781ad8d46d7221060/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/api/Const.js: -------------------------------------------------------------------------------- 1 | export const databaseTypes = ['mysql', 'postgresql'] 2 | 3 | export const documentTemplatePropertiesKey = "document_template_properties" 4 | 5 | export const appHost = process.env.VUE_APP_API_URL 6 | 7 | export const innerDatabaseTypes = [ 8 | { 9 | author: {}, 10 | template: { 11 | databaseType: "mysql-8.0.28", 12 | jdbcDriverFileUrl: "https://maven.aliyun.com/repository/central/mysql/mysql-connector-java/8.0.28/mysql-connector-java-8.0.28.jar", 13 | icon: require('@/assets/database/MySQL.svg'), 14 | description: "mysql-8.0.28", 15 | jdbcDriverClassName: "com.mysql.cj.jdbc.Driver", 16 | jdbcProtocol: "jdbc:mysql", 17 | urlPattern: "{{jdbc.protocol}}://{{db.url}}/{{db.name}}", 18 | isLocalUpload: false, 19 | } 20 | }, 21 | { 22 | author: {}, 23 | template: { 24 | databaseType: "mysql-5.1.49", 25 | jdbcDriverFileUrl: "https://maven.aliyun.com/repository/central/mysql/mysql-connector-java/5.1.49/mysql-connector-java-5.1.49.jar", 26 | icon: require('@/assets/database/MySQL.svg'), 27 | description: "mysql-5.1.49", 28 | jdbcDriverClassName: "com.mysql.jdbc.Driver", 29 | jdbcProtocol: "jdbc:mysql", 30 | urlPattern: "{{jdbc.protocol}}://{{db.url}}/{{db.name}}", 31 | isLocalUpload: false, 32 | } 33 | }, 34 | { 35 | author: {}, 36 | template: { 37 | databaseType: "postgresql-42.3.4", 38 | jdbcDriverFileUrl: "https://maven.aliyun.com/repository/central/org/postgresql/postgresql/42.3.4/postgresql-42.3.4.jar", 39 | icon: require('@/assets/database/PostgreSQL.svg'), 40 | description: "postgresql jdbc version 42.3.4", 41 | jdbcDriverClassName: "org.postgresql.Driver", 42 | jdbcProtocol: "jdbc:postgresql", 43 | urlPattern: "{{jdbc.protocol}}://{{db.url}}/{{db.name}}", 44 | isLocalUpload: false, 45 | } 46 | }, 47 | { 48 | author: {}, 49 | template: { 50 | databaseType: "mariadb-3.0.3", 51 | jdbcDriverFileUrl: "https://maven.aliyun.com/repository/central/org/mariadb/jdbc/mariadb-java-client/3.0.3/mariadb-java-client-3.0.3.jar", 52 | icon: require('@/assets/database/MariaDB.svg'), 53 | description: "mariadb-3.0.3", 54 | jdbcDriverClassName: "org.mariadb.jdbc.Driver", 55 | jdbcProtocol: "jdbc:mariadb", 56 | urlPattern: "{{jdbc.protocol}}://{{db.url}}/{{db.name}}", 57 | isLocalUpload: false, 58 | } 59 | }, 60 | { 61 | author: {}, 62 | template: { 63 | databaseType: "oracle-thin-12.2.0.1", 64 | jdbcDriverFileUrl: "https://maven.aliyun.com/repository/central/com/oracle/database/jdbc/ojdbc8/12.2.0.1/ojdbc8-12.2.0.1.jar", 65 | icon: require('@/assets/database/Oracle.svg'), 66 | description: "oracle-thin-12.2.0.1", 67 | jdbcDriverClassName: "oracle.jdbc.OracleDriver", 68 | jdbcProtocol: "jdbc:oracle:thin", 69 | urlPattern: "{{jdbc.protocol}}:@{{db.url}}:{{db.name}}", 70 | isLocalUpload: false, 71 | } 72 | }, 73 | { 74 | author: {}, 75 | template: { 76 | databaseType: "sqlServer-9.4.1.jre8", 77 | jdbcDriverFileUrl: "https://maven.aliyun.com/repository/central/com/microsoft/sqlserver/mssql-jdbc/9.4.1.jre8/mssql-jdbc-9.4.1.jre8.jar", 78 | icon: require('@/assets/database/Sqlserver.svg'), 79 | description: "sqlServer-9.4.1.jre8", 80 | jdbcDriverClassName: "com.microsoft.sqlserver.jdbc.SQLServerDriver", 81 | jdbcProtocol: "jdbc:sqlserver", 82 | urlPattern: "{{jdbc.protocol}}://{{db.url}};databaseName={{db.name}}", 83 | isLocalUpload: false, 84 | } 85 | }, 86 | // { 87 | // author: {}, 88 | // template: { 89 | // databaseType: "hive", 90 | // jdbcDriverFileUrl: "https://maven.aliyun.com/repository/central/org/apache/hive/hive-jdbc/3.1.3/hive-jdbc-3.1.3-standalone.jar", 91 | // icon: require('@/assets/database/Hive.svg'), 92 | // description: "hive", 93 | // jdbcDriverClassName: "org.apache.hive.jdbc.HiveDriver", 94 | // jdbcProtocol: "jdbc:hive2", 95 | // urlPattern: "{{jdbc.protocol}}://{{db.url}}/{{db.name}}", 96 | // isLocalUpload: false, 97 | // } 98 | // } 99 | ] -------------------------------------------------------------------------------- /src/api/DatabaseType.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/fetch'; 2 | 3 | const base = '/api/v1.0/database_types' 4 | 5 | const simples = '/api/v1.0/simple_database_types' 6 | 7 | export const deleteDatabaseType = (id) => { 8 | return axios.delete(base+"/"+id) 9 | } 10 | 11 | export const createDatabaseType = (body) => { 12 | return axios.post(base, body) 13 | } 14 | 15 | export const updateDatabaseType = (body) => { 16 | return axios.patch(base, body) 17 | } 18 | 19 | export const listSimples = () => { 20 | return axios.get(simples) 21 | } 22 | 23 | export const listPage = (filter) => { 24 | return axios.get(base, { 25 | params: filter 26 | }) 27 | } 28 | 29 | export const resolveDriverClassName = (request) => { 30 | return axios.post(base + "/driver_class_name", request) 31 | } 32 | 33 | export const uploadDriver = (formData) => { 34 | return axios.post('/api/v1.0/database_types/upload_driver', formData, { 35 | headers: { 36 | 'Content-Type': 'multipart/form-data' 37 | } 38 | }) 39 | } -------------------------------------------------------------------------------- /src/api/Document.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/fetch'; 2 | 3 | const base = '/api/v1.0' 4 | 5 | export const getOneByProjectId = (projectId, parameters) => { 6 | return axios.get(base + '/projects/'+projectId+'/documents', { 7 | params: parameters 8 | }) 9 | } 10 | 11 | export const getTables = (projectId,documentId, tableIds) => { 12 | return axios.post(base + '/projects/'+projectId+'/documents/'+documentId+'/table_documents', tableIds) 13 | } 14 | 15 | export const getSimpleOneByProjectId = (projectId, parameters) => { 16 | return axios.get(base + '/projects/'+projectId+'/documents/simple', { 17 | params: parameters 18 | }) 19 | } 20 | 21 | export const syncByProjectId = (projectId) => { 22 | return axios.post(base + "/projects/"+projectId+"/documents") 23 | } 24 | 25 | export const getVersionByProjectId =(projectId, parameters) => { 26 | return axios.get(base + "/projects/"+projectId+"/document_versions", { 27 | params: parameters 28 | }) 29 | } 30 | 31 | export const getDiff =(projectId, parameters) => { 32 | return axios.get(base + "/projects/"+projectId+"/diff_documents", { 33 | params: parameters 34 | }) 35 | } 36 | 37 | export const exportDocument = (projectId, params, name, callback) => { 38 | return fileDownload(base + "/projects/"+projectId+"/document_files", params, name, callback) 39 | } 40 | 41 | export const supportFileTypes = () => { 42 | return axios.get(base + "/document_file_types") 43 | } 44 | 45 | export const listTables =(projectId, parameters) => { 46 | return axios.get(base + "/projects/"+projectId+"/tables", { 47 | params: parameters 48 | }) 49 | } 50 | 51 | function fileDownload(path, params, name, callback){ 52 | axios.get(path, { 53 | responseType: 'blob', 54 | params: params 55 | }).then(response => { 56 | let urlObj = window.URL || window.webkitURL || window; 57 | const link = document.createElement('a'); 58 | link.href = urlObj.createObjectURL(new Blob([response])); 59 | link.download = name; //or any other extension 60 | document.body.appendChild(link); 61 | link.click(); 62 | callback() 63 | }).catch(() => callback()) 64 | } 65 | -------------------------------------------------------------------------------- /src/api/DocumentDescription.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/fetch'; 2 | 3 | const base = '/api/v1.0' 4 | 5 | export const saveDescription = (groupId, projectId, body) => { 6 | return axios.post(base + '/groups/' +groupId+'/projects/'+projectId+'/descriptions', body) 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/api/DocumentDiscussion.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/fetch'; 2 | 3 | const base = '/api/v1.0' 4 | 5 | export const deleteDiscussion = (groupId, projectId, remarkId) => { 6 | return axios.delete(base + '/groups/' +groupId+'/projects/'+projectId+'/discussions/'+remarkId) 7 | } 8 | 9 | export const createDiscussion = (groupId, projectId, body) => { 10 | return axios.post(base + '/groups/' +groupId+'/projects/'+projectId+'/discussions', body) 11 | } 12 | 13 | export const listDiscussions = (groupId, projectId, parameters) => { 14 | return axios.get(base + '/groups/' +groupId+'/projects/'+projectId+'/discussions', { 15 | params: parameters 16 | }) 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/api/DocumentTemplate.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/fetch'; 2 | 3 | const base = '/api/v1.0' 4 | 5 | export const listProperties = () => { 6 | return axios.get(base + '/document_template/properties') 7 | } 8 | 9 | export const updateProperties = (body) => { 10 | return axios.patch(base + '/document_template/properties', body) 11 | } 12 | -------------------------------------------------------------------------------- /src/api/Group.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/fetch'; 2 | 3 | const base = '/api/v1.0/groups' 4 | 5 | export const listGroups = (pageQuery) => { 6 | return axios.get(base, { 7 | params: pageQuery 8 | }) 9 | } 10 | 11 | export const getGroup= (id) => { 12 | return axios.get(base + "/" + id) 13 | } 14 | 15 | export const createOrUpdateGroup = (body) => { 16 | if (body.id && body.id != null) { 17 | return updateGroup(body) 18 | } else { 19 | return createGroup(body) 20 | } 21 | } 22 | 23 | export const createGroup = (body) => { 24 | return axios.post(base, body) 25 | } 26 | 27 | export const updateGroup = (body) => { 28 | return axios.patch(base, body) 29 | } 30 | 31 | export const deleteGroup = (id) => { 32 | return axios.delete(base + '/' + id) 33 | } 34 | 35 | export const listGroupMembers = (groupId, pageQuery) => { 36 | return axios.get(base + '/' + groupId + '/members', { 37 | params: pageQuery 38 | }) 39 | } 40 | 41 | export const addGroupMember = (groupId, body) => { 42 | return axios.post(base + '/' + groupId + '/members', body) 43 | } 44 | 45 | export const removeGroupMember = (groupId, userId) => { 46 | return axios.delete(base +'/'+groupId+'/members/'+userId) 47 | } 48 | 49 | export const updateGroupMemberRole = (groupId, userId, role) => { 50 | const body = { 51 | role: role 52 | } 53 | return axios.patch(base +'/'+groupId+'/members/'+userId, body) 54 | } 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/api/Login.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/fetch'; 2 | 3 | export const login = (form) => { 4 | const data = new FormData(); 5 | data.append('username', form.username); 6 | data.append('password', form.password); 7 | return axios.post('/login', data) 8 | } 9 | 10 | export const oauth2Login = (registrationId, parameters) => { 11 | return axios.get('/oauth2/login/'+registrationId, { 12 | params: parameters 13 | }) 14 | } 15 | 16 | export const logout = () => { 17 | return axios.get('/logout') 18 | } 19 | 20 | export const loginInfo = () => { 21 | return axios.get('/api/v1.0/login_info') 22 | } 23 | 24 | export const refreshAccessToken = (refreshToken) => { 25 | return axios.post('/access_tokens', { 26 | refreshToken: refreshToken 27 | }) 28 | } -------------------------------------------------------------------------------- /src/api/MockData.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/fetch'; 2 | 3 | const base = '/api/v1.0' 4 | 5 | export const listRules = (groupId, projectId, query) => { 6 | return axios.get(base +"/groups/" + groupId + "/projects/" + projectId +"/mock_rules" , { 7 | params: query 8 | }) 9 | } 10 | 11 | export const saveTableRules = (groupId, projectId, tableId, rule) => { 12 | return axios.post(base +"/groups/" + groupId + "/projects/" + projectId +"/tables/"+tableId+"/mock_rules", rule) 13 | } 14 | 15 | export const getMockSql = (groupId, projectId, query) => { 16 | return axios.get(base +"/groups/" + groupId + "/projects/" + projectId +"/mock_data/sql" , { 17 | params: query 18 | }) 19 | } -------------------------------------------------------------------------------- /src/api/OAuthApp.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/fetch'; 2 | 3 | const base = '/oauth2' 4 | 5 | export const listAll = () => { 6 | return axios.get(base + "/apps") 7 | } 8 | 9 | export const authorizationUrl = (id, params) => { 10 | return axios.get(base + "/authorization/"+id, { 11 | params: params 12 | }) 13 | } 14 | 15 | const apiBase = "/api/v1.0/oauth2_apps" 16 | 17 | export const pageList = (pageQuery) => { 18 | return axios.get(apiBase , { 19 | params: pageQuery 20 | }) 21 | } 22 | 23 | export const deleteById = (id) => { 24 | return axios.delete(apiBase+'/'+id) 25 | } 26 | 27 | export const getById = (id) => { 28 | return axios.get(apiBase+'/'+id) 29 | } 30 | 31 | export const createApp = (body) => { 32 | return axios.post(apiBase, body) 33 | } 34 | 35 | export const updateApp = (body) => { 36 | return axios.patch(apiBase, body) 37 | } 38 | 39 | export const listPlatforms = () => { 40 | return axios.get(apiBase +"/platforms") 41 | } -------------------------------------------------------------------------------- /src/api/OperationLog.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/fetch'; 2 | 3 | const base = '/api/v1.0/operation_logs' 4 | 5 | export const listOperationLogs = (pageQuery) => { 6 | return axios.get(base, { 7 | params: pageQuery 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /src/api/Project.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/fetch'; 2 | 3 | const base = '/api/v1.0/projects' 4 | 5 | export const listProjects = (parameters) => { 6 | return axios.get(base, { 7 | params: parameters 8 | }) 9 | } 10 | 11 | export const getProjectById = (id) => { 12 | return axios.get(base + "/" + id) 13 | } 14 | 15 | export const createOrUpdateProject = (request) => { 16 | if (request.id) { 17 | return updateProject(request) 18 | } else { 19 | return createProject(request) 20 | } 21 | } 22 | 23 | export const createProject = (request) => { 24 | return axios.post(base, request); 25 | } 26 | 27 | export const testConnection = (request) => { 28 | return axios.post(base + '/test_connection', request) 29 | } 30 | 31 | export const listProjectManualTasks = (id, body) => { 32 | return axios.post(base + "/" + id +"/list_manual_tasks", body) 33 | } 34 | 35 | export const cancelProjectTask = (projectId, taskId) => { 36 | return axios.patch(base + "/" + projectId +"/tasks/" + taskId+"/cancel") 37 | } 38 | 39 | const groupProjectBase = '/api/v1.0/groups' 40 | 41 | export const updateProject = (request) => { 42 | return axios.patch(groupProjectBase +'/'+request.groupId+'/projects', request); 43 | } 44 | 45 | export const deleteProjectById = (groupId, id) => { 46 | return axios.delete(groupProjectBase + '/' +groupId +'/projects/' + id); 47 | } 48 | -------------------------------------------------------------------------------- /src/api/Search.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/fetch'; 2 | 3 | const base = "/api/v1.0/search" 4 | 5 | export const query = (keyword) => { 6 | return axios.get(base, { 7 | params: keyword 8 | }) 9 | } -------------------------------------------------------------------------------- /src/api/System.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/fetch'; 2 | 3 | const base = '/api/v1.0/settings' 4 | 5 | export const getEmailSetting = () => { 6 | return axios.get(base+"/sys_email") 7 | } 8 | 9 | export const updateEmailSetting = (request) => { 10 | return axios.post(base+"/sys_email", request); 11 | } 12 | 13 | export const deleteEmailSetting = () => { 14 | return axios.delete(base+"/sys_email"); 15 | } -------------------------------------------------------------------------------- /src/api/User.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/fetch'; 2 | 3 | const base = '/api/v1.0/users' 4 | 5 | export const listUsers = (pageQuery) => { 6 | return axios.get(base, { 7 | params: pageQuery 8 | }) 9 | } 10 | 11 | export const enableUser = (userId) => { 12 | return axios.post(base+"/"+userId+"/enable") 13 | 14 | } 15 | 16 | export const disableUser = (userId) => { 17 | return axios.post(base+"/"+userId+"/disable") 18 | } 19 | 20 | export const getByUserId = (userId) => { 21 | return axios.get(base+"/"+userId) 22 | } 23 | 24 | export const deleteByUserId = (userId) => { 25 | return axios.delete(base+"/"+userId) 26 | } 27 | 28 | export const createUser = (request) => { 29 | return axios.post(base, request) 30 | } 31 | 32 | export const renewPassword = (id) => { 33 | return axios.post(base +'/' + id +'/renew_password') 34 | } 35 | 36 | export const addSysOwnerTo = (userId) => { 37 | return axios.post(base +'/' + userId +'/sys_owners') 38 | } 39 | 40 | export const removeSysOwnerFrom = (userId) => { 41 | return axios.delete(base +'/' + userId +'/sys_owners') 42 | } 43 | 44 | export const updatePassword = (userId, body) => { 45 | return axios.post(base +'/' + userId +'/password', body) 46 | } 47 | 48 | export const updateNickname = (userId, body) => { 49 | return axios.post(base +'/' + userId +'/nickname', body) 50 | } -------------------------------------------------------------------------------- /src/api/UserProject.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/fetch'; 2 | 3 | const base = '/api/v1.0/user_projects/favorites' 4 | 5 | export const listFavorites = (pageQuery) => { 6 | return axios.get(base, { 7 | params: pageQuery 8 | }) 9 | } 10 | 11 | export const removeFavorite = (projectId) => { 12 | return axios.delete(base + "/" + projectId) 13 | } 14 | 15 | export const addFavorite = (projectId) => { 16 | return axios.post(base +'/' + projectId) 17 | } -------------------------------------------------------------------------------- /src/assets/app/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/app/gitlab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/app/wework.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/common/jar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/database/Hive.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/database/MariaDB.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MDB-HLogo_RGB 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/assets/database/MySQL.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/database/Oracle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/database/PostgreSQL.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/database/default.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/database/dm8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vran-dev/databasir-frontend/3ea6be4ebb0848b1110640c781ad8d46d7221060/src/assets/database/dm8.png -------------------------------------------------------------------------------- /src/assets/icon/doc-table.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icon/user-group.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vran-dev/databasir-frontend/3ea6be4ebb0848b1110640c781ad8d46d7221060/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/AppNav.vue: -------------------------------------------------------------------------------- 1 | 54 | 59 | -------------------------------------------------------------------------------- /src/components/Avatar.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 38 | -------------------------------------------------------------------------------- /src/components/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/DatabaseIcon.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 38 | 47 | -------------------------------------------------------------------------------- /src/components/Oauth2AppType.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 32 | -------------------------------------------------------------------------------- /src/components/document/Diagram.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 13 | -------------------------------------------------------------------------------- /src/components/document/DocumentDiffTag.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/document/DocumentDiscussion.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 117 | 118 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import ElementPlus from 'element-plus' 3 | import * as Icons from '@element-plus/icons' 4 | 5 | import 'highlight.js/styles/stackoverflow-light.css'; 6 | import hljs from 'highlight.js/lib/core'; 7 | import javascript from 'highlight.js/lib/languages/javascript'; 8 | import hljsVuePlugin from "@highlightjs/vue-plugin"; 9 | 10 | import 'element-plus/dist/index.css' 11 | import router from './router' 12 | import store from './store' 13 | import App from './App.vue' 14 | import { user } from './utils/auth' 15 | import DatabaseIcon from './components/DatabaseIcon.vue' 16 | import { VueClipboard } from '@soerenmartius/vue3-clipboard' 17 | 18 | import 'xe-utils' 19 | import VXETable from 'vxe-table' 20 | import 'vxe-table/lib/style.css' 21 | 22 | // element icon 23 | const app = createApp(App) 24 | Object.keys(Icons).forEach(key => { 25 | app.component(key, Icons[key]) 26 | }) 27 | app.component('database-icon', DatabaseIcon) 28 | 29 | // permission directive 30 | app.directive('require-roles', { 31 | mounted(el, binding) { 32 | const roles = binding.value 33 | if (!user.hasAnyRoles(roles)) { 34 | el.parentNode && el.parentNode.removeChild(el) 35 | } 36 | }, 37 | }) 38 | 39 | // custom select directive 40 | app.directive("select-more", { 41 | updated(el, binding) { 42 | const child = el.querySelector('.select-trigger'); 43 | const id = child.getAttribute('aria-describedby'); 44 | const poper = document.getElementById(id); 45 | if (poper == null) { 46 | return; 47 | } 48 | const selector = poper.parentElement.querySelector('.el-select-dropdown .el-scrollbar .el-select-dropdown__wrap'); 49 | if (selector == null) { 50 | console.log('load select component failed') 51 | return; 52 | } 53 | selector.addEventListener('scroll', function () { 54 | const condition = this.scrollHeight - this.scrollTop - 1 <= this.clientHeight; 55 | if (condition) { 56 | binding.value(); 57 | } 58 | }); 59 | }, 60 | }); 61 | 62 | 63 | hljs.registerLanguage('javascript', javascript); 64 | app.use(hljsVuePlugin) 65 | app.use(store) 66 | app.use(ElementPlus) 67 | app.use(router) 68 | app.use(VueClipboard) 69 | app.use(VXETable) 70 | app.mount('#app') 71 | 72 | -------------------------------------------------------------------------------- /src/router/breadcurmb.js: -------------------------------------------------------------------------------- 1 | function index() { 2 | return { 3 | name:'首页', 4 | to: { 5 | path: '/' 6 | } 7 | } 8 | } 9 | 10 | function groupList() { 11 | return { 12 | name:'项目中心', 13 | to: { 14 | name: 'groupListPage' 15 | } 16 | } 17 | } 18 | 19 | function groupDashboard(route) { 20 | var groupName = '项目组' 21 | if (route.query.groupName) { 22 | groupName = route.query.groupName 23 | } 24 | return { 25 | name: groupName, 26 | to: { 27 | path: '/groups/'+route.params.groupId 28 | } 29 | } 30 | } 31 | 32 | function groupProjectDocument(route) { 33 | var name = '项目文档' 34 | if (route.query.projectName) { 35 | name = route.query.projectName 36 | } 37 | var projectId = route.params.projectId 38 | var groupId = route.params.groupId 39 | return { 40 | name: name, 41 | to: { 42 | path: '/groups/' + groupId + '/projects/' + projectId + '/documents', 43 | query: { 44 | projectName: name 45 | } 46 | } 47 | } 48 | } 49 | 50 | function userList() { 51 | return { 52 | name:'用户列表', 53 | to: { 54 | path: '/users' 55 | } 56 | } 57 | } 58 | 59 | 60 | function userProfile() { 61 | return { 62 | name:'个人中心', 63 | to: { 64 | path: '/profile' 65 | } 66 | } 67 | } 68 | 69 | function sysEmailEdit() { 70 | return { 71 | name:'邮箱设置', 72 | to: { 73 | path: '/settings/sysEmail' 74 | } 75 | } 76 | } 77 | 78 | function sysLog() { 79 | return { 80 | name:'操作日志', 81 | to: { 82 | path: '/settings/sysLog' 83 | } 84 | } 85 | } 86 | 87 | function sysOauth2() { 88 | return { 89 | name:'登录设置', 90 | to: { 91 | path: '/settings/sysOauth2' 92 | } 93 | } 94 | } 95 | 96 | function sysDatabaseType() { 97 | return { 98 | name:'数据库扩展', 99 | to: { 100 | path: '/settings/sysDatabaseType' 101 | } 102 | } 103 | } 104 | 105 | function sysDocumentTemplate() { 106 | return { 107 | name:'文档模板', 108 | to: { 109 | path: '/settings/sysDocumentTemplate' 110 | } 111 | } 112 | } 113 | 114 | 115 | const breadcurmbMap = { 116 | index: () => [index() ], 117 | groupList: () => [index(), groupList()], 118 | groupDashboard: (route, state) => [index(), groupList(), groupDashboard(route, state)], 119 | groupProjectDocument: (route, state) => [index(), groupList(), groupDashboard(route, state), groupProjectDocument(route)], 120 | userProfile: () => [index(), userProfile()], 121 | userList: () => [index(), userList()], 122 | sysEmailEdit: () => [index(), sysEmailEdit()], 123 | sysLog: () => [index(), sysLog()], 124 | sysOauth2: () => [index(), sysOauth2()], 125 | sysDatabaseType: () => [index(), sysDatabaseType()], 126 | sysDocumentTemplate: () => [index(), sysDocumentTemplate()], 127 | } 128 | 129 | export default breadcurmbMap -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router'; 2 | import Layout from "../layouts/Layout.vue" 3 | import breadcurmbMap from './breadcurmb' 4 | import { token } from '../utils/auth'; 5 | 6 | const routes = [ 7 | { 8 | path: '/login', 9 | component: () => import('@/views/Login.vue'), 10 | hidden: true, 11 | meta: { 12 | requireAuth: false 13 | } 14 | }, 15 | { 16 | path: '/login/oauth2/:id', 17 | component: () => import('@/views/OAuth2Login.vue'), 18 | hidden: true, 19 | props: true, 20 | meta: { 21 | requireAuth: false 22 | } 23 | }, 24 | { 25 | path: '/', 26 | hidden: true, 27 | component: Layout, 28 | children: [ 29 | { 30 | path: '', 31 | hidden: true, 32 | component: () => import('@/views/GroupList.vue'), 33 | meta: { 34 | breadcrumb: breadcurmbMap.groupList 35 | } 36 | } 37 | ] 38 | }, 39 | { 40 | path: '/groups', 41 | icon: 'Collection', 42 | hidden: true, 43 | component: Layout, 44 | meta: { 45 | nav: '项目中心', 46 | }, 47 | children: [ 48 | { 49 | path: '', 50 | name: 'groupListPage', 51 | hidden: true, 52 | component: () => import('@/views/GroupList.vue'), 53 | meta: { 54 | breadcrumb: breadcurmbMap.groupList 55 | } 56 | }, 57 | { 58 | path: ':groupId', 59 | hidden: true, 60 | component: () => import('@/views/GroupDashboard.vue'), 61 | meta: { 62 | breadcrumb: breadcurmbMap.groupDashboard 63 | } 64 | }, 65 | { 66 | path: ':groupId/projects/:projectId/documents', 67 | hidden: true, 68 | component: () => import('@/views/Document.vue'), 69 | meta: { 70 | breadcrumb: breadcurmbMap.groupProjectDocument 71 | } 72 | } 73 | ] 74 | }, 75 | { 76 | path: '/users', 77 | icon: 'List', 78 | component: Layout, 79 | meta: { 80 | nav:'用户中心', 81 | requireAnyRoles: ['SYS_OWNER'] 82 | }, 83 | children: [ 84 | { 85 | path: '', 86 | hidden: true, 87 | component: () => import('@/views/UserList.vue'), 88 | meta: { 89 | breadcrumb: breadcurmbMap.userList 90 | } 91 | } 92 | ] 93 | }, 94 | { 95 | path: '/profile', 96 | icon: 'User', 97 | component: Layout, 98 | meta: { 99 | nav: '个人中心', 100 | breadcrumb: breadcurmbMap.userProfile 101 | }, 102 | children: [ 103 | { 104 | path: '', 105 | hidden: true, 106 | component: () => import('@/views/UserProfile.vue') 107 | } 108 | ] 109 | }, 110 | { 111 | path: '/settings', 112 | icon: 'Setting', 113 | component: Layout, 114 | meta: { 115 | nav:'系统中心', 116 | requireAnyRoles: ['SYS_OWNER'] 117 | }, 118 | children: [ 119 | { 120 | path: 'sysEmail', 121 | icon: 'Notification', 122 | component: () => import('@/views/SysEmailEdit.vue'), 123 | meta: { 124 | nav: '邮箱设置', 125 | breadcrumb: breadcurmbMap.sysEmailEdit 126 | } 127 | }, 128 | { 129 | path: 'sysLog', 130 | icon: 'Document', 131 | component: () => import('@/views/SysLog.vue'), 132 | meta: { 133 | nav: '操作日志', 134 | breadcrumb: breadcurmbMap.sysLog 135 | } 136 | }, 137 | { 138 | path: 'sysOauth2', 139 | icon: 'Connection', 140 | component: () => import('@/views/SysOauth2.vue'), 141 | meta: { 142 | nav: '登陆设置', 143 | breadcrumb: breadcurmbMap.sysOauth2 144 | } 145 | }, 146 | { 147 | path: 'sysDatabaseType', 148 | icon: 'office-building', 149 | component: () => import('@/views/SysDatabaseType.vue'), 150 | meta: { 151 | nav: '数据库扩展', 152 | breadcrumb: breadcurmbMap.sysDatabaseType 153 | } 154 | }, 155 | { 156 | path: 'sysDocumentTemplate', 157 | icon: 'document-add', 158 | component: () => import('@/views/SysDocumentTemplateProperty.vue'), 159 | meta: { 160 | nav: '文档模板', 161 | breadcrumb: breadcurmbMap.sysDocumentTemplate 162 | } 163 | }, 164 | // TODO 165 | { 166 | path: 'sysKey', 167 | icon: 'Key', 168 | hidden: 'true', 169 | component: Layout, 170 | meta: { 171 | nav: '系统秘钥', 172 | breadcrumb: breadcurmbMap.sysKeyEdit 173 | } 174 | } 175 | ] 176 | } 177 | ]; 178 | 179 | const router = createRouter({ 180 | history: createWebHistory(), 181 | routes 182 | }); 183 | 184 | // 权限路由守卫 185 | router.beforeEach((to, from, next) => { 186 | if (to.meta.requireAuth == false) { 187 | if (to.path == '/login' && token.hasAccessToken()) { 188 | next(from) 189 | } else { 190 | next() 191 | } 192 | } else { 193 | if(token.hasAccessToken()) { 194 | next() 195 | } else { 196 | next({ path: '/login' }) 197 | } 198 | } 199 | }) 200 | 201 | // groupName 参数路由守卫 202 | router.beforeEach((to, from, next) => { 203 | if (!to.query.groupName && from.query.groupName) { 204 | to.query.groupName = from.query.groupName 205 | } 206 | if (!to.query.projectName && from.query.projectName) { 207 | to.query.projectName = from.query.projectName 208 | } 209 | next(); 210 | }) 211 | 212 | export default router; -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | import { user } from '../utils/auth' 3 | 4 | const store = createStore({ 5 | state() { 6 | const data = { 7 | user: { 8 | nickname: null, 9 | usernmae: null, 10 | email: null, 11 | avatar: null, 12 | }, 13 | groupListActiveTab: null, 14 | menu: { 15 | isCollapse: true, 16 | } 17 | } 18 | const loginData = user.loadUserLoginData() 19 | if (loginData) { 20 | data.user.nickname = loginData.nickname 21 | data.user.usernmae = loginData.usernmae 22 | data.user.email = loginData.email 23 | data.user.avatar = loginData.avatar 24 | } 25 | return data; 26 | }, 27 | mutations: { 28 | userUpdate(state, param) { 29 | if (param.nickname) { 30 | state.user.nickname = param.nickname 31 | } 32 | if (param.usernmae) { 33 | state.user.usernmae = param.usernmae 34 | } 35 | if (param.email) { 36 | state.user.email = param.email 37 | } 38 | if (param.avatar) { 39 | state.user.avatar = param.avatar 40 | } 41 | }, 42 | foldMenu(state) { 43 | state.menu.isCollapse = true 44 | }, 45 | expandMenu(state) { 46 | state.menu.isCollapse = false 47 | }, 48 | switchGroupListActiveTab(state, groupListActiveTab) { 49 | if (groupListActiveTab) { 50 | state.groupListActiveTab = groupListActiveTab 51 | } 52 | } 53 | } 54 | }) 55 | 56 | export default store -------------------------------------------------------------------------------- /src/utils/DatabaseFieldFormatter.js: -------------------------------------------------------------------------------- 1 | 2 | export const formatter = { 3 | formatColumnType(column) { 4 | const ignoreLengthTypes = ['timestamp', 'json', 'bool'] 5 | // 长度 6 | if (column.decimalDigits == null || column.decimalDigits == 0 || ignoreLengthTypes.some(type => type == column.type.toLowerCase())) { 7 | return column.type + '('+column.size+')' 8 | } else { 9 | return column.type + '('+column.size+', '+column.decimalDigits+')' 10 | } 11 | }, 12 | 13 | formatColumnName(column, withComment) { 14 | if (withComment) { 15 | if (column.comment && column.comment != '') { 16 | return column.name + ' /* ' + column.comment +' */' 17 | } 18 | } 19 | return column.name 20 | } 21 | } -------------------------------------------------------------------------------- /src/utils/DialogWidthCalculator.js: -------------------------------------------------------------------------------- 1 | export const responsive = (map) => { 2 | const width = window.innerWidth 3 | if (width >= 1920) { 4 | return (map.xl?map.xl: null) 5 | } else if (width >= 1200) { 6 | return (map.lg?map.lg: map.xl?map.xl: null) 7 | 8 | } else if (width >= 992) { 9 | return (map.md? map.md: map.lg?map.lg: map.xl?map.xl: null) 10 | 11 | } else if (width >= 768) { 12 | return map.sm ? map.sm : (map.md? map.md: map.lg?map.lg: map.xl?map.xl: null) 13 | } else { 14 | return map.xs ? map.xs :(map.sm ? map.sm : (map.md? map.md: map.lg?map.lg: map.xl?map.xl: null)) 15 | } 16 | } 17 | 18 | 19 | export const dialogPercentWidth = () => { 20 | return responsive({ 21 | xl: "36%", 22 | lg: "42%", 23 | md: "60%", 24 | sm: "80%", 25 | xs: "90%" 26 | }) 27 | } -------------------------------------------------------------------------------- /src/utils/auth.js: -------------------------------------------------------------------------------- 1 | const accessTokenKey = 'accessToken' 2 | const accessTokenExpireAtKey = 'accessTokenExpireAt' 3 | const userLoginDataKey = 'userLoginData' 4 | 5 | export const token = { 6 | 7 | hasAccessToken() { 8 | const accessToken = this.loadAccessToken() 9 | return accessToken 10 | }, 11 | 12 | hasValidAccessToken() { 13 | const accessToken = this.loadAccessToken() 14 | const expireAt = window.localStorage.getItem(accessTokenExpireAtKey) 15 | if (!accessToken || !expireAt) { 16 | console.log('warn: not found accessToken and expireAt key') 17 | return false 18 | } 19 | // 10s gap 20 | return expireAt > new Date().getTime() -(1000 * 10) 21 | }, 22 | 23 | saveAccessToken(token, tokenExpireAt) { 24 | window.localStorage.setItem(accessTokenKey, token) 25 | window.localStorage.setItem(accessTokenExpireAtKey, tokenExpireAt) 26 | }, 27 | 28 | loadAccessToken() { 29 | let token = window.localStorage.getItem(accessTokenKey) 30 | if (token) { 31 | return token 32 | } 33 | return null 34 | } 35 | } 36 | 37 | export const user = { 38 | 39 | saveUserLoginData(userLoginData) { 40 | window.localStorage.setItem(accessTokenKey, userLoginData.accessToken) 41 | window.localStorage.setItem(accessTokenExpireAtKey, userLoginData.accessTokenExpireAt) 42 | window.localStorage.setItem(userLoginDataKey, JSON.stringify(userLoginData)) 43 | }, 44 | 45 | removeUserLoginData() { 46 | window.localStorage.removeItem(userLoginDataKey) 47 | window.localStorage.removeItem(accessTokenKey) 48 | window.localStorage.removeItem(accessTokenExpireAtKey) 49 | }, 50 | 51 | loadUserLoginData() { 52 | if (!window.localStorage.getItem(userLoginDataKey)) { 53 | return null; 54 | } 55 | const data = window.localStorage.getItem(userLoginDataKey) 56 | return JSON.parse(data) 57 | }, 58 | 59 | hasAnyRoles(roles) { 60 | const data = window.localStorage.getItem(userLoginDataKey) 61 | if (data == null) { 62 | return false 63 | } 64 | const user = JSON.parse(data) 65 | return user 66 | .roles 67 | .map(role => { 68 | if (role.groupId) { 69 | return role.role + '?groupId=' + role.groupId 70 | } else { 71 | return role.role 72 | } 73 | }) 74 | .some(exists => roles.some(expected => expected == exists)) 75 | }, 76 | 77 | getRefreshToken() { 78 | const data = window.localStorage.getItem(userLoginDataKey) 79 | if (data == null) { 80 | return null 81 | } 82 | const user = JSON.parse(data) 83 | return user.refreshToken 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/utils/fetch.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { ElMessage } from 'element-plus' 3 | import router from '../router'; 4 | import { token, user } from './auth'; 5 | import { refreshAccessToken } from '../api/Login'; 6 | 7 | const BASE_API = process.env.VUE_APP_API_URL 8 | // default config 9 | axios.defaults.baseURL = BASE_API, 10 | axios.defaults.timeout = 15 * 1000; 11 | axios.defaults.withCredentials = false; 12 | axios.defaults.headers.post['Content-Type'] = 'application/json'; 13 | axios.defaults.headers.post["Access-Control-Allow-Origin-Type"] = "*"; 14 | 15 | // token request config 16 | // eslint-disable-next-line 17 | let tokenRefreshLock = false 18 | 19 | function lock() { 20 | tokenRefreshLock = true 21 | } 22 | 23 | function unlock() { 24 | tokenRefreshLock = false 25 | } 26 | 27 | let blockRequests = [] 28 | 29 | function blockRequest(request) { 30 | blockRequests.push(request) 31 | } 32 | 33 | function relaseRequests(config) { 34 | blockRequests.forEach(request => { 35 | request(config) 36 | }) 37 | blockRequests = [] 38 | } 39 | 40 | // 请求拦截器 41 | axios.interceptors.request.use(async function (config) { 42 | if (config.url == '/access_tokens' || config.url.startsWith('/oauth2') || config.url == '/login') { 43 | return config 44 | } 45 | 46 | if (token.hasValidAccessToken()) { 47 | config.headers.Authorization = 'Bearer ' + token.loadAccessToken() 48 | return config; 49 | } 50 | if (tokenRefreshLock) { 51 | const promise = new Promise((resolve) => { 52 | blockRequest(() => { 53 | if (config) { 54 | config.headers.Authorization = 'Bearer ' + token.loadAccessToken() 55 | } 56 | resolve(config) 57 | }) 58 | }) 59 | return promise 60 | } 61 | return config 62 | }, function (error) { 63 | unlock() 64 | return Promise.reject(error); 65 | }); 66 | 67 | // response拦截器 68 | axios.interceptors.response.use( 69 | (response) => { 70 | const res = response.data; 71 | if (res.errCode) { 72 | notify(res.errMessage) 73 | } 74 | return res; 75 | }, 76 | (error) => { 77 | if (error.response) { 78 | if(error.response.status == 401) { 79 | if (error.response.data.errCode == 'X_0002' || error.response.data.errCode == 'X_0001') { 80 | user.removeUserLoginData() 81 | notify('登陆状态失效,请重新登陆') 82 | redirectLogin() 83 | } else if (error.response.data.errCode == 'X_0004') { 84 | return refresh(error.config).then(() => retryFromResponse(error.config)) 85 | } 86 | } else if (error.response.status == 403) { 87 | notify('无执行该操作的权限') 88 | } else { 89 | notify(error.message) 90 | } 91 | } else { 92 | console.log(error) 93 | notify('网络异常,请稍后再试') 94 | } 95 | return Promise.reject(error); 96 | } 97 | ); 98 | 99 | async function refresh(config) { 100 | const refreshToken = user.getRefreshToken() 101 | if(!refreshToken) { 102 | redirectLogin() 103 | } 104 | 105 | if(tokenRefreshLock) { 106 | return new Promise((resolve) => { 107 | blockRequest(() => { 108 | if (config) { 109 | config.headers.Authorization = 'Bearer ' + token.loadAccessToken() 110 | } 111 | resolve(config) 112 | }) 113 | }) 114 | } 115 | 116 | lock() 117 | return await refreshAccessToken(refreshToken).then(resp => { 118 | if (resp.errCode) { 119 | redirectLogin() 120 | } else { 121 | token.saveAccessToken(resp.data.accessToken, resp.data.accessTokenExpireAt) 122 | unlock() 123 | } 124 | }).finally(() => unlock()) 125 | } 126 | 127 | async function retryFromResponse(config) { 128 | try { 129 | const res = await axios({ 130 | method: config.method, 131 | data: config.data, 132 | url: config.url, 133 | baseURL: config.baseURL, 134 | headers: { 135 | Authorization: 'Bearer ' + token.loadAccessToken() 136 | }, 137 | }) 138 | relaseRequests(config) 139 | return res; 140 | } catch(error) { 141 | console.log(error) 142 | notify("网络异常,请稍后再试"); 143 | } 144 | } 145 | 146 | 147 | function redirectLogin () { 148 | user.removeUserLoginData() 149 | router.replace('/login') 150 | } 151 | 152 | function notify(msg) { 153 | ElMessage({ 154 | message: msg, 155 | type: 'error', 156 | duration: 5 * 1000, 157 | grouping: true, 158 | }); 159 | } 160 | 161 | export default axios; 162 | -------------------------------------------------------------------------------- /src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 82 | -------------------------------------------------------------------------------- /src/views/OAuth2Login.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 25 | -------------------------------------------------------------------------------- /src/views/SysDocumentTemplateProperty.vue: -------------------------------------------------------------------------------- 1 | 87 | 102 | -------------------------------------------------------------------------------- /src/views/SysEmailEdit.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | -------------------------------------------------------------------------------- /src/views/SysLog.vue: -------------------------------------------------------------------------------- 1 | 105 | 106 | -------------------------------------------------------------------------------- /src/views/SysOauth2.vue: -------------------------------------------------------------------------------- 1 | 143 | 144 | 149 | 150 | -------------------------------------------------------------------------------- /src/views/UserList.vue: -------------------------------------------------------------------------------- 1 | 150 | 151 | -------------------------------------------------------------------------------- /src/views/UserProfile.vue: -------------------------------------------------------------------------------- 1 | 75 | 76 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const port = process.env.port || 3000 2 | module.exports = { 3 | devServer: { 4 | port: port 5 | }, 6 | outputDir: '../api/src/main/resources/static' 7 | }; --------------------------------------------------------------------------------