├── .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 |
2 |
3 |
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 |
--------------------------------------------------------------------------------
/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 |
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 |
2 |
52 |
53 |
54 |
59 |
--------------------------------------------------------------------------------
/src/components/Avatar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ userNickname }}
7 |
8 | 个人中心
9 | 注销登陆
10 |
11 |
12 |
13 |
14 |
15 |
38 |
--------------------------------------------------------------------------------
/src/components/Breadcrumb.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ item.name }}
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/DatabaseIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {{ databaseType }}
34 |
35 |
36 |
37 |
38 |
47 |
--------------------------------------------------------------------------------
/src/components/Oauth2AppType.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {{ appType }}
19 |
20 |
21 |
22 |
23 |
32 |
--------------------------------------------------------------------------------
/src/components/document/Diagram.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
--------------------------------------------------------------------------------
/src/components/document/DocumentDiffTag.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 新增
4 |
5 |
6 | 修改
7 |
8 |
9 | 删除
10 |
11 |
12 | 无变化
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/document/DocumentDiscussion.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 | {{ title }}
9 |
10 |
11 |
12 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | 提交
75 |
76 |
77 |
78 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Databasir
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | 登录
23 |
24 |
25 |
26 | 忘记密码?
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | 第三方登录
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
82 |
--------------------------------------------------------------------------------
/src/views/OAuth2Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 | 立即跳转
12 |
13 |
14 |
15 |
16 |
17 |
18 |
25 |
--------------------------------------------------------------------------------
/src/views/SysDocumentTemplateProperty.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 | Tables
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Columns
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | Indexes
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | Foreign Keys
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | Trigger
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
102 |
--------------------------------------------------------------------------------
/src/views/SysEmailEdit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 系统邮箱设置
7 |
8 |
9 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | :
30 |
31 |
32 |
33 |
34 |
35 |
36 |
39 |
40 |
41 |
44 |
45 |
46 |
47 | 保存
48 | 重置
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/views/SysLog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{logModuleColumnLabel}}
11 |
12 |
13 |
14 |
15 |
16 |
17 | {{ item.text }}
18 |
19 |
20 |
21 |
22 |
23 |
24 | {{ scope.row.operationModule }}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {{logStatusColumnLabel}}
34 |
35 |
36 |
37 |
38 |
39 |
40 | {{ item.text }}
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | 成功
49 |
50 |
51 | 失败
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | {{ scope.row.operationResponse.errMessage }}
62 |
63 |
64 |
65 |
66 |
67 | {{ scope.row.involvedGroup.name }}
68 |
69 | -
70 |
71 |
72 |
73 |
74 |
75 | {{ scope.row.involvedProject.name }}
76 |
77 | -
78 |
79 |
80 |
81 |
82 |
83 |
84 | {{ scope.row.involvedUser.nickname }}
85 |
86 | -
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/src/views/SysOauth2.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | {{item.appName}}
35 |
36 |
37 |
38 |
39 |
44 |
45 |
46 | 应用 ID
47 |
48 | {{ item.registrationId }}
49 |
50 |
51 |
52 | 应用类型
53 |
54 |
55 | {{ item.appType }}
56 |
57 |
58 |
59 |
60 | 创建时间
61 |
62 | {{item.createAt}}
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | 编辑
71 |
72 |
73 | 删除
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | 请在 {{ appFormData.appType }} 中配置回调地址
119 |
120 | {{ redirectUri }}{{ appFormData.registrationId }}
121 |
122 |
123 |
124 |
125 | 保存
126 | 取消
127 |
128 |
129 |
130 |
131 |
132 |
133 |
139 |
140 |
141 |
142 |
143 |
144 |
149 |
150 |
--------------------------------------------------------------------------------
/src/views/UserList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {{ scope.row.email }}
25 |
26 |
27 |
28 |
29 |
33 |
34 |
35 |
36 | {{userEnabledColumnLabel}}
37 |
38 |
39 |
40 |
41 |
42 |
43 | {{ item.text }}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | 重置密码
64 |
65 | 删除账号
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
81 |
82 |
83 |
84 |
85 |
91 |
95 | {{ userDetailData.id }}
96 | {{ userDetailData.nickname }}
97 | {{ userDetailData.username }}
98 | {{ userDetailData.email }}
99 | {{ userDetailData.enabled?'启用中':'已禁用' }}
100 | {{ userDetailData.createAt }}
101 |
102 |
103 | 角色信息
104 |
105 |
106 |
107 |
108 |
109 | {{ scope.row.groupName }}
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | 保存
143 | 取消
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
--------------------------------------------------------------------------------
/src/views/UserProfile.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | 修改密码
39 |
40 |
41 |
42 |
43 |
44 |
50 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | 确认
68 | 取消
69 |
70 |
71 |
72 |
73 |
74 |
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 | };
--------------------------------------------------------------------------------