init
This commit is contained in:
16
src/App.vue
Normal file
16
src/App.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
52
src/api/base/region.js
Normal file
52
src/api/base/region.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/region/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getLazyTree = (parentCode, params) => {
|
||||
return request({
|
||||
url: '/blade-system/region/lazy-tree',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
parentCode,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = code => {
|
||||
return request({
|
||||
url: '/blade-system/region/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
code,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = id => {
|
||||
return request({
|
||||
url: '/blade-system/region/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const submit = row => {
|
||||
return request({
|
||||
url: '/blade-system/region/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
15
src/api/common.js
Normal file
15
src/api/common.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import request from '@/axios';
|
||||
|
||||
/**
|
||||
* 文件流返回
|
||||
* @param url 接口地址
|
||||
* @param params 接口参数
|
||||
*/
|
||||
export const exportBlob = (url, params) => {
|
||||
return request({
|
||||
url: url,
|
||||
params: params,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
});
|
||||
};
|
||||
63
src/api/data/record.js
Normal file
63
src/api/data/record.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import request from '@/axios';
|
||||
|
||||
// 获取数据审计表列表
|
||||
export const getRecordList = (current, size, params = {}) => {
|
||||
return request({
|
||||
url: '/blade-system/record-data/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
current,
|
||||
size,
|
||||
...params,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 获取数据审计表详情
|
||||
export const getRecordDetail = id => {
|
||||
return request({
|
||||
url: '/blade-system/record-data/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 新增数据审计表记录
|
||||
export const addRecord = data => {
|
||||
return request({
|
||||
url: '/blade-system/record-data/save',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
// 修改数据审计表记录
|
||||
export const updateRecord = data => {
|
||||
return request({
|
||||
url: '/blade-system/record-data/update',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
// 新增或修改数据审计表记录
|
||||
export const submitRecord = data => {
|
||||
return request({
|
||||
url: '/blade-system/record-data/submit',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
// 删除数据审计表记录
|
||||
export const removeRecord = ids => {
|
||||
return request({
|
||||
url: '/blade-system/record-data/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
59
src/api/desk/notice.js
Normal file
59
src/api/desk/notice.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-desk/notice/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
cryptoToken: false,
|
||||
cryptoData: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-desk/notice/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
cryptoToken: false,
|
||||
cryptoData: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-desk/notice/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
cryptoToken: false,
|
||||
cryptoData: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-desk/notice/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
cryptoToken: false,
|
||||
cryptoData: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const getNotice = id => {
|
||||
return request({
|
||||
url: '/blade-desk/notice/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
cryptoToken: false,
|
||||
cryptoData: false,
|
||||
});
|
||||
};
|
||||
122
src/api/flow/flow.js
Normal file
122
src/api/flow/flow.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const modelList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-flow/model/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const managerList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-flow/manager/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const followList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-flow/follow/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const removeModel = ids => {
|
||||
return request({
|
||||
url: '/blade-flow/model/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const deployModel = params => {
|
||||
return request({
|
||||
url: '/blade-flow/model/deploy',
|
||||
method: 'post',
|
||||
params,
|
||||
});
|
||||
};
|
||||
|
||||
export const changeState = params => {
|
||||
return request({
|
||||
url: '/blade-flow/manager/change-state',
|
||||
method: 'post',
|
||||
params,
|
||||
});
|
||||
};
|
||||
|
||||
export const deployUpload = (category, tenantIds, files) => {
|
||||
const formData = new FormData();
|
||||
formData.append('category', category);
|
||||
formData.append('tenantIds', tenantIds);
|
||||
files.forEach(file => {
|
||||
formData.append('files', file);
|
||||
});
|
||||
return request({
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
url: '/blade-flow/manager/deploy-upload',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteDeployment = deploymentIds => {
|
||||
return request({
|
||||
url: '/blade-flow/manager/delete-deployment',
|
||||
method: 'post',
|
||||
params: {
|
||||
deploymentIds,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteProcessInstance = params => {
|
||||
return request({
|
||||
url: '/blade-flow/follow/delete-process-instance',
|
||||
method: 'post',
|
||||
params,
|
||||
});
|
||||
};
|
||||
|
||||
export const submitModel = data => {
|
||||
return request({
|
||||
url: '/blade-flow/model/submit',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
export const detail = params => {
|
||||
return request({
|
||||
url: '/blade-flow/model/detail',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
};
|
||||
|
||||
export const modelView = params => {
|
||||
return request({
|
||||
url: '/blade-flow/process/model-view',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
};
|
||||
78
src/api/job/jobinfo.js
Normal file
78
src/api/job/jobinfo.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-job/job-info/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-job/job-info/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-job/job-info/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-job/job-info/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-job/job-info/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const change = row => {
|
||||
return request({
|
||||
url: '/blade-job/job-info/change',
|
||||
method: 'post',
|
||||
params: {
|
||||
id: row.id,
|
||||
enable: row.enable,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const run = row => {
|
||||
return request({
|
||||
url: '/blade-job/job-info/run',
|
||||
method: 'post',
|
||||
params: {
|
||||
id: row.id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const sync = row => {
|
||||
return request({
|
||||
url: '/blade-job/job-info/sync',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
57
src/api/job/jobserver.js
Normal file
57
src/api/job/jobserver.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-job/job-server/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-job/job-server/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-job/job-server/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-job/job-server/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-job/job-server/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const sync = row => {
|
||||
return request({
|
||||
url: '/blade-job/job-server/sync',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
62
src/api/logs.js
Normal file
62
src/api/logs.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getUsualList = (current, size) => {
|
||||
return request({
|
||||
url: '/blade-log/usual/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getApiList = (current, size) => {
|
||||
return request({
|
||||
url: '/blade-log/api/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getErrorList = (current, size) => {
|
||||
return request({
|
||||
url: '/blade-log/error/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getUsualLogs = id => {
|
||||
return request({
|
||||
url: '/blade-log/usual/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
export const getApiLogs = id => {
|
||||
return request({
|
||||
url: '/blade-log/api/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
export const getErrorLogs = id => {
|
||||
return request({
|
||||
url: '/blade-log/error/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
22
src/api/report/report.js
Normal file
22
src/api/report/report.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-report/report/rest/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-report/report/rest/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
49
src/api/resource/attach.js
Normal file
49
src/api/resource/attach.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-resource/attach/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-resource/attach/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-resource/attach/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-resource/attach/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-resource/attach/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
59
src/api/resource/oss.js
Normal file
59
src/api/resource/oss.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-resource/oss/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-resource/oss/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-resource/oss/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-resource/oss/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-resource/oss/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const enable = id => {
|
||||
return request({
|
||||
url: '/blade-resource/oss/enable',
|
||||
method: 'post',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
71
src/api/resource/sms.js
Normal file
71
src/api/resource/sms.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-resource/sms/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-resource/sms/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-resource/sms/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-resource/sms/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-resource/sms/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const enable = id => {
|
||||
return request({
|
||||
url: '/blade-resource/sms/enable',
|
||||
method: 'post',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const send = (code, phones, params) => {
|
||||
return request({
|
||||
url: '/blade-resource/sms/endpoint/send-message',
|
||||
method: 'post',
|
||||
params: {
|
||||
code,
|
||||
phones,
|
||||
params,
|
||||
},
|
||||
});
|
||||
};
|
||||
77
src/api/system/apikey.js
Normal file
77
src/api/system/apikey.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/api-key/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-system/api-key/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-system/api-key/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-system/api-key/save',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-system/api-key/update',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const submit = row => {
|
||||
return request({
|
||||
url: '/blade-system/api-key/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const enable = ids => {
|
||||
return request({
|
||||
url: '/blade-system/api-key/enable',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const disable = ids => {
|
||||
return request({
|
||||
url: '/blade-system/api-key/disable',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
23
src/api/system/apikeylog.js
Normal file
23
src/api/system/apikeylog.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/api-key-log/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-system/api-key-log/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
45
src/api/system/authlock.js
Normal file
45
src/api/system/authlock.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getAuthLockPage = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/auth-lock/page',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getAuthLockDetail = id => {
|
||||
return request({
|
||||
url: '/blade-system/auth-lock/detail',
|
||||
method: 'get',
|
||||
params: { id },
|
||||
});
|
||||
};
|
||||
|
||||
export const authLockUnlock = (id, unlockReason) => {
|
||||
return request({
|
||||
url: '/blade-system/auth-lock/unlock',
|
||||
method: 'post',
|
||||
params: {
|
||||
id,
|
||||
unlockReason,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const lockUser = (userId, lockReason, lockBeginTime, lockEndTime) => {
|
||||
return request({
|
||||
url: '/blade-system/auth-lock/lock',
|
||||
method: 'post',
|
||||
params: {
|
||||
userId,
|
||||
lockReason,
|
||||
lockBeginTime,
|
||||
lockEndTime,
|
||||
},
|
||||
});
|
||||
};
|
||||
21
src/api/system/authlog.js
Normal file
21
src/api/system/authlog.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getAuthLogPage = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/auth-log/page',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getAuthLogDetail = id => {
|
||||
return request({
|
||||
url: '/blade-system/auth-log/detail',
|
||||
method: 'get',
|
||||
params: { id },
|
||||
});
|
||||
};
|
||||
49
src/api/system/client.js
Normal file
49
src/api/system/client.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/client/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-system/client/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-system/client/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-system/client/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-system/client/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
80
src/api/system/dept.js
Normal file
80
src/api/system/dept.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/dept/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getLazyList = (parentId, params) => {
|
||||
return request({
|
||||
url: '/blade-system/dept/lazy-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
parentId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-system/dept/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-system/dept/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-system/dept/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const getDept = id => {
|
||||
return request({
|
||||
url: '/blade-system/dept/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeptTree = tenantId => {
|
||||
return request({
|
||||
url: '/blade-system/dept/tree',
|
||||
method: 'get',
|
||||
params: {
|
||||
tenantId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeptLazyTree = parentId => {
|
||||
return request({
|
||||
url: '/blade-system/dept/lazy-tree',
|
||||
method: 'get',
|
||||
params: {
|
||||
parentId,
|
||||
},
|
||||
});
|
||||
};
|
||||
88
src/api/system/dict.js
Normal file
88
src/api/system/dict.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/dict/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getParentList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/dict/parent-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getChildList = (current, size, parentId, params) => {
|
||||
return request({
|
||||
url: '/blade-system/dict/child-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
parentId: parentId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-system/dict/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-system/dict/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-system/dict/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const getDict = id => {
|
||||
return request({
|
||||
url: '/blade-system/dict/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
export const getDictTree = () => {
|
||||
return request({
|
||||
url: '/blade-system/dict/tree?code=DICT',
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
export const getDictionary = params => {
|
||||
return request({
|
||||
url: '/blade-system/dict/dictionary',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
};
|
||||
88
src/api/system/dictbiz.js
Normal file
88
src/api/system/dictbiz.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/dict-biz/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getParentList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/dict-biz/parent-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getChildList = (current, size, parentId, params) => {
|
||||
return request({
|
||||
url: '/blade-system/dict-biz/child-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
parentId: parentId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-system/dict-biz/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-system/dict-biz/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-system/dict-biz/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const getDict = id => {
|
||||
return request({
|
||||
url: '/blade-system/dict-biz/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
export const getDictTree = () => {
|
||||
return request({
|
||||
url: '/blade-system/dict-biz/tree?code=DICT',
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
export const getDictionary = params => {
|
||||
return request({
|
||||
url: '/blade-system/dict-biz/dictionary',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
};
|
||||
114
src/api/system/menu.js
Normal file
114
src/api/system/menu.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/menu/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getLazyList = (parentId, params) => {
|
||||
return request({
|
||||
url: '/blade-system/menu/lazy-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
parentId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getLazyMenuList = (parentId, params) => {
|
||||
return request({
|
||||
url: '/blade-system/menu/lazy-menu-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
parentId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getMenuList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/menu/menu-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getMenuTree = tenantId => {
|
||||
return request({
|
||||
url: '/blade-system/menu/tree',
|
||||
method: 'get',
|
||||
params: {
|
||||
tenantId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-system/menu/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-system/menu/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-system/menu/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const getMenu = id => {
|
||||
return request({
|
||||
url: '/blade-system/menu/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getTopMenu = () =>
|
||||
request({
|
||||
url: '/blade-system/menu/top-menu',
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
export const getRoutes = topMenuId =>
|
||||
request({
|
||||
url: '/blade-system/menu/routes',
|
||||
method: 'get',
|
||||
params: {
|
||||
topMenuId,
|
||||
},
|
||||
});
|
||||
|
||||
export const getMainMenu = () =>
|
||||
request({
|
||||
url: '/blade-system/menu/main-menu',
|
||||
method: 'get',
|
||||
});
|
||||
49
src/api/system/param.js
Normal file
49
src/api/system/param.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/param/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-system/param/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-system/param/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-system/param/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-system/param/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
59
src/api/system/post.js
Normal file
59
src/api/system/post.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/post/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getPostList = tenantId => {
|
||||
return request({
|
||||
url: '/blade-system/post/select',
|
||||
method: 'get',
|
||||
params: {
|
||||
tenantId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-system/post/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-system/post/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-system/post/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-system/post/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
77
src/api/system/ratelimit.js
Normal file
77
src/api/system/ratelimit.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/rate-limit/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-system/rate-limit/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-system/rate-limit/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-system/rate-limit/save',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-system/rate-limit/update',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const submit = row => {
|
||||
return request({
|
||||
url: '/blade-system/rate-limit/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const enable = ids => {
|
||||
return request({
|
||||
url: '/blade-system/rate-limit/enable',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const disable = ids => {
|
||||
return request({
|
||||
url: '/blade-system/rate-limit/disable',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
23
src/api/system/ratelimitlog.js
Normal file
23
src/api/system/ratelimitlog.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/rate-limit-log/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-system/rate-limit-log/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
96
src/api/system/role.js
Normal file
96
src/api/system/role.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/role/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
export const grantTree = () => {
|
||||
return request({
|
||||
url: '/blade-system/menu/grant-tree',
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
export const grant = (roleIds, menuIds, dataScopeIds, apiScopeIds) => {
|
||||
return request({
|
||||
url: '/blade-system/role/grant',
|
||||
method: 'post',
|
||||
data: {
|
||||
roleIds,
|
||||
menuIds,
|
||||
dataScopeIds,
|
||||
apiScopeIds,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-system/role/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-system/role/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-system/role/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const getRole = roleIds => {
|
||||
return request({
|
||||
url: '/blade-system/menu/role-tree-keys',
|
||||
method: 'get',
|
||||
params: {
|
||||
roleIds,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getRoleTree = tenantId => {
|
||||
return request({
|
||||
url: '/blade-system/role/tree',
|
||||
method: 'get',
|
||||
params: {
|
||||
tenantId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getRoleTreeById = roleId => {
|
||||
return request({
|
||||
url: '/blade-system/role/tree-by-id',
|
||||
method: 'get',
|
||||
params: {
|
||||
roleId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getRoleAlias = () => {
|
||||
return request({
|
||||
url: '/blade-system/role/alias',
|
||||
method: 'get',
|
||||
params: {},
|
||||
});
|
||||
};
|
||||
97
src/api/system/scope.js
Normal file
97
src/api/system/scope.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getListDataScope = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/data-scope/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const removeDataScope = ids => {
|
||||
return request({
|
||||
url: '/blade-system/data-scope/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const addDataScope = row => {
|
||||
return request({
|
||||
url: '/blade-system/data-scope/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateDataScope = row => {
|
||||
return request({
|
||||
url: '/blade-system/data-scope/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const getMenuDataScope = id => {
|
||||
return request({
|
||||
url: '/blade-system/data-scope/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getListApiScope = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/api-scope/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const removeApiScope = ids => {
|
||||
return request({
|
||||
url: '/blade-system/api-scope/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const addApiScope = row => {
|
||||
return request({
|
||||
url: '/blade-system/api-scope/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateApiScope = row => {
|
||||
return request({
|
||||
url: '/blade-system/api-scope/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const getMenuApiScope = id => {
|
||||
return request({
|
||||
url: '/blade-system/api-scope/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
122
src/api/system/tenant.js
Normal file
122
src/api/system/tenant.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/tenant/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-system/tenant/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-system/tenant/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-system/tenant/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const setting = (ids, form) => {
|
||||
return request({
|
||||
url: '/blade-system/tenant/setting',
|
||||
method: 'post',
|
||||
params: {
|
||||
...form,
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const datasource = (tenantId, datasourceId) => {
|
||||
return request({
|
||||
url: '/blade-system/tenant/datasource',
|
||||
method: 'post',
|
||||
params: {
|
||||
tenantId,
|
||||
datasourceId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const info = domain => {
|
||||
return request({
|
||||
url: '/blade-system/tenant/info',
|
||||
method: 'get',
|
||||
params: {
|
||||
domain,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const packageInfo = tenantId => {
|
||||
return request({
|
||||
url: '/blade-system/tenant/package-detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
tenantId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const packageSetting = (tenantId, packageId) => {
|
||||
return request({
|
||||
url: '/blade-system/tenant/package-setting',
|
||||
method: 'post',
|
||||
params: {
|
||||
tenantId,
|
||||
packageId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const recycle = ids => {
|
||||
return request({
|
||||
url: '/blade-system/tenant/recycle',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const pass = ids => {
|
||||
return request({
|
||||
url: '/blade-system/tenant/pass',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-system/tenant/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
49
src/api/system/tenantdatasource.js
Normal file
49
src/api/system/tenantdatasource.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/tenant-datasource/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-system/tenant-datasource/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-system/tenant-datasource/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-system/tenant-datasource/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-system/tenant-datasource/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
49
src/api/system/tenantpackage.js
Normal file
49
src/api/system/tenantpackage.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/tenant-package/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-system/tenant-package/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-system/tenant-package/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-system/tenant-package/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-system/tenant-package/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
87
src/api/system/topmenu.js
Normal file
87
src/api/system/topmenu.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-system/topmenu/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-system/topmenu/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-system/topmenu/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-system/topmenu/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-system/topmenu/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const grantTree = () => {
|
||||
return request({
|
||||
url: '/blade-system/menu/grant-top-tree',
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
export const getTopTree = topMenuIds => {
|
||||
return request({
|
||||
url: '/blade-system/menu/top-tree-keys',
|
||||
method: 'get',
|
||||
params: {
|
||||
topMenuIds,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const grant = (topMenuIds, menuIds) => {
|
||||
return request({
|
||||
url: '/blade-system/topmenu/grant',
|
||||
method: 'post',
|
||||
data: {
|
||||
topMenuIds,
|
||||
menuIds,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const enable = id => {
|
||||
return request({
|
||||
url: '/blade-system/topmenu/enable',
|
||||
method: 'post',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
175
src/api/system/user.js
Normal file
175
src/api/system/user.js
Normal file
@@ -0,0 +1,175 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params, deptId) => {
|
||||
return request({
|
||||
url: '/blade-system/user/page',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
deptId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-system/user/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-system/user/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-system/user/update',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const updatePlatform = (userId, userType, userExt) => {
|
||||
return request({
|
||||
url: '/blade-system/user/update-platform',
|
||||
method: 'post',
|
||||
params: {
|
||||
userId,
|
||||
userType,
|
||||
userExt,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getUser = id => {
|
||||
return request({
|
||||
url: '/blade-system/user/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getUserPlatform = id => {
|
||||
return request({
|
||||
url: '/blade-system/user/platform-detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getUserInfo = () => {
|
||||
return request({
|
||||
url: '/blade-system/user/info',
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
export const resetPassword = userIds => {
|
||||
return request({
|
||||
url: '/blade-system/user/reset-password',
|
||||
method: 'post',
|
||||
params: {
|
||||
userIds,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const updatePassword = (oldPassword, newPassword, newPassword1) => {
|
||||
return request({
|
||||
url: '/blade-system/user/update-password',
|
||||
method: 'post',
|
||||
params: {
|
||||
oldPassword,
|
||||
newPassword,
|
||||
newPassword1,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const updateInfo = row => {
|
||||
return request({
|
||||
url: '/blade-system/user/update-info',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const grant = (userIds, roleIds) => {
|
||||
return request({
|
||||
url: '/blade-system/user/grant',
|
||||
method: 'post',
|
||||
params: {
|
||||
userIds,
|
||||
roleIds,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const auditPass = userIds => {
|
||||
return request({
|
||||
url: '/blade-system/user/audit-pass',
|
||||
method: 'post',
|
||||
params: {
|
||||
userIds,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const auditRefuse = userIds => {
|
||||
return request({
|
||||
url: '/blade-system/user/audit-refuse',
|
||||
method: 'post',
|
||||
params: {
|
||||
userIds,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const setLeader = userId => {
|
||||
return request({
|
||||
url: '/blade-system/user/set-leader',
|
||||
method: 'post',
|
||||
params: {
|
||||
userId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getLeaderList = (tenantId, realName) => {
|
||||
return request({
|
||||
url: '/blade-system/user/leader-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
tenantId,
|
||||
realName,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getUserPage = (current, size, account, name) => {
|
||||
return request({
|
||||
url: '/blade-system/user/user-page',
|
||||
method: 'get',
|
||||
params: {
|
||||
current,
|
||||
size,
|
||||
account,
|
||||
name,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
78
src/api/tool/code.js
Normal file
78
src/api/tool/code.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-develop/code/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const build = ids => {
|
||||
return request({
|
||||
url: '/blade-develop/code/gen-code',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
system: 'saber',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const buildFast = form => {
|
||||
return request({
|
||||
url: '/blade-develop/code/gen-code-fast',
|
||||
method: 'post',
|
||||
data: form,
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-develop/code/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-develop/code/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-develop/code/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const copy = id => {
|
||||
return request({
|
||||
url: '/blade-develop/code/copy',
|
||||
method: 'post',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getCode = id => {
|
||||
return request({
|
||||
url: '/blade-develop/code/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
88
src/api/tool/codesetting.js
Normal file
88
src/api/tool/codesetting.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-develop/code-setting/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-develop/code-setting/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-develop/code-setting/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-develop/code-setting/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-develop/code-setting/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const enable = id => {
|
||||
return request({
|
||||
url: '/blade-develop/code-setting/enable',
|
||||
method: 'post',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getEnableDetail = () => {
|
||||
return request({
|
||||
url: '/blade-develop/code-setting/enable-detail',
|
||||
method: 'get',
|
||||
params: {},
|
||||
});
|
||||
};
|
||||
|
||||
export const getTableForm = tableName => {
|
||||
return request({
|
||||
url: '/blade-develop/code-setting/table-form',
|
||||
method: 'get',
|
||||
params: {
|
||||
tableName,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getTablePrototype = (tableName, datasourceId) => {
|
||||
return request({
|
||||
url: '/blade-develop/code-setting/table-prototype',
|
||||
method: 'get',
|
||||
params: {
|
||||
tableName,
|
||||
datasourceId,
|
||||
},
|
||||
});
|
||||
};
|
||||
49
src/api/tool/datasource.js
Normal file
49
src/api/tool/datasource.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-develop/datasource/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-develop/datasource/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-develop/datasource/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-develop/datasource/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-develop/datasource/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
110
src/api/tool/model.js
Normal file
110
src/api/tool/model.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const getList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-develop/model/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDetail = id => {
|
||||
return request({
|
||||
url: '/blade-develop/model/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = ids => {
|
||||
return request({
|
||||
url: '/blade-develop/model/remove',
|
||||
method: 'post',
|
||||
params: {
|
||||
ids,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const add = row => {
|
||||
return request({
|
||||
url: '/blade-develop/model/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const update = row => {
|
||||
return request({
|
||||
url: '/blade-develop/model/submit',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const getTableList = datasourceId => {
|
||||
return request({
|
||||
url: '/blade-develop/model/table-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
datasourceId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getTableInfo = (modelId, datasourceId) => {
|
||||
return request({
|
||||
url: '/blade-develop/model/table-info',
|
||||
method: 'get',
|
||||
params: {
|
||||
modelId,
|
||||
datasourceId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getTableInfoByName = (tableName, datasourceId) => {
|
||||
return request({
|
||||
url: '/blade-develop/model/table-info',
|
||||
method: 'get',
|
||||
params: {
|
||||
tableName,
|
||||
datasourceId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getModelPrototype = (modelId, datasourceId) => {
|
||||
return request({
|
||||
url: '/blade-develop/model/model-prototype',
|
||||
method: 'get',
|
||||
params: {
|
||||
modelId,
|
||||
datasourceId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const submitModelPrototype = row => {
|
||||
return request({
|
||||
url: '/blade-develop/model-prototype/submit-list',
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
};
|
||||
|
||||
export const prototypeDetail = modelId => {
|
||||
return request({
|
||||
url: '/blade-develop/model-prototype/select',
|
||||
method: 'get',
|
||||
params: {
|
||||
modelId,
|
||||
},
|
||||
});
|
||||
};
|
||||
196
src/api/user.js
Normal file
196
src/api/user.js
Normal file
@@ -0,0 +1,196 @@
|
||||
import request from '@/axios';
|
||||
|
||||
import website from '@/config/website';
|
||||
import func from '@/utils/func';
|
||||
|
||||
export const loginByUsername = (tenantId, deptId, roleId, username, password, type, key, code) =>
|
||||
request({
|
||||
url: '/blade-auth/oauth/token',
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Tenant-Id': tenantId,
|
||||
'Dept-Id': func.toStr(deptId),
|
||||
'Role-Id': func.toStr(roleId),
|
||||
'Captcha-Key': key,
|
||||
'Captcha-Code': code,
|
||||
},
|
||||
params: {
|
||||
tenantId,
|
||||
username,
|
||||
password,
|
||||
grant_type: website.captchaMode
|
||||
? website.captchaType === 'behavior'
|
||||
? 'behavior'
|
||||
: 'captcha'
|
||||
: 'password',
|
||||
scope: 'all',
|
||||
type,
|
||||
},
|
||||
});
|
||||
|
||||
export const loginBySocial = (tenantId, source, code, state) =>
|
||||
request({
|
||||
url: '/blade-auth/oauth/token',
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Tenant-Id': tenantId,
|
||||
},
|
||||
params: {
|
||||
tenantId,
|
||||
source,
|
||||
code,
|
||||
state,
|
||||
grant_type: 'social',
|
||||
scope: 'all',
|
||||
},
|
||||
});
|
||||
|
||||
export const loginBySso = (state, code) =>
|
||||
request({
|
||||
url: '/blade-auth/oauth/token',
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Tenant-Id': state,
|
||||
},
|
||||
params: {
|
||||
tenantId: state,
|
||||
code,
|
||||
grant_type: 'authorization_code',
|
||||
scope: 'all',
|
||||
redirect_uri: website.oauth2.redirectUri,
|
||||
},
|
||||
});
|
||||
|
||||
export const loginByPhone = (tenantId, phone, id, value) =>
|
||||
request({
|
||||
url: '/blade-auth/oauth/token',
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Tenant-Id': tenantId,
|
||||
},
|
||||
params: {
|
||||
tenantId,
|
||||
phone,
|
||||
id,
|
||||
value,
|
||||
grant_type: 'sms_code',
|
||||
scope: 'all',
|
||||
},
|
||||
});
|
||||
|
||||
export const refreshToken = (refresh_token, tenantId, deptId, roleId) =>
|
||||
request({
|
||||
url: '/blade-auth/oauth/token',
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Tenant-Id': tenantId,
|
||||
'Dept-Id': func.toStr(deptId),
|
||||
'Role-Id': func.toStr(roleId),
|
||||
},
|
||||
params: {
|
||||
tenantId,
|
||||
refresh_token,
|
||||
grant_type: 'refresh_token',
|
||||
scope: 'all',
|
||||
},
|
||||
});
|
||||
|
||||
export const registerUser = (tenantId, name, account, password, phone, email) =>
|
||||
request({
|
||||
url: '/blade-auth/oauth/token',
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Tenant-Id': tenantId,
|
||||
},
|
||||
params: {
|
||||
name,
|
||||
username: account,
|
||||
account,
|
||||
password,
|
||||
phone,
|
||||
email,
|
||||
grant_type: 'register',
|
||||
scope: 'all',
|
||||
},
|
||||
});
|
||||
|
||||
export const registerGuest = (form, oauthId) =>
|
||||
request({
|
||||
url: '/blade-system/user/register-guest',
|
||||
method: 'post',
|
||||
params: {
|
||||
tenantId: form.tenantId,
|
||||
name: form.name,
|
||||
account: form.account,
|
||||
password: form.password,
|
||||
oauthId,
|
||||
},
|
||||
});
|
||||
|
||||
export const getButtons = () =>
|
||||
request({
|
||||
url: '/blade-system/menu/buttons',
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
export const getCaptcha = () =>
|
||||
request({
|
||||
url: '/blade-auth/oauth/captcha',
|
||||
method: 'get',
|
||||
authorization: false,
|
||||
});
|
||||
|
||||
export const getBehavior = () =>
|
||||
request({
|
||||
url: '/blade-auth/oauth/behavior',
|
||||
method: 'get',
|
||||
authorization: false,
|
||||
});
|
||||
|
||||
export const checkBehavior = (key, answer) =>
|
||||
request({
|
||||
url: '/blade-auth/oauth/behavior/check',
|
||||
method: 'post',
|
||||
authorization: false,
|
||||
params: {
|
||||
key,
|
||||
answer,
|
||||
},
|
||||
});
|
||||
|
||||
export const logout = () =>
|
||||
request({
|
||||
url: '/blade-auth/oauth/logout',
|
||||
method: 'get',
|
||||
authorization: false,
|
||||
});
|
||||
|
||||
export const getUserInfo = () =>
|
||||
request({
|
||||
url: '/blade-auth/oauth/user-info',
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
export const sendLogs = list =>
|
||||
request({
|
||||
url: '/blade-auth/oauth/logout',
|
||||
method: 'post',
|
||||
data: list,
|
||||
});
|
||||
|
||||
export const clearCache = () =>
|
||||
request({
|
||||
url: '/blade-auth/oauth/clear-cache',
|
||||
method: 'get',
|
||||
authorization: false,
|
||||
});
|
||||
|
||||
export const sendSms = (tenantId, phone) =>
|
||||
request({
|
||||
url: '/blade-auth/oauth/sms/send-validate',
|
||||
method: 'post',
|
||||
params: {
|
||||
tenantId,
|
||||
phone,
|
||||
},
|
||||
});
|
||||
33
src/api/work/process.js
Normal file
33
src/api/work/process.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import request from '@/axios';
|
||||
|
||||
// =====================参数===========================
|
||||
|
||||
export const historyFlowList = processInstanceId => {
|
||||
return request({
|
||||
url: '/blade-flow/process/history-flow-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
processInstanceId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// =====================请假流程===========================
|
||||
|
||||
export const leaveProcess = data => {
|
||||
return request({
|
||||
url: '/blade-desk/process/leave/start-process',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
export const leaveDetail = businessId => {
|
||||
return request({
|
||||
url: '/blade-desk/process/leave/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
businessId,
|
||||
},
|
||||
});
|
||||
};
|
||||
79
src/api/work/work.js
Normal file
79
src/api/work/work.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import request from '@/axios';
|
||||
|
||||
export const startList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-flow/work/start-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const claimList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-flow/work/claim-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const todoList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-flow/work/todo-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const sendList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-flow/work/send-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const doneList = (current, size, params) => {
|
||||
return request({
|
||||
url: '/blade-flow/work/done-list',
|
||||
method: 'get',
|
||||
params: {
|
||||
...params,
|
||||
current,
|
||||
size,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const claimTask = taskId => {
|
||||
return request({
|
||||
url: '/blade-flow/work/claim-task',
|
||||
method: 'post',
|
||||
params: {
|
||||
taskId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const completeTask = data => {
|
||||
return request({
|
||||
url: '/blade-flow/work/complete-task',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
};
|
||||
226
src/axios.js
Normal file
226
src/axios.js
Normal file
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* 全站http配置
|
||||
*
|
||||
* axios参数说明
|
||||
* isSerialize是否开启form表单提交
|
||||
* isToken是否需要token
|
||||
*/
|
||||
import axios from 'axios';
|
||||
import store from '@/store/';
|
||||
import router from '@/router/';
|
||||
import { serialize } from '@/utils/util';
|
||||
import { getToken, getRefreshToken, removeToken, removeRefreshToken } from '@/utils/auth';
|
||||
import { isURL, validatenull } from '@/utils/validate';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import website from '@/config/website';
|
||||
import NProgress from 'nprogress'; // progress bar
|
||||
import 'nprogress/nprogress.css'; // progress bar style
|
||||
import { Base64 } from 'js-base64';
|
||||
import { baseUrl } from '@/config/env';
|
||||
import crypto from '@/utils/crypto';
|
||||
|
||||
// 刷新令牌授权类型,其自身失败由 401 拦截逻辑统一收口
|
||||
const REFRESH_GRANT_TYPE = 'refresh_token';
|
||||
// 会话失效后只提示一次并跳转登录,在重新登录前抑制所有重复的失效提示
|
||||
let isSessionExpired = false;
|
||||
// 标记当前是否正在刷新令牌,避免并发请求重复发起刷新
|
||||
let isRefreshing = false;
|
||||
// 共享的刷新令牌 Promise,令并发的失效请求复用同一次刷新结果
|
||||
let refreshTokenPromise = null;
|
||||
|
||||
// 判断是否为刷新令牌请求
|
||||
const isRefreshTokenRequest = config =>
|
||||
(config.url || '').includes('/oauth/token') &&
|
||||
(config.params || {}).grant_type === REFRESH_GRANT_TYPE;
|
||||
|
||||
// 判断是否为重新登录请求:会开启新的会话周期
|
||||
const isLoginRequest = config => {
|
||||
const grantType = (config.params || {}).grant_type;
|
||||
return (
|
||||
(config.url || '').includes('/oauth/token') && !!grantType && grantType !== REFRESH_GRANT_TYPE
|
||||
);
|
||||
};
|
||||
|
||||
// 为请求写入最新的访问令牌,供初始请求与刷新后的重放共用
|
||||
const applyToken = config => {
|
||||
const meta = config.meta || {};
|
||||
if (meta.isToken === false) return;
|
||||
const token = getToken();
|
||||
if (!token) return;
|
||||
config.headers[website.tokenHeader] =
|
||||
config.cryptoToken === true
|
||||
? 'crypto ' + crypto.encryptAES(token, crypto.cryptoKey)
|
||||
: 'bearer ' + token;
|
||||
};
|
||||
|
||||
// 会话失效统一出口:整段失效期内只提示一次,清理登录态并跳转登录页
|
||||
const redirectToLogin = () => {
|
||||
if (isSessionExpired) return;
|
||||
isSessionExpired = true;
|
||||
ElMessage({
|
||||
message: '登录状态已失效,请重新登录',
|
||||
type: 'error',
|
||||
});
|
||||
removeToken();
|
||||
removeRefreshToken();
|
||||
store.dispatch('FedLogOut').then(() => router.push({ path: '/login' }));
|
||||
};
|
||||
|
||||
axios.defaults.timeout = 10000;
|
||||
//返回其他状态码
|
||||
axios.defaults.validateStatus = function (status) {
|
||||
return status >= 200 && status <= 500; // 默认的
|
||||
};
|
||||
//跨域请求,允许保存cookie
|
||||
axios.defaults.withCredentials = true;
|
||||
// NProgress Configuration
|
||||
NProgress.configure({
|
||||
showSpinner: false,
|
||||
});
|
||||
|
||||
//http request拦截
|
||||
axios.interceptors.request.use(
|
||||
config => {
|
||||
// start progress bar
|
||||
NProgress.start();
|
||||
//地址为已经配置状态则不添加前缀
|
||||
if (!isURL(config.url) && !config.url.startsWith(baseUrl)) {
|
||||
config.url = baseUrl + config.url;
|
||||
}
|
||||
//安全请求header
|
||||
config.headers['Blade-Requested-With'] = 'BladeHttpRequest';
|
||||
//设置语言请求头
|
||||
config.headers['Accept-Language'] = store.getters.language || 'zh-CN';
|
||||
//headers判断是否需要
|
||||
const authorization = config.authorization === false;
|
||||
if (!authorization) {
|
||||
config.headers['Authorization'] = `Basic ${Base64.encode(
|
||||
`${website.clientId}:${website.clientSecret}`
|
||||
)}`;
|
||||
}
|
||||
//headers判断请求是否携带token
|
||||
const meta = config.meta || {};
|
||||
// 重新登录会开启新的会话周期,解除失效锁以便后续失效提示能再次触发
|
||||
if (isLoginRequest(config)) {
|
||||
isSessionExpired = false;
|
||||
}
|
||||
// 写入访问令牌
|
||||
applyToken(config);
|
||||
//判断传递数据是否加密
|
||||
const cryptoData = config.cryptoData === true;
|
||||
// 开启报文加密
|
||||
if (cryptoData) {
|
||||
if (config.params) {
|
||||
const data = crypto.encryptAES(JSON.stringify(config.params), crypto.aesKey);
|
||||
config.params = { data };
|
||||
}
|
||||
if (config.data) {
|
||||
config.text = true;
|
||||
config.data = crypto.encryptAES(JSON.stringify(config.data), crypto.aesKey);
|
||||
}
|
||||
}
|
||||
//headers中配置text请求
|
||||
if (config.text === true) {
|
||||
config.headers['Content-Type'] = 'text/plain';
|
||||
}
|
||||
//headers中配置serialize为true开启序列化
|
||||
if (config.method === 'post' && meta.isSerialize === true) {
|
||||
config.data = serialize(config.data);
|
||||
}
|
||||
return config;
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
//http response拦截
|
||||
axios.interceptors.response.use(
|
||||
res => {
|
||||
NProgress.done();
|
||||
//获取配置信息
|
||||
const config = res.config;
|
||||
const cryptoData = config.cryptoData === true;
|
||||
//解析加密报文
|
||||
if (cryptoData) {
|
||||
res.data = JSON.parse(crypto.decryptAES(res.data, crypto.aesKey));
|
||||
}
|
||||
//获取状态信息
|
||||
const status = res.data.error_code || res.data.code || res.status;
|
||||
const statusWhiteList = website.statusWhiteList || [];
|
||||
const message = res.data.msg || res.data.error_description || '系统错误';
|
||||
//如果在白名单里则自行catch逻辑处理
|
||||
if (statusWhiteList.includes(status)) return Promise.reject(res);
|
||||
|
||||
// 令牌失效且尚未重试:尝试刷新令牌后重放原请求
|
||||
if (status === 401 && !config._retry) {
|
||||
// 已登出(本地无令牌):残留请求的 401 属预期,后端已拒绝,前端静默忽略即可
|
||||
if (!getToken()) {
|
||||
return Promise.reject(new Error(message));
|
||||
}
|
||||
config._retry = true;
|
||||
|
||||
// 无刷新令牌时不再发起空刷新,直接进入失效流程
|
||||
if (validatenull(getRefreshToken())) {
|
||||
redirectToLogin();
|
||||
return Promise.reject(new Error(message));
|
||||
}
|
||||
|
||||
// 并发的失效请求复用同一次刷新,避免重复刷新与重复提示
|
||||
if (!isRefreshing) {
|
||||
isRefreshing = true;
|
||||
refreshTokenPromise = store
|
||||
.dispatch('RefreshToken')
|
||||
.catch(error => {
|
||||
redirectToLogin();
|
||||
return Promise.reject(error);
|
||||
})
|
||||
.finally(() => {
|
||||
isRefreshing = false;
|
||||
});
|
||||
}
|
||||
|
||||
return refreshTokenPromise.then(() => {
|
||||
applyToken(config);
|
||||
return axios(config);
|
||||
});
|
||||
}
|
||||
|
||||
// 刷新后仍然失效:会话已不可恢复,统一跳转登录
|
||||
if (status === 401 && config._retry) {
|
||||
redirectToLogin();
|
||||
return Promise.reject(new Error(message));
|
||||
}
|
||||
|
||||
// 刷新令牌请求:成功响应(200)须放行给刷新动作取用新令牌;仅其自身失败(error_code)时静默丢弃,
|
||||
// 交由上方 401 逻辑统一收口,避免与失效跳转重复提示
|
||||
if (isRefreshTokenRequest(config) && status !== 200) {
|
||||
return Promise.reject(new Error(message));
|
||||
}
|
||||
|
||||
// OAuth2 业务错误码提示
|
||||
if (status > 2000 && !validatenull(res.data.error_description)) {
|
||||
ElMessage({
|
||||
message: message,
|
||||
type: 'error',
|
||||
});
|
||||
return Promise.reject(new Error(message));
|
||||
}
|
||||
|
||||
// 其余非 200 响应统一提示
|
||||
if (status !== 200) {
|
||||
ElMessage({
|
||||
message: message,
|
||||
type: 'error',
|
||||
});
|
||||
return Promise.reject(new Error(message));
|
||||
}
|
||||
return res;
|
||||
},
|
||||
error => {
|
||||
NProgress.done();
|
||||
return Promise.reject(new Error(error));
|
||||
}
|
||||
);
|
||||
|
||||
export default axios;
|
||||
128
src/components/basic-block/main.vue
Normal file
128
src/components/basic-block/main.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<div class="basic-block" :style="styleName">
|
||||
<div class="box" :style="boxStyleName">
|
||||
<router-link :to="to">
|
||||
<span v-text="text"></span>
|
||||
<p v-text="dept"></p>
|
||||
<i :class="icon"></i>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'basicBlock',
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
},
|
||||
to: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
},
|
||||
dept: {
|
||||
type: String,
|
||||
},
|
||||
time: {
|
||||
type: [Number, String],
|
||||
},
|
||||
gutter: {
|
||||
type: [Number, String],
|
||||
default: 5,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
},
|
||||
width: {
|
||||
type: [Number, String],
|
||||
},
|
||||
height: {
|
||||
type: [Number, String],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
styleName() {
|
||||
return {
|
||||
animationDelay: `${this.time / 25}s`,
|
||||
width: `${this.width}px`,
|
||||
height: `${this.height}px`,
|
||||
margin: `${this.gutter}px`,
|
||||
};
|
||||
},
|
||||
boxStyleName() {
|
||||
return {
|
||||
backgroundColor: this.color,
|
||||
backgroundImage: `url('${this.background}')`,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.basic-block {
|
||||
opacity: 0;
|
||||
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
animation: mymove 1s;
|
||||
animation-fill-mode: forwards;
|
||||
|
||||
.box {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: all 1s;
|
||||
background-size: cover;
|
||||
|
||||
&:hover {
|
||||
transform: rotateY(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p {
|
||||
width: 80%;
|
||||
font-size: 10px;
|
||||
color: #eee;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
right: 15px;
|
||||
font-size: 50px !important;
|
||||
}
|
||||
|
||||
@keyframes mymove {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
57
src/components/basic-container/main.vue
Normal file
57
src/components/basic-container/main.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div class="basic-container" :style="styleName" :class="{ 'basic-container--block': block }">
|
||||
<el-card class="basic-container__card">
|
||||
<slot></slot>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'basicContainer',
|
||||
props: {
|
||||
radius: {
|
||||
type: [String, Number],
|
||||
default: 10,
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
},
|
||||
block: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
styleName() {
|
||||
return {
|
||||
borderRadius: `${this.radius}px`,
|
||||
background: this.background,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.basic-container {
|
||||
padding: 10px 6px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&--block {
|
||||
height: 100%;
|
||||
|
||||
.basic-container__card {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__card {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
140
src/components/basic-video/main.vue
Normal file
140
src/components/basic-video/main.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div :style="styleName" class="basic-video">
|
||||
<div class="basic-video__border">
|
||||
<span :style="borderStyleName"></span>
|
||||
<span :style="borderStyleName"></span>
|
||||
<span :style="borderStyleName"></span>
|
||||
<span :style="borderStyleName"></span>
|
||||
</div>
|
||||
<img :style="imgStyleName" class="basic-video__img" :src="background" />
|
||||
<video class="basic-video__main" ref="main" autoplay muted></video>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RecordVideo from './plugin';
|
||||
|
||||
export default {
|
||||
name: 'basic-video',
|
||||
props: {
|
||||
background: {
|
||||
type: String,
|
||||
},
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: 500,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
styleName() {
|
||||
return {
|
||||
width: `${this.width}px`,
|
||||
};
|
||||
},
|
||||
imgStyleName() {
|
||||
return {
|
||||
width: `${this.width / 2}px`,
|
||||
};
|
||||
},
|
||||
borderStyleName() {
|
||||
return {
|
||||
width: `${this.width / 15}px`,
|
||||
height: `${this.width / 15}px`,
|
||||
borderWidth: `${5}px`,
|
||||
};
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
videoObj: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.videoObj = new RecordVideo(this.$refs.main);
|
||||
const videoPromise = this.videoObj.init();
|
||||
videoPromise.then(() => {
|
||||
this.videoObj.mediaRecorder.addEventListener('stop', this.getData, false);
|
||||
});
|
||||
},
|
||||
startRecord() {
|
||||
this.videoObj.startRecord();
|
||||
},
|
||||
stopRecord() {
|
||||
this.videoObj.stopRecord();
|
||||
},
|
||||
getData() {
|
||||
const blob = new Blob(this.videoObj.chunks, {
|
||||
type: 'video/mp4',
|
||||
});
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(blob);
|
||||
reader.addEventListener('loadend', () => {
|
||||
var video_base64 = reader.result;
|
||||
this.$emit('data-change', video_base64);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.basic-video {
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&__border {
|
||||
span {
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-width: 4px;
|
||||
color: #0073eb;
|
||||
border-style: solid;
|
||||
|
||||
&:nth-child(1) {
|
||||
left: 15px;
|
||||
top: 15px;
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
right: 15px;
|
||||
top: 15px;
|
||||
border-left: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
bottom: 15px;
|
||||
left: 15px;
|
||||
border-right: 0;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
bottom: 15px;
|
||||
right: 15px;
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__img {
|
||||
width: 100px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
&__main {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
88
src/components/basic-video/plugin.js
Normal file
88
src/components/basic-video/plugin.js
Normal file
@@ -0,0 +1,88 @@
|
||||
export default class RecordVideo {
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param {Object} videoObj 视频对象
|
||||
*/
|
||||
constructor(videoObj) {
|
||||
this.video = videoObj;
|
||||
this.mediaRecorder = null;
|
||||
this.chunks = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*
|
||||
* @return {Object} promise
|
||||
*/
|
||||
init() {
|
||||
// 返回Promise对象
|
||||
// resolve 正常处理
|
||||
// reject 处理异常情况
|
||||
return new Promise((resovle, reject) => {
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({
|
||||
audio: true,
|
||||
video: true,
|
||||
// video: {
|
||||
// width: this.videoWidth,
|
||||
// height: this.videoHeight
|
||||
// }
|
||||
})
|
||||
// 返回一个媒体内容的流
|
||||
.then(stream => {
|
||||
// 检测是否支持 srcObject,该属性在新的浏览器支持
|
||||
if ('srcObject' in this.video) {
|
||||
this.video.srcObject = stream;
|
||||
} else {
|
||||
// 兼容旧的浏览器
|
||||
this.video.src = window.URL.createObjectURL(stream);
|
||||
}
|
||||
|
||||
// 当视频的元数据已经加载时触发
|
||||
this.video.addEventListener('loadmetadata', () => {
|
||||
this.video.play();
|
||||
});
|
||||
this.mediaRecorder = new MediaRecorder(stream);
|
||||
this.mediaRecorder.addEventListener('dataavailable', e => {
|
||||
this.chunks.push(e.data);
|
||||
});
|
||||
resovle();
|
||||
})
|
||||
// 异常抓取,包括用于禁用麦克风、摄像头
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频开始录制
|
||||
*/
|
||||
startRecord() {
|
||||
if (this.mediaRecorder.state === 'inactive') {
|
||||
this.mediaRecorder.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频结束录制
|
||||
*/
|
||||
stopRecord() {
|
||||
if (this.mediaRecorder.state === 'recording') {
|
||||
this.mediaRecorder.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测当前浏览器对否支持
|
||||
*
|
||||
* @return {boolean} 当前浏览器是否支持
|
||||
*/
|
||||
isSupport() {
|
||||
const flag = navigator.mediaDevices && navigator.mediaDevices.getUserMedia;
|
||||
if (flag) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
101
src/components/behavior-captcha/concat-panel.vue
Normal file
101
src/components/behavior-captcha/concat-panel.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="concat-panel">
|
||||
<div class="concat-stage" :style="stageSize">
|
||||
<div class="concat-top" :style="topStyle"></div>
|
||||
<div class="concat-bottom" :style="bottomStyle"></div>
|
||||
</div>
|
||||
<track-slider
|
||||
ref="slider"
|
||||
hint="按住滑块,对齐上下图案"
|
||||
:locked="locked"
|
||||
@drag="handleDrag"
|
||||
@release="handleRelease"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TrackSlider from './track-slider.vue';
|
||||
|
||||
export default {
|
||||
name: 'concatPanel',
|
||||
components: { TrackSlider },
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
locked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['confirm'],
|
||||
data() {
|
||||
return {
|
||||
moveX: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 舞台尺寸以服务端下发的逻辑尺寸为准,避免前后端各存一份常量产生偏差
|
||||
stageSize() {
|
||||
return { width: `${this.data.width}px`, height: `${this.data.height}px` };
|
||||
},
|
||||
topStyle() {
|
||||
return {
|
||||
height: `${this.data.sliceY}px`,
|
||||
backgroundImage: `url(${this.data.image})`,
|
||||
backgroundSize: `${this.data.width}px ${this.data.height}px`,
|
||||
backgroundPosition: '0 0',
|
||||
};
|
||||
},
|
||||
// 下条以背景横向循环平移实现推回复位,纵向偏移对准切缝以下的内容
|
||||
bottomStyle() {
|
||||
return {
|
||||
height: `${this.data.height - this.data.sliceY}px`,
|
||||
backgroundImage: `url(${this.data.image})`,
|
||||
backgroundSize: `${this.data.width}px ${this.data.height}px`,
|
||||
backgroundPosition: `${this.moveX}px -${this.data.sliceY}px`,
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 更换题目后下条回到初始错位状态
|
||||
data() {
|
||||
this.moveX = 0;
|
||||
this.$refs.slider?.reset();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 滑轨行程映射整幅画布宽度的循环位移
|
||||
handleDrag(progress) {
|
||||
this.moveX = Math.round(progress * this.data.width);
|
||||
},
|
||||
handleRelease() {
|
||||
this.$emit('confirm', String(this.moveX));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.concat-stage {
|
||||
position: relative;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
.concat-stage::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 12px;
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 24, 39, 0.08);
|
||||
pointer-events: none;
|
||||
}
|
||||
.concat-top,
|
||||
.concat-bottom {
|
||||
width: 100%;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
</style>
|
||||
528
src/components/behavior-captcha/index.vue
Normal file
528
src/components/behavior-captcha/index.vue
Normal file
@@ -0,0 +1,528 @@
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<transition name="behavior-fade">
|
||||
<div v-if="visible" class="behavior-overlay">
|
||||
<div class="behavior-panel" :class="{ 'is-shake': status === 'fail' }">
|
||||
<div class="behavior-header">
|
||||
<div class="behavior-badge">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="behavior-title">
|
||||
<span class="behavior-title-main">安全验证</span>
|
||||
<span class="behavior-title-sub">{{ subtitle }}</span>
|
||||
</div>
|
||||
<div class="behavior-actions">
|
||||
<button class="behavior-icon-btn" :class="{ 'is-spinning': status === 'loading' }" title="换一题" @click="load">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 12a9 9 0 1 1-2.64-6.36" />
|
||||
<path d="M21 3v6h-6" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="behavior-icon-btn" title="关闭" @click="close">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 6 6 18" />
|
||||
<path d="m6 6 12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="behavior-body">
|
||||
<transition name="behavior-swap" mode="out-in">
|
||||
<div v-if="status === 'loading'" key="skeleton" class="behavior-skeleton">
|
||||
<div class="behavior-skeleton-stage">
|
||||
<div class="behavior-skeleton-brand">
|
||||
<span class="behavior-skeleton-halo"></span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="behavior-skeleton-text">正在构建安全环境<i></i><i></i><i></i></span>
|
||||
</div>
|
||||
<div class="behavior-skeleton-bar"></div>
|
||||
</div>
|
||||
<div v-else key="panel" class="behavior-content">
|
||||
<word-panel v-if="type === 'word' || type === 'idiom'" :data="behaviorData" :locked="locked" @confirm="handleConfirm" />
|
||||
<puzzle-panel v-else-if="type === 'puzzle'" :data="behaviorData" :locked="locked" @confirm="handleConfirm" />
|
||||
<concat-panel v-else-if="type === 'concat'" :data="behaviorData" :locked="locked" @confirm="handleConfirm" />
|
||||
<rotate-panel v-else-if="type === 'rotate'" :data="behaviorData" :locked="locked" @confirm="handleConfirm" />
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<transition name="behavior-result">
|
||||
<div v-if="status === 'success'" class="behavior-success">
|
||||
<svg class="behavior-success-icon" viewBox="0 0 52 52">
|
||||
<circle class="behavior-success-circle" cx="26" cy="26" r="24" fill="none" />
|
||||
<path class="behavior-success-check" fill="none" d="M14 27l8 8 16-17" />
|
||||
</svg>
|
||||
<span class="behavior-success-text">验证通过</span>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<transition name="behavior-result">
|
||||
<div v-if="status === 'fail'" class="behavior-fail">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 8v4" />
|
||||
<path d="M12 16h.01" />
|
||||
</svg>
|
||||
<span>验证未通过,即将自动重试</span>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WordPanel from './word-panel.vue';
|
||||
import PuzzlePanel from './puzzle-panel.vue';
|
||||
import ConcatPanel from './concat-panel.vue';
|
||||
import RotatePanel from './rotate-panel.vue';
|
||||
import { getBehavior, checkBehavior } from '@/api/user';
|
||||
|
||||
const SUBTITLES = {
|
||||
word: '请按提示顺序点击图中文字',
|
||||
idiom: '请找出图中成语并按语序点击',
|
||||
puzzle: '请拖动滑块完成拼图',
|
||||
concat: '请拖动滑块对齐上下图案',
|
||||
rotate: '请拖动滑块将图案转至正确方向',
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'behaviorCaptcha',
|
||||
components: { WordPanel, PuzzlePanel, ConcatPanel, RotatePanel },
|
||||
emits: ['success'],
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
// 状态机: loading 出题中 / ready 待作答 / checking 核验中 / success 通过 / fail 未通过
|
||||
status: 'loading',
|
||||
key: '',
|
||||
type: '',
|
||||
behaviorData: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
subtitle() {
|
||||
return SUBTITLES[this.type] || '完成验证以继续登录';
|
||||
},
|
||||
locked() {
|
||||
return this.status !== 'ready';
|
||||
},
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.clearTimer();
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.visible = true;
|
||||
this.load();
|
||||
},
|
||||
close() {
|
||||
this.clearTimer();
|
||||
this.visible = false;
|
||||
this.status = 'loading';
|
||||
this.key = '';
|
||||
this.type = '';
|
||||
this.behaviorData = {};
|
||||
},
|
||||
load() {
|
||||
this.clearTimer();
|
||||
this.status = 'loading';
|
||||
getBehavior()
|
||||
.then(res => {
|
||||
const data = res.data;
|
||||
// 题面图预解码完成后再切换视图:出题响应到达时图片尚未完成首帧绘制,
|
||||
// 立即切换会让舞台区域先渲染为空白,由骨架屏覆盖整个等待期
|
||||
return this.preloadImages([data.data.image, data.data.piece, data.data.ring]).then(() => {
|
||||
this.key = data.key;
|
||||
this.behaviorData = data.data;
|
||||
this.type = data.type;
|
||||
this.status = 'ready';
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.close();
|
||||
});
|
||||
},
|
||||
preloadImages(sources) {
|
||||
const tasks = sources.filter(Boolean).map(
|
||||
source =>
|
||||
new Promise(resolve => {
|
||||
const image = new Image();
|
||||
image.onload = resolve;
|
||||
// 解码失败不阻塞流程,交由面板正常渲染兜底
|
||||
image.onerror = resolve;
|
||||
image.src = source;
|
||||
if (image.decode) {
|
||||
image.decode().then(resolve, resolve);
|
||||
}
|
||||
})
|
||||
);
|
||||
return Promise.all(tasks);
|
||||
},
|
||||
handleConfirm(answer) {
|
||||
if (this.status !== 'ready') return;
|
||||
this.status = 'checking';
|
||||
checkBehavior(this.key, answer).then(
|
||||
res => {
|
||||
this.status = 'success';
|
||||
const ticket = res.data.ticket;
|
||||
this.timer = setTimeout(() => {
|
||||
const key = this.key;
|
||||
this.close();
|
||||
this.$emit('success', { key, ticket });
|
||||
}, 700);
|
||||
},
|
||||
() => {
|
||||
// 题目已被服务端一次性消费,短暂展示失败反馈后自动更换题目
|
||||
this.status = 'fail';
|
||||
this.timer = setTimeout(() => this.load(), 900);
|
||||
}
|
||||
);
|
||||
},
|
||||
clearTimer() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.behavior-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(17, 24, 39, 0.4);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.behavior-panel {
|
||||
position: relative;
|
||||
/* 宽度跟随舞台内容自适应,保证题面图与提示条、滑轨结构性等宽对称 */
|
||||
width: fit-content;
|
||||
padding: 18px 20px 20px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.behavior-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.behavior-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, #6366f1, #3b82f6);
|
||||
box-shadow: 0 4px 10px rgba(59, 130, 246, 0.3);
|
||||
color: #ffffff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.behavior-badge svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
.behavior-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 10px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.behavior-title-main {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.behavior-title-sub {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.behavior-actions {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
.behavior-icon-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
.behavior-icon-btn svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.behavior-icon-btn:hover {
|
||||
background: #f3f4f6;
|
||||
color: #111827;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.behavior-icon-btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
.behavior-icon-btn.is-spinning svg {
|
||||
animation: behavior-spin 0.8s linear infinite;
|
||||
}
|
||||
@keyframes behavior-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.behavior-body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 骨架加载态:品牌徽标呼吸 + 斜向扫光,尺寸与题面舞台一致避免交接跳动 */
|
||||
.behavior-skeleton-stage {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 14px;
|
||||
width: 340px;
|
||||
height: 220px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #f8f9fc, #f1f3f8);
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 24, 39, 0.04);
|
||||
overflow: hidden;
|
||||
}
|
||||
.behavior-skeleton-stage::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: -60%;
|
||||
width: 50%;
|
||||
background: linear-gradient(105deg, transparent, rgba(255, 255, 255, 0.75), transparent);
|
||||
transform: skewX(-16deg);
|
||||
animation: behavior-sweep 1.8s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
|
||||
}
|
||||
@keyframes behavior-sweep {
|
||||
to {
|
||||
left: 120%;
|
||||
}
|
||||
}
|
||||
.behavior-skeleton-brand {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border-radius: 13px;
|
||||
background: linear-gradient(135deg, #6366f1, #3b82f6);
|
||||
box-shadow: 0 6px 16px rgba(59, 130, 246, 0.28);
|
||||
color: #ffffff;
|
||||
}
|
||||
.behavior-skeleton-brand svg {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
.behavior-skeleton-halo {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 13px;
|
||||
box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.35);
|
||||
animation: behavior-pulse 1.8s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
|
||||
}
|
||||
@keyframes behavior-pulse {
|
||||
to {
|
||||
box-shadow: 0 0 0 16px rgba(99, 102, 241, 0);
|
||||
}
|
||||
}
|
||||
.behavior-skeleton-text {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.behavior-skeleton-text i {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
margin-left: 3px;
|
||||
border-radius: 50%;
|
||||
background: #9ca3af;
|
||||
animation: behavior-dot 1.2s ease-in-out infinite;
|
||||
}
|
||||
.behavior-skeleton-text i:nth-child(2) {
|
||||
animation-delay: 0.15s;
|
||||
}
|
||||
.behavior-skeleton-text i:nth-child(3) {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
@keyframes behavior-dot {
|
||||
0%,
|
||||
60%,
|
||||
100% {
|
||||
opacity: 0.25;
|
||||
}
|
||||
30% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.behavior-skeleton-bar {
|
||||
height: 40px;
|
||||
margin-top: 12px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: behavior-shimmer 1.4s ease-in-out infinite;
|
||||
}
|
||||
@keyframes behavior-shimmer {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 骨架与题面的交接过渡 */
|
||||
.behavior-swap-enter-active {
|
||||
transition: opacity 0.25s cubic-bezier(0.2, 0.8, 0.2, 1), transform 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
.behavior-swap-leave-active {
|
||||
transition: opacity 0.15s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
.behavior-swap-enter-from {
|
||||
opacity: 0;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
.behavior-swap-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 成功遮罩 */
|
||||
.behavior-success {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
.behavior-success-icon {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
stroke: #10b981;
|
||||
stroke-width: 3;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
.behavior-success-circle {
|
||||
stroke-dasharray: 151;
|
||||
stroke-dashoffset: 151;
|
||||
animation: behavior-stroke 0.45s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
|
||||
}
|
||||
.behavior-success-check {
|
||||
stroke-dasharray: 40;
|
||||
stroke-dashoffset: 40;
|
||||
animation: behavior-stroke 0.3s cubic-bezier(0.2, 0.8, 0.2, 1) 0.35s forwards;
|
||||
}
|
||||
@keyframes behavior-stroke {
|
||||
to {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
.behavior-success-text {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
/* 失败提示条 */
|
||||
.behavior-fail {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding: 9px 12px;
|
||||
border-radius: 8px;
|
||||
background: #fef2f2;
|
||||
color: #ef4444;
|
||||
font-size: 12px;
|
||||
}
|
||||
.behavior-fail svg {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 面板抖动反馈 */
|
||||
.is-shake {
|
||||
animation: behavior-shake 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
@keyframes behavior-shake {
|
||||
20% {
|
||||
transform: translateX(-6px);
|
||||
}
|
||||
40% {
|
||||
transform: translateX(6px);
|
||||
}
|
||||
60% {
|
||||
transform: translateX(-4px);
|
||||
}
|
||||
80% {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 弹窗过渡 */
|
||||
.behavior-fade-enter-active,
|
||||
.behavior-fade-leave-active {
|
||||
transition: opacity 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
.behavior-fade-enter-active .behavior-panel,
|
||||
.behavior-fade-leave-active .behavior-panel {
|
||||
transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
.behavior-fade-enter-from,
|
||||
.behavior-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.behavior-fade-enter-from .behavior-panel,
|
||||
.behavior-fade-leave-to .behavior-panel {
|
||||
transform: scale(0.96) translateY(8px);
|
||||
}
|
||||
.behavior-result-enter-active,
|
||||
.behavior-result-leave-active {
|
||||
transition: opacity 0.15s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
.behavior-result-enter-from,
|
||||
.behavior-result-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
100
src/components/behavior-captcha/puzzle-panel.vue
Normal file
100
src/components/behavior-captcha/puzzle-panel.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="puzzle-panel">
|
||||
<div class="puzzle-stage" :style="stageSize">
|
||||
<img class="puzzle-image" :src="data.image" alt="" draggable="false" />
|
||||
<img
|
||||
class="puzzle-piece"
|
||||
:src="data.piece"
|
||||
alt=""
|
||||
draggable="false"
|
||||
:style="{
|
||||
left: `${pieceX}px`,
|
||||
top: `${data.pieceY}px`,
|
||||
width: `${data.pieceSize}px`,
|
||||
height: `${data.pieceSize}px`,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<track-slider
|
||||
ref="slider"
|
||||
hint="按住滑块,拖动完成拼图"
|
||||
:locked="locked"
|
||||
@drag="handleDrag"
|
||||
@release="handleRelease"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TrackSlider from './track-slider.vue';
|
||||
|
||||
export default {
|
||||
name: 'puzzlePanel',
|
||||
components: { TrackSlider },
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
locked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['confirm'],
|
||||
data() {
|
||||
return {
|
||||
pieceX: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 舞台尺寸以服务端下发的逻辑尺寸为准,避免前后端各存一份常量产生偏差
|
||||
stageSize() {
|
||||
return { width: `${this.data.width}px`, height: `${this.data.height}px` };
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 更换题目后拼图块回到起点
|
||||
data() {
|
||||
this.pieceX = 0;
|
||||
this.$refs.slider?.reset();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 滑轨行程与拼图块行程等比映射,保证滑到尽头时拼图块恰好到达画布右缘
|
||||
handleDrag(progress) {
|
||||
this.pieceX = Math.round(progress * (this.data.width - this.data.pieceSize));
|
||||
},
|
||||
handleRelease() {
|
||||
this.$emit('confirm', String(this.pieceX));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.puzzle-stage {
|
||||
position: relative;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
.puzzle-stage::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 12px;
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 24, 39, 0.08);
|
||||
pointer-events: none;
|
||||
}
|
||||
.puzzle-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.puzzle-piece {
|
||||
position: absolute;
|
||||
filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.35));
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
101
src/components/behavior-captcha/rotate-panel.vue
Normal file
101
src/components/behavior-captcha/rotate-panel.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="rotate-panel">
|
||||
<div class="rotate-stage" :style="stageSize">
|
||||
<img class="rotate-image" :src="data.image" alt="" draggable="false" />
|
||||
<img
|
||||
class="rotate-ring"
|
||||
:src="data.ring"
|
||||
alt=""
|
||||
draggable="false"
|
||||
:style="{
|
||||
width: `${data.ringSize}px`,
|
||||
height: `${data.ringSize}px`,
|
||||
transform: `translate(-50%, -50%) rotate(${angle}deg)`,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<track-slider
|
||||
ref="slider"
|
||||
hint="按住滑块,转动图案至正确方向"
|
||||
:locked="locked"
|
||||
@drag="handleDrag"
|
||||
@release="handleRelease"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TrackSlider from './track-slider.vue';
|
||||
|
||||
export default {
|
||||
name: 'rotatePanel',
|
||||
components: { TrackSlider },
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
locked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['confirm'],
|
||||
data() {
|
||||
return {
|
||||
angle: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 舞台尺寸以服务端下发的逻辑尺寸为准,避免前后端各存一份常量产生偏差
|
||||
stageSize() {
|
||||
return { width: `${this.data.width}px`, height: `${this.data.height}px` };
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 更换题目后内圆回到初始方向
|
||||
data() {
|
||||
this.angle = 0;
|
||||
this.$refs.slider?.reset();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 滑轨行程映射整圆角度
|
||||
handleDrag(progress) {
|
||||
this.angle = Math.round(progress * 360);
|
||||
},
|
||||
handleRelease() {
|
||||
this.$emit('confirm', String(this.angle));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rotate-stage {
|
||||
position: relative;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
.rotate-stage::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 12px;
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 24, 39, 0.08);
|
||||
pointer-events: none;
|
||||
}
|
||||
.rotate-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.rotate-ring {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
border-radius: 50%;
|
||||
will-change: transform;
|
||||
}
|
||||
</style>
|
||||
136
src/components/behavior-captcha/track-slider.vue
Normal file
136
src/components/behavior-captcha/track-slider.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div ref="track" class="track-slider" :class="{ 'is-dragging': dragging }">
|
||||
<div class="track-fill" :style="{ width: `${handleX + handleSize / 2}px` }"></div>
|
||||
<span v-show="!moved" class="track-hint">{{ hint }}</span>
|
||||
<div
|
||||
class="track-handle"
|
||||
:style="{ transform: `translateX(${handleX}px)` }"
|
||||
@pointerdown="handleDown"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M5 12h14" />
|
||||
<path d="m13 6 6 6-6 6" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const HANDLE_SIZE = 40;
|
||||
|
||||
export default {
|
||||
name: 'trackSlider',
|
||||
props: {
|
||||
hint: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
locked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['drag', 'release'],
|
||||
data() {
|
||||
return {
|
||||
handleSize: HANDLE_SIZE,
|
||||
handleX: 0,
|
||||
dragging: false,
|
||||
moved: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleDown(event) {
|
||||
if (this.locked) return;
|
||||
this.dragging = true;
|
||||
this.startX = event.clientX;
|
||||
this.baseX = this.handleX;
|
||||
this.handleEl = event.currentTarget;
|
||||
this.handleEl.setPointerCapture(event.pointerId);
|
||||
this.handleEl.addEventListener('pointermove', this.handleMove);
|
||||
this.handleEl.addEventListener('pointerup', this.handleUp, { once: true });
|
||||
},
|
||||
handleMove(event) {
|
||||
if (!this.dragging) return;
|
||||
const maxX = this.$refs.track.clientWidth - HANDLE_SIZE;
|
||||
this.handleX = Math.min(Math.max(this.baseX + event.clientX - this.startX, 0), maxX);
|
||||
this.moved = this.moved || this.handleX > 0;
|
||||
this.$emit('drag', maxX > 0 ? this.handleX / maxX : 0);
|
||||
},
|
||||
handleUp() {
|
||||
this.handleEl.removeEventListener('pointermove', this.handleMove);
|
||||
this.dragging = false;
|
||||
// 从未移动的误触不视为作答,避免白白消费一次题目
|
||||
if (!this.moved) return;
|
||||
const maxX = this.$refs.track.clientWidth - HANDLE_SIZE;
|
||||
this.$emit('release', maxX > 0 ? this.handleX / maxX : 0);
|
||||
},
|
||||
reset() {
|
||||
this.handleX = 0;
|
||||
this.dragging = false;
|
||||
this.moved = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.track-slider {
|
||||
position: relative;
|
||||
height: 40px;
|
||||
margin-top: 12px;
|
||||
border-radius: 10px;
|
||||
background: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.track-fill {
|
||||
position: absolute;
|
||||
inset: 0 auto 0 0;
|
||||
background: linear-gradient(90deg, rgba(99, 102, 241, 0.12), rgba(59, 130, 246, 0.18));
|
||||
transition: none;
|
||||
}
|
||||
.track-hint {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
pointer-events: none;
|
||||
}
|
||||
.track-handle {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06), 0 2px 6px rgba(0, 0, 0, 0.04);
|
||||
color: #6b7280;
|
||||
cursor: grab;
|
||||
transition: box-shadow 0.15s cubic-bezier(0.2, 0.8, 0.2, 1), background 0.15s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
.track-handle svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.track-handle:hover {
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15), 0 4px 16px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.is-dragging .track-handle {
|
||||
background: linear-gradient(135deg, #6366f1, #3b82f6);
|
||||
border-color: transparent;
|
||||
color: #ffffff;
|
||||
cursor: grabbing;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15), 0 4px 12px rgba(59, 130, 246, 0.35);
|
||||
}
|
||||
</style>
|
||||
235
src/components/behavior-captcha/word-panel.vue
Normal file
235
src/components/behavior-captcha/word-panel.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<div class="word-panel">
|
||||
<div class="word-stage" :style="stageSize" @click="handleStageClick">
|
||||
<img class="word-image" :src="data.image" alt="" draggable="false" />
|
||||
<transition-group name="word-dot">
|
||||
<span
|
||||
v-for="(dot, index) in dots"
|
||||
:key="dot.id"
|
||||
class="word-dot"
|
||||
:style="{ left: `${dot.x}px`, top: `${dot.y}px` }"
|
||||
>{{ index + 1 }}</span
|
||||
>
|
||||
</transition-group>
|
||||
</div>
|
||||
<div class="word-bar">
|
||||
<span class="word-bar-label">{{ data.prompt ? '依次点击' : '按语序点击' }}</span>
|
||||
<div class="word-chips">
|
||||
<span
|
||||
v-for="(chip, index) in chips"
|
||||
:key="index"
|
||||
class="word-chip"
|
||||
:class="{ 'is-active': index < dots.length, 'is-ordinal': !data.prompt }"
|
||||
>{{ chip }}</span
|
||||
>
|
||||
</div>
|
||||
<button class="word-undo" title="撤销上一步" :disabled="!dots.length" @click="undo">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M9 14 4 9l5-5" />
|
||||
<path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'wordPanel',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
locked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['confirm'],
|
||||
data() {
|
||||
return {
|
||||
dots: [],
|
||||
dotSeed: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 舞台尺寸以服务端下发的逻辑尺寸为准,避免前后端各存一份常量产生偏差
|
||||
stageSize() {
|
||||
return { width: `${this.data.width}px`, height: `${this.data.height}px` };
|
||||
},
|
||||
// 语序模式不下发目标字符,提示条降级为作答进度序号
|
||||
chips() {
|
||||
if (this.data.prompt) {
|
||||
return this.data.prompt;
|
||||
}
|
||||
return Array.from({ length: this.data.targetCount }, (item, index) => index + 1);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 更换题目后清空作答痕迹
|
||||
data() {
|
||||
this.dots = [];
|
||||
this.clearTimer();
|
||||
},
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.clearTimer();
|
||||
},
|
||||
methods: {
|
||||
handleStageClick(event) {
|
||||
if (this.locked || this.dots.length >= this.data.targetCount) return;
|
||||
// 以图片原始尺寸换算坐标,保证展示缩放时命中判定仍然准确
|
||||
const rect = event.currentTarget.getBoundingClientRect();
|
||||
const x = Math.round(((event.clientX - rect.left) / rect.width) * this.data.width);
|
||||
const y = Math.round(((event.clientY - rect.top) / rect.height) * this.data.height);
|
||||
this.dots.push({ x, y, id: ++this.dotSeed });
|
||||
if (this.dots.length === this.data.targetCount) {
|
||||
// 短暂停留让最后一个序号标记完成入场,再提交作答
|
||||
this.timer = setTimeout(() => {
|
||||
this.$emit('confirm', this.dots.map(dot => `${dot.x},${dot.y}`).join(';'));
|
||||
}, 300);
|
||||
}
|
||||
},
|
||||
undo() {
|
||||
if (this.locked) return;
|
||||
this.dots.pop();
|
||||
this.clearTimer();
|
||||
},
|
||||
clearTimer() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.word-stage {
|
||||
position: relative;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
cursor: crosshair;
|
||||
user-select: none;
|
||||
}
|
||||
.word-stage::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 12px;
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 24, 39, 0.08);
|
||||
pointer-events: none;
|
||||
}
|
||||
.word-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.word-dot {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: -12px 0 0 -12px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #6366f1, #3b82f6);
|
||||
border: 2px solid rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 4px 10px rgba(59, 130, 246, 0.35);
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
pointer-events: none;
|
||||
}
|
||||
.word-dot-enter-active {
|
||||
transition: all 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
.word-dot-enter-from {
|
||||
opacity: 0;
|
||||
transform: scale(0.3);
|
||||
}
|
||||
.word-dot-leave-active {
|
||||
transition: all 0.15s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
.word-dot-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.3);
|
||||
}
|
||||
|
||||
.word-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
margin-top: 12px;
|
||||
padding: 0 10px 0 12px;
|
||||
border-radius: 10px;
|
||||
background: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
.word-bar-label {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.word-chips {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
.word-chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 7px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
color: #374151;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
transition: all 0.15s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
.word-chip.is-ordinal {
|
||||
color: #9ca3af;
|
||||
font-size: 12px;
|
||||
}
|
||||
.word-chip.is-active {
|
||||
background: linear-gradient(135deg, #6366f1, #3b82f6);
|
||||
border-color: transparent;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.word-undo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 7px;
|
||||
background: transparent;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.15s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
.word-undo svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
.word-undo:hover:not(:disabled) {
|
||||
background: #f3f4f6;
|
||||
color: #111827;
|
||||
}
|
||||
.word-undo:disabled {
|
||||
opacity: 0.35;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
135
src/components/code-editor/main.vue
Normal file
135
src/components/code-editor/main.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="code-editor">
|
||||
<textarea ref="textarea" v-show="false"></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CodeMirror from 'codemirror';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
// 主题
|
||||
import 'codemirror/theme/idea.css';
|
||||
import 'codemirror/theme/nord.css';
|
||||
import 'codemirror/theme/xq-light.css';
|
||||
import 'codemirror/mode/clike/clike';
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
import 'codemirror/addon/display/autorefresh';
|
||||
// 搜索
|
||||
import 'codemirror/addon/scroll/annotatescrollbar.js';
|
||||
import 'codemirror/addon/search/matchesonscrollbar.js';
|
||||
import 'codemirror/addon/search/match-highlighter.js';
|
||||
import 'codemirror/addon/search/jump-to-line.js';
|
||||
import 'codemirror/addon/dialog/dialog.js';
|
||||
import 'codemirror/addon/dialog/dialog.css';
|
||||
import 'codemirror/addon/search/searchcursor.js';
|
||||
import 'codemirror/addon/search/search.js';
|
||||
// 折叠
|
||||
import 'codemirror/addon/fold/foldgutter.css';
|
||||
import 'codemirror/addon/fold/foldcode';
|
||||
import 'codemirror/addon/fold/foldgutter';
|
||||
import 'codemirror/addon/fold/brace-fold';
|
||||
import 'codemirror/addon/fold/comment-fold';
|
||||
// 格式化
|
||||
import formatter from '@/utils/formatter';
|
||||
import { validatejson, validatenull } from '@/utils/validate';
|
||||
|
||||
export default {
|
||||
name: 'CodeEditor',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: '',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: '450px',
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'javascript',
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: 'idea',
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
json: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.editor = CodeMirror.fromTextArea(this.$refs.textarea, {
|
||||
mode: this.mode, // 默认设置为 'javascript' 或根据需要配置
|
||||
theme: this.theme, // 直接设置主题或根据需要配置
|
||||
readOnly: this.readonly,
|
||||
autoRefresh: true,
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
tabSize: 2,
|
||||
foldGutter: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
|
||||
extraKeys: {
|
||||
// 绑定快捷键 Ctrl-F / Cmd-F 开启搜索
|
||||
'Ctrl-F': 'findPersistent',
|
||||
'Cmd-F': 'findPersistent',
|
||||
// 如果需要,您还可以添加 "Ctrl-R"/"Cmd-R" 绑定替换功能
|
||||
'Ctrl-R': 'replace',
|
||||
'Cmd-R': 'replace',
|
||||
},
|
||||
});
|
||||
|
||||
// 设置高度
|
||||
this.editor.setSize('auto', this.height);
|
||||
|
||||
// 设置文本
|
||||
const editorValue = this.json ? formatter.prettyCode(this.modelValue) : this.modelValue;
|
||||
this.editor.setValue(editorValue);
|
||||
|
||||
this.editor.on('change', () => {
|
||||
this.$emit('update:modelValue', this.editor.getValue()); // 更新 modelValue
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
modelValue(newVal) {
|
||||
if (newVal !== this.editor.getValue()) {
|
||||
const editorValue = this.json ? formatter.prettyCode(this.modelValue) : this.modelValue;
|
||||
this.editor.setValue(editorValue);
|
||||
}
|
||||
},
|
||||
height(newHeight) {
|
||||
this.editor.setSize('auto', newHeight);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
prettyCode() {
|
||||
if (this.json) {
|
||||
const val = this.editor.getValue();
|
||||
if (validatenull(val)) {
|
||||
this.$message.warning('请先填写数据');
|
||||
return;
|
||||
}
|
||||
if (!validatejson(val)) {
|
||||
this.$message.warning('数据 JSON 格式错误');
|
||||
return;
|
||||
}
|
||||
this.editor.setValue(formatter.prettyCode(val));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.code-editor {
|
||||
line-height: 1.2 !important;
|
||||
width: calc(100% - 4px);
|
||||
height: 100%;
|
||||
border: 1px solid #ccc; /* 添加边框样式 */
|
||||
}
|
||||
</style>
|
||||
597
src/components/cron-editor/main.vue
Normal file
597
src/components/cron-editor/main.vue
Normal file
@@ -0,0 +1,597 @@
|
||||
<template>
|
||||
<div class="cron-editor">
|
||||
<el-card shadow="never" class="cron-card">
|
||||
<div class="cron-mode-tabs">
|
||||
<el-radio-group v-model="cronMode" @change="handleModeChange" :disabled="disabled">
|
||||
<el-radio-button label="daily">每天</el-radio-button>
|
||||
<el-radio-button label="weekly">每周</el-radio-button>
|
||||
<el-radio-button label="monthly">每月</el-radio-button>
|
||||
<el-radio-button label="custom">自定义</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="cron-config-area">
|
||||
<!-- 每天模式 -->
|
||||
<div v-if="cronMode === 'daily'" class="mode-content">
|
||||
<el-form :model="dailyConfig" label-width="100px" size="default">
|
||||
<el-form-item label="执行时间">
|
||||
<el-time-picker
|
||||
v-model="dailyConfig.time"
|
||||
format="HH:mm:ss"
|
||||
value-format="HH:mm:ss"
|
||||
placeholder="选择时间"
|
||||
:disabled="disabled"
|
||||
@change="generateCron"
|
||||
/>
|
||||
<span class="tip-text">每天在指定时间执行</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 每周模式 -->
|
||||
<div v-if="cronMode === 'weekly'" class="mode-content">
|
||||
<el-form :model="weeklyConfig" label-width="100px" size="default">
|
||||
<el-form-item label="星期选择">
|
||||
<el-checkbox-group v-model="weeklyConfig.weekDays" @change="generateCron" :disabled="disabled">
|
||||
<el-checkbox label="1">周一</el-checkbox>
|
||||
<el-checkbox label="2">周二</el-checkbox>
|
||||
<el-checkbox label="3">周三</el-checkbox>
|
||||
<el-checkbox label="4">周四</el-checkbox>
|
||||
<el-checkbox label="5">周五</el-checkbox>
|
||||
<el-checkbox label="6">周六</el-checkbox>
|
||||
<el-checkbox label="7">周日</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="执行时间">
|
||||
<el-time-picker
|
||||
v-model="weeklyConfig.time"
|
||||
format="HH:mm:ss"
|
||||
value-format="HH:mm:ss"
|
||||
placeholder="选择时间"
|
||||
:disabled="disabled"
|
||||
@change="generateCron"
|
||||
/>
|
||||
<span class="tip-text">在选定的星期几执行</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 每月模式 -->
|
||||
<div v-if="cronMode === 'monthly'" class="mode-content">
|
||||
<el-form :model="monthlyConfig" label-width="100px" size="default">
|
||||
<el-form-item label="日期选择">
|
||||
<el-select
|
||||
v-model="monthlyConfig.days"
|
||||
multiple
|
||||
placeholder="选择日期"
|
||||
:disabled="disabled"
|
||||
@change="generateCron"
|
||||
style="width: 400px"
|
||||
>
|
||||
<el-option
|
||||
v-for="day in 31"
|
||||
:key="day"
|
||||
:label="`${day} 号`"
|
||||
:value="day"
|
||||
/>
|
||||
</el-select>
|
||||
<span class="tip-text">每月的哪几天</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="执行时间">
|
||||
<el-time-picker
|
||||
v-model="monthlyConfig.time"
|
||||
format="HH:mm:ss"
|
||||
value-format="HH:mm:ss"
|
||||
placeholder="选择时间"
|
||||
:disabled="disabled"
|
||||
@change="generateCron"
|
||||
/>
|
||||
<span class="tip-text">在选定的日期执行</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 自定义模式 -->
|
||||
<div v-if="cronMode === 'custom'" class="mode-content">
|
||||
<el-form :model="customConfig" label-width="100px" size="default">
|
||||
<el-form-item label="秒">
|
||||
<el-input
|
||||
v-model="customConfig.second"
|
||||
placeholder="0-59 或 * 或 */5"
|
||||
:disabled="disabled"
|
||||
@input="generateCron"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="分">
|
||||
<el-input
|
||||
v-model="customConfig.minute"
|
||||
placeholder="0-59 或 * 或 */10"
|
||||
:disabled="disabled"
|
||||
@input="generateCron"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="时">
|
||||
<el-input
|
||||
v-model="customConfig.hour"
|
||||
placeholder="0-23 或 * 或 8-18"
|
||||
:disabled="disabled"
|
||||
@input="generateCron"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="日">
|
||||
<el-input
|
||||
v-model="customConfig.day"
|
||||
placeholder="1-31 或 * 或 ?"
|
||||
:disabled="disabled"
|
||||
@input="generateCron"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="月">
|
||||
<el-input
|
||||
v-model="customConfig.month"
|
||||
placeholder="1-12 或 * 或 JAN-DEC"
|
||||
:disabled="disabled"
|
||||
@input="generateCron"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="周">
|
||||
<el-input
|
||||
v-model="customConfig.week"
|
||||
placeholder="0-7 或 * 或 ? 或 MON-SUN"
|
||||
:disabled="disabled"
|
||||
@input="generateCron"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="cron-help">
|
||||
<div class="cron-help-title">
|
||||
<el-icon class="help-icon"><InfoFilled /></el-icon>
|
||||
<span>Cron 表达式说明</span>
|
||||
</div>
|
||||
<ul class="cron-help-list">
|
||||
<li><strong>*</strong> <span>表示所有值</span></li>
|
||||
<li><strong>?</strong> <span>表示不指定值(仅日和周可用)</span></li>
|
||||
<li><strong>-</strong> <span>表示范围,如:8-18</span></li>
|
||||
<li><strong>,</strong> <span>表示列举,如:1,3,5</span></li>
|
||||
<li><strong>/</strong> <span>表示增量,如:*/5 表示每5个单位</span></li>
|
||||
<li><strong>周</strong> <span>1-7 或 MON-SUN(1=周一,7=周日)</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cron-result">
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="Cron 表达式">
|
||||
<el-input
|
||||
v-model="cronExpression"
|
||||
readonly
|
||||
placeholder="自动生成的 Cron 表达式"
|
||||
class="cron-expression-input"
|
||||
>
|
||||
<template #append>
|
||||
<el-button
|
||||
v-clipboard:copy="cronExpression"
|
||||
v-clipboard:success="onCopySuccess"
|
||||
v-clipboard:error="onCopyError">
|
||||
复制
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="执行说明" v-if="cronDescription" class="description-item">
|
||||
<el-tag type="primary" effect="plain" class="cron-description">{{ cronDescription }}</el-tag>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { InfoFilled } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
export default {
|
||||
name: 'CronEditor',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
InfoFilled,
|
||||
cronMode: 'daily',
|
||||
cronExpression: '',
|
||||
cronDescription: '',
|
||||
|
||||
// 每天配置
|
||||
dailyConfig: {
|
||||
time: '00:00:00'
|
||||
},
|
||||
|
||||
// 每周配置
|
||||
weeklyConfig: {
|
||||
weekDays: ['1'],
|
||||
time: '00:00:00'
|
||||
},
|
||||
|
||||
// 每月配置
|
||||
monthlyConfig: {
|
||||
days: [1],
|
||||
time: '00:00:00'
|
||||
},
|
||||
|
||||
// 自定义配置
|
||||
customConfig: {
|
||||
second: '0',
|
||||
minute: '0',
|
||||
hour: '0',
|
||||
day: '*',
|
||||
month: '*',
|
||||
week: '?'
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
modelValue: {
|
||||
handler(newVal) {
|
||||
if (newVal && newVal !== this.cronExpression) {
|
||||
this.parseCron(newVal);
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
cronExpression(newVal) {
|
||||
this.$emit('update:modelValue', newVal);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!this.modelValue) {
|
||||
this.generateCron();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 切换模式时重置配置
|
||||
*/
|
||||
handleModeChange() {
|
||||
this.generateCron();
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成 Cron 表达式
|
||||
*/
|
||||
generateCron() {
|
||||
let cron = '';
|
||||
let description = '';
|
||||
|
||||
switch (this.cronMode) {
|
||||
case 'daily':
|
||||
cron = this.generateDailyCron();
|
||||
description = `每天 ${this.dailyConfig.time} 执行`;
|
||||
break;
|
||||
case 'weekly':
|
||||
cron = this.generateWeeklyCron();
|
||||
description = this.getWeeklyDescription();
|
||||
break;
|
||||
case 'monthly':
|
||||
cron = this.generateMonthlyCron();
|
||||
description = this.getMonthlyDescription();
|
||||
break;
|
||||
case 'custom':
|
||||
cron = this.generateCustomCron();
|
||||
description = '自定义表达式';
|
||||
break;
|
||||
}
|
||||
|
||||
this.cronExpression = cron;
|
||||
this.cronDescription = description;
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成每天的 Cron 表达式
|
||||
*/
|
||||
generateDailyCron() {
|
||||
const time = this.dailyConfig.time || '00:00:00';
|
||||
const [hour, minute, second] = time.split(':');
|
||||
return `${second} ${minute} ${hour} * * ?`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成每周的 Cron 表达式
|
||||
*/
|
||||
generateWeeklyCron() {
|
||||
if (!this.weeklyConfig.weekDays || this.weeklyConfig.weekDays.length === 0) {
|
||||
return '0 0 0 ? * 1';
|
||||
}
|
||||
|
||||
const time = this.weeklyConfig.time || '00:00:00';
|
||||
const [hour, minute, second] = time.split(':');
|
||||
const weekDays = this.weeklyConfig.weekDays.sort((a, b) => a - b).join(',');
|
||||
|
||||
return `${second} ${minute} ${hour} ? * ${weekDays}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成每月的 Cron 表达式
|
||||
*/
|
||||
generateMonthlyCron() {
|
||||
if (!this.monthlyConfig.days || this.monthlyConfig.days.length === 0) {
|
||||
return '0 0 0 1 * ?';
|
||||
}
|
||||
|
||||
const time = this.monthlyConfig.time || '00:00:00';
|
||||
const [hour, minute, second] = time.split(':');
|
||||
const days = this.monthlyConfig.days.sort((a, b) => a - b).join(',');
|
||||
|
||||
return `${second} ${minute} ${hour} ${days} * ?`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成自定义的 Cron 表达式
|
||||
*/
|
||||
generateCustomCron() {
|
||||
const { second, minute, hour, day, month, week } = this.customConfig;
|
||||
return `${second || '0'} ${minute || '0'} ${hour || '0'} ${day || '*'} ${month || '*'} ${week || '?'}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取每周执行说明
|
||||
*/
|
||||
getWeeklyDescription() {
|
||||
const weekMap = {
|
||||
'1': '周一',
|
||||
'2': '周二',
|
||||
'3': '周三',
|
||||
'4': '周四',
|
||||
'5': '周五',
|
||||
'6': '周六',
|
||||
'7': '周日'
|
||||
};
|
||||
|
||||
const weekDays = this.weeklyConfig.weekDays.map(day => weekMap[day]).join('、');
|
||||
return `每周 ${weekDays} ${this.weeklyConfig.time} 执行`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取每月执行说明
|
||||
*/
|
||||
getMonthlyDescription() {
|
||||
const days = this.monthlyConfig.days.sort((a, b) => a - b).join('、');
|
||||
return `每月 ${days} 号 ${this.monthlyConfig.time} 执行`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 解析 Cron 表达式(反向解析,用于回显)
|
||||
*/
|
||||
parseCron(cron) {
|
||||
if (!cron) return;
|
||||
|
||||
const parts = cron.trim().split(/\s+/);
|
||||
if (parts.length !== 6) {
|
||||
// 如果不是标准6位表达式,设置为自定义模式
|
||||
this.cronMode = 'custom';
|
||||
this.customConfig = {
|
||||
second: parts[0] || '0',
|
||||
minute: parts[1] || '0',
|
||||
hour: parts[2] || '0',
|
||||
day: parts[3] || '*',
|
||||
month: parts[4] || '*',
|
||||
week: parts[5] || '?'
|
||||
};
|
||||
this.cronExpression = cron;
|
||||
return;
|
||||
}
|
||||
|
||||
const [second, minute, hour, day, month, week] = parts;
|
||||
|
||||
// 尝试识别模式
|
||||
if (day === '*' && week === '?') {
|
||||
// 可能是每天
|
||||
this.cronMode = 'daily';
|
||||
this.dailyConfig.time = `${hour.padStart(2, '0')}:${minute.padStart(2, '0')}:${second.padStart(2, '0')}`;
|
||||
} else if (day === '?' && week !== '?' && week !== '*') {
|
||||
// 可能是每周
|
||||
this.cronMode = 'weekly';
|
||||
this.weeklyConfig.weekDays = week.split(',');
|
||||
this.weeklyConfig.time = `${hour.padStart(2, '0')}:${minute.padStart(2, '0')}:${second.padStart(2, '0')}`;
|
||||
} else if (day !== '*' && day !== '?' && week === '?') {
|
||||
// 可能是每月
|
||||
this.cronMode = 'monthly';
|
||||
this.monthlyConfig.days = day.split(',').map(d => parseInt(d));
|
||||
this.monthlyConfig.time = `${hour.padStart(2, '0')}:${minute.padStart(2, '0')}:${second.padStart(2, '0')}`;
|
||||
} else {
|
||||
// 自定义模式
|
||||
this.cronMode = 'custom';
|
||||
this.customConfig = { second, minute, hour, day, month, week };
|
||||
}
|
||||
|
||||
this.cronExpression = cron;
|
||||
this.generateCron();
|
||||
},
|
||||
|
||||
/**
|
||||
* 复制成功回调
|
||||
*/
|
||||
onCopySuccess() {
|
||||
ElMessage.success('Cron 表达式已复制到剪贴板');
|
||||
},
|
||||
|
||||
/**
|
||||
* 复制失败回调
|
||||
*/
|
||||
onCopyError() {
|
||||
ElMessage.error('复制失败,请手动复制');
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取当前 Cron 表达式(供父组件调用)
|
||||
*/
|
||||
getCronExpression() {
|
||||
return this.cronExpression;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cron-editor {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cron-card {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.cron-mode-tabs {
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.cron-mode-tabs :deep(.el-radio-button__inner) {
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cron-config-area {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.mode-content {
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
margin-left: 12px;
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.cron-help {
|
||||
margin-top: 18px;
|
||||
padding: 16px;
|
||||
background: linear-gradient(135deg, #fefce8 0%, #fef9c3 100%);
|
||||
border: 1px solid #fde047;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 6px rgba(234, 179, 8, 0.1);
|
||||
}
|
||||
|
||||
.cron-help-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #854d0e;
|
||||
}
|
||||
|
||||
.help-icon {
|
||||
margin-right: 6px;
|
||||
font-size: 16px;
|
||||
color: #ca8a04;
|
||||
}
|
||||
|
||||
.cron-help-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.cron-help-list li {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
line-height: 26px;
|
||||
color: #713f12;
|
||||
font-size: 13px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.cron-help-list li strong {
|
||||
display: inline-block;
|
||||
min-width: 36px;
|
||||
margin-right: 8px;
|
||||
padding: 2px 8px;
|
||||
background-color: #fef3c7;
|
||||
border: 1px solid #fde047;
|
||||
border-radius: 4px;
|
||||
color: #b45309;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cron-help-list li span {
|
||||
flex: 1;
|
||||
color: #78716c;
|
||||
}
|
||||
|
||||
.cron-result {
|
||||
margin-top: 25px;
|
||||
padding: 18px 20px;
|
||||
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
|
||||
border: 1px solid #bae6fd;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.08);
|
||||
}
|
||||
|
||||
.cron-description {
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
background-color: #f0f9ff;
|
||||
border-color: #bfdbfe;
|
||||
color: #3b82f6;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cron-expression-input :deep(.el-input__inner) {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
:deep(.el-checkbox) {
|
||||
margin-right: 20px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.mode-content :deep(.el-form-item) {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.mode-content :deep(.el-form-item:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.el-select .el-input__inner) {
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
:deep(.el-time-picker) {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.description-item {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
23
src/components/error-page/403.vue
Normal file
23
src/components/error-page/403.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="error-page">
|
||||
<div class="img" style="background-image: url('/img/403.svg')"></div>
|
||||
<div class="content">
|
||||
<h1>403</h1>
|
||||
<div class="desc">抱歉,你无权访问该页面</div>
|
||||
<div class="actions">
|
||||
<router-link :to="{ path: '/' }">
|
||||
<el-button type="primary">返回首页</el-button>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'error-403',
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@use './style.scss';
|
||||
</style>
|
||||
23
src/components/error-page/404.vue
Normal file
23
src/components/error-page/404.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="error-page">
|
||||
<div class="img" style="background-image: url('/img/404.svg')"></div>
|
||||
<div class="content">
|
||||
<h1>404</h1>
|
||||
<div class="desc">抱歉,你访问的页面不存在</div>
|
||||
<div class="actions">
|
||||
<router-link :to="{ path: '/' }">
|
||||
<el-button type="primary">返回首页</el-button>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'error-404',
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@use './style.scss';
|
||||
</style>
|
||||
23
src/components/error-page/500.vue
Normal file
23
src/components/error-page/500.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="error-page">
|
||||
<div class="img" style="background-image: url('/img/500.svg')"></div>
|
||||
<div class="content">
|
||||
<h1>500</h1>
|
||||
<div class="desc">抱歉,服务器出错了</div>
|
||||
<div class="actions">
|
||||
<router-link :to="{ path: '/' }">
|
||||
<el-button type="primary">返回首页</el-button>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'error-500',
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@use './style.scss';
|
||||
</style>
|
||||
35
src/components/error-page/style.scss
Normal file
35
src/components/error-page/style.scss
Normal file
@@ -0,0 +1,35 @@
|
||||
.error-page {
|
||||
background: #f0f2f5;
|
||||
margin-top: -30px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.img {
|
||||
margin-right: 80px;
|
||||
height: 360px;
|
||||
width: 100%;
|
||||
max-width: 430px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.content {
|
||||
h1 {
|
||||
color: #434e59;
|
||||
font-size: 72px;
|
||||
font-weight: 600;
|
||||
line-height: 72px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
194
src/components/flow-design-step/main.vue
Normal file
194
src/components/flow-design-step/main.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<div v-if="componentLoaded">
|
||||
<el-dialog
|
||||
v-if="website.design.designMode"
|
||||
title="流程配置"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
v-model="visible"
|
||||
:close-on-press-escape="false"
|
||||
:fullscreen="true"
|
||||
:before-close="handleNutflowClose"
|
||||
class="nf-dialog"
|
||||
>
|
||||
<nf-design-base
|
||||
v-if="nutflowOption.step === 1"
|
||||
class="animated fadeIn"
|
||||
style="height: calc(100vh - 108px)"
|
||||
ref="nf-design"
|
||||
:options="nutflowOption.step1"
|
||||
></nf-design-base>
|
||||
<nf-design-base
|
||||
v-if="nutflowOption.step === 2"
|
||||
class="animated fadeIn"
|
||||
style="height: calc(100vh - 108px)"
|
||||
ref="nf-design-view"
|
||||
:options="nutflowOption.step2"
|
||||
></nf-design-base>
|
||||
<template #footer>
|
||||
<span class="avue-dialog__footer">
|
||||
<el-button @click="handleNutflowClose(() => {}, true)">取 消</el-button>
|
||||
<el-button v-if="nutflowOption.step === 1" type="success" @click="handleStep(1)"
|
||||
>下 一 步</el-button
|
||||
>
|
||||
<el-button v-if="nutflowOption.step === 2" type="success" @click="handleStep(-1)"
|
||||
>上 一 步</el-button
|
||||
>
|
||||
<el-button v-if="nutflowOption.step === 2" type="primary" @click="handleSubmitModel"
|
||||
>确 定</el-button
|
||||
>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { loadFlowModule } from '@/utils/module';
|
||||
import { submitModel } from '@/api/flow/flow';
|
||||
|
||||
export default {
|
||||
name: 'flowDesign',
|
||||
props: {
|
||||
isDisplay: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
componentLoaded: false,
|
||||
nutflowOption: {
|
||||
process: {},
|
||||
step: 1,
|
||||
step1: {
|
||||
toolbar: [
|
||||
'open',
|
||||
'create',
|
||||
'fit',
|
||||
'zoom-in',
|
||||
'zoom-out',
|
||||
'undo',
|
||||
'redo',
|
||||
'import',
|
||||
'preview',
|
||||
],
|
||||
},
|
||||
step2: {
|
||||
mode: 'view',
|
||||
simulation: true,
|
||||
minimap: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 懒加载流程设计器模块
|
||||
loadFlowModule(this.$app).then(() => {
|
||||
this.componentLoaded = true;
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
isDisplay: {
|
||||
handler(val) {
|
||||
this.visible = val;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
visible: {
|
||||
handler(val) {
|
||||
this.$emit('update:is-display', val);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleSubmitModel() {
|
||||
const registry = this.$refs['nf-design-view'].getElementRegistry().getAll();
|
||||
const { businessObject } = registry[0];
|
||||
const { id, name, documentation } = businessObject;
|
||||
const description = documentation && documentation.length > 0 ? documentation[0].text : null;
|
||||
const params = {
|
||||
...this.nutflowOption.process,
|
||||
modelKey: id,
|
||||
name,
|
||||
description,
|
||||
modelEditorXml: this.nutflowOption.process.xml,
|
||||
};
|
||||
submitModel(params).then(() => {
|
||||
this.$message.success('操作成功');
|
||||
this.handleNutflowClose();
|
||||
this.$emit('loadData');
|
||||
});
|
||||
},
|
||||
handleStep(step) {
|
||||
if (step === 1) {
|
||||
// 下一步
|
||||
this.$refs['nf-design'].getData('xml').then(data => {
|
||||
this.nutflowOption.step1.xml = data;
|
||||
this.nutflowOption.step2.xml = data;
|
||||
this.nutflowOption.process.xml = data;
|
||||
this.nutflowOption.step = 2;
|
||||
});
|
||||
} else this.nutflowOption.step = 1;
|
||||
},
|
||||
handleNutflowClose(done, flag) {
|
||||
const initOption = {
|
||||
process: {},
|
||||
step: 1,
|
||||
step1: {
|
||||
toolbar: [
|
||||
'open',
|
||||
'create',
|
||||
'fit',
|
||||
'zoom-in',
|
||||
'zoom-out',
|
||||
'undo',
|
||||
'redo',
|
||||
'import',
|
||||
'preview',
|
||||
],
|
||||
},
|
||||
step2: {
|
||||
mode: 'view',
|
||||
simulation: true,
|
||||
minimap: true,
|
||||
},
|
||||
};
|
||||
if (done || flag) {
|
||||
this.$confirm('确定要关闭吗?关闭未保存的修改都会丢失。', '警告', {
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
this.nutflowOption = initOption;
|
||||
if (typeof done == 'function') done();
|
||||
this.visible = false;
|
||||
})
|
||||
.catch(() => {});
|
||||
} else {
|
||||
this.nutflowOption = initOption;
|
||||
this.visible = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.flow-design-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 !important;
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -40%);
|
||||
max-height: calc(100% - 30px);
|
||||
max-width: calc(100% - 30px);
|
||||
|
||||
.el-dialog__body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
133
src/components/flow-design/main.vue
Normal file
133
src/components/flow-design/main.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div v-if="componentLoaded">
|
||||
<el-dialog
|
||||
v-if="isDialog"
|
||||
v-model="visible"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
title="流程图展示"
|
||||
width="70%"
|
||||
class="flow-design-dialog"
|
||||
>
|
||||
<nf-design-base ref="bpmn" style="height: 60vh" :options="option"></nf-design-base>
|
||||
</el-dialog>
|
||||
<div v-else>
|
||||
<nf-design-base
|
||||
v-if="visible"
|
||||
ref="bpmn"
|
||||
style="height: 60vh"
|
||||
:options="option"
|
||||
></nf-design-base>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { loadFlowModule } from '@/utils/module';
|
||||
import { modelView } from '@/api/flow/flow';
|
||||
|
||||
export default {
|
||||
name: 'flowDesign',
|
||||
props: {
|
||||
isDialog: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isDisplay: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
processInstanceId: String,
|
||||
processDefinitionId: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
componentLoaded: false,
|
||||
option: {
|
||||
mode: 'view',
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 懒加载流程设计器模块
|
||||
loadFlowModule(this.$app).then(() => {
|
||||
this.componentLoaded = true;
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
isDisplay: {
|
||||
handler(val) {
|
||||
this.visible = val;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
visible: {
|
||||
handler(val) {
|
||||
this.$emit('update:is-display', val);
|
||||
},
|
||||
},
|
||||
processInstanceId: {
|
||||
handler(val) {
|
||||
if (!val) return;
|
||||
this.getDetail({ processInstanceId: this.processInstanceId });
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
processDefinitionId: {
|
||||
handler(val) {
|
||||
if (!val) return;
|
||||
this.getDetail({ processDefinitionId: this.processDefinitionId });
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getDetail(params) {
|
||||
modelView(params).then(res => {
|
||||
const data = res.data.data;
|
||||
const { xml, flow } = data;
|
||||
this.option.xml = xml;
|
||||
if (flow) {
|
||||
const flows = [];
|
||||
flow.forEach(f => {
|
||||
let { endTime } = f;
|
||||
|
||||
const ff = {
|
||||
id: f.historyActivityId,
|
||||
class: !endTime && f.historyActivityType !== 'candidate' ? 'nodePrimary' : '',
|
||||
};
|
||||
|
||||
if (f.historyActivityType === 'sequenceFlow') ff.class = 'lineWarn';
|
||||
else if (!ff.class && f.historyActivityType !== 'candidate') ff.class = 'nodeSuccess';
|
||||
|
||||
const index = flows.findIndex(fl => fl.id === f.historyActivityId);
|
||||
if (index !== -1) flows.splice(index, 1, ff);
|
||||
else flows.push(ff);
|
||||
});
|
||||
this.option.flows = flows;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.flow-design-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 !important;
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -40%);
|
||||
max-height: calc(100% - 30px);
|
||||
max-width: calc(100% - 30px);
|
||||
|
||||
.el-dialog__body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
90
src/components/highlight/main.vue
Normal file
90
src/components/highlight/main.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="code-block-container">
|
||||
<pre v-if="highlightedCode"><code v-html="highlightedCode" class="hljs"></code></pre>
|
||||
<button v-if="clipboard"
|
||||
v-clipboard:copy="this.code"
|
||||
v-clipboard:success="onCopySuccess"
|
||||
class="copy-btn">
|
||||
一键复制
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import hljs from 'highlight.js';
|
||||
import formatter from '@/utils/formatter';
|
||||
// 引入语言支持
|
||||
import json from 'highlight.js/lib/languages/json';
|
||||
import java from 'highlight.js/lib/languages/java';
|
||||
import javascript from 'highlight.js/lib/languages/javascript';
|
||||
import 'highlight.js/styles/github.css';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
code: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: 'javascript'
|
||||
},
|
||||
clipboard: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
highlightedCode() {
|
||||
hljs.registerLanguage('json', json);
|
||||
hljs.registerLanguage('java', java);
|
||||
hljs.registerLanguage('javascript', javascript);
|
||||
let code = this.code;
|
||||
if (this.language === 'json') {
|
||||
code = formatter.prettyCode(this.code);
|
||||
}
|
||||
return hljs.highlight(code, {language: this.language, ignoreIllegals: true}).value;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onCopySuccess() {
|
||||
this.$message.success('复制成功');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.code-block-container {
|
||||
position: relative;
|
||||
background-color: #f0f0f0;
|
||||
width: 100%;
|
||||
overflow-y: auto; /* 只显示纵向滚动条 */
|
||||
overflow-x: hidden; /* 隐藏横向滚动条 */
|
||||
white-space: pre-wrap; /* 防止内容溢出 */
|
||||
word-wrap: break-word; /* 自动换行 */
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
padding: 0.5em;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
position: absolute;
|
||||
top: 0.5em;
|
||||
right: 0.5em;
|
||||
padding: 0.25em 0.5em;
|
||||
font-size: 0.75em;
|
||||
color: #fff;
|
||||
background-color: #606266;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background-color: #909399;
|
||||
}
|
||||
</style>
|
||||
82
src/components/iframe/main.vue
Normal file
82
src/components/iframe/main.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<basic-container>
|
||||
<iframe :src="src" class="iframe" ref="iframe" />
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NProgress from 'nprogress'; // progress bar
|
||||
import 'nprogress/nprogress.css'; // progress bar style
|
||||
export default {
|
||||
name: 'AvueIframe',
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
created() {
|
||||
NProgress.configure({ showSpinner: false });
|
||||
},
|
||||
mounted() {
|
||||
this.load();
|
||||
},
|
||||
watch: {
|
||||
$route: function () {
|
||||
this.load();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
src() {
|
||||
return this.$route.query.url.replace(/#/g, '&');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 显示等待框
|
||||
show() {
|
||||
NProgress.start();
|
||||
},
|
||||
// 隐藏等待狂
|
||||
hide() {
|
||||
NProgress.done();
|
||||
},
|
||||
// 加载组件
|
||||
load() {
|
||||
this.show();
|
||||
//超时3s自动隐藏等待狂,加强用户体验
|
||||
let time = 3;
|
||||
const timeFunc = setInterval(() => {
|
||||
time--;
|
||||
if (time == 0) {
|
||||
this.hide();
|
||||
clearInterval(timeFunc);
|
||||
}
|
||||
}, 1000);
|
||||
this.iframeInit();
|
||||
},
|
||||
//iframe窗口初始化
|
||||
iframeInit() {
|
||||
const iframe = this.$refs.iframe;
|
||||
const clientHeight = document.documentElement.clientHeight - 150;
|
||||
if (!iframe) return;
|
||||
iframe.style.height = `${clientHeight}px`;
|
||||
if (iframe.attachEvent) {
|
||||
iframe.attachEvent('onload', () => {
|
||||
this.hide();
|
||||
});
|
||||
} else {
|
||||
iframe.onload = () => {
|
||||
this.hide();
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
129
src/components/third-register/main.vue
Normal file
129
src/components/third-register/main.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="账号注册"
|
||||
append-to-body
|
||||
v-model="accountBox"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:show-close="false"
|
||||
width="20%"
|
||||
>
|
||||
<el-form :model="form" ref="form" label-width="80px">
|
||||
<el-form-item v-if="tenantMode" label="租户编号">
|
||||
<el-input v-model="form.tenantId" placeholder="请输入租户编号"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户姓名">
|
||||
<el-input v-model="form.name" placeholder="请输入用户姓名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="账号名称">
|
||||
<el-input v-model="form.account" placeholder="请输入账号名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="账号密码">
|
||||
<el-input v-model="form.password" placeholder="请输入账号密码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码">
|
||||
<el-input v-model="form.password2" placeholder="请输入确认密码"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" :loading="loading" @click="handleRegister">确 定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { validatenull } from 'utils/validate';
|
||||
import { registerGuest } from '@/api/user';
|
||||
import { getTopUrl } from 'utils/util';
|
||||
import { info } from '@/api/system/tenant';
|
||||
import { resetRouter } from '@/router/index';
|
||||
|
||||
export default {
|
||||
name: 'thirdRegister',
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
tenantId: '',
|
||||
name: '',
|
||||
account: '',
|
||||
password: '',
|
||||
password2: '',
|
||||
},
|
||||
loading: false,
|
||||
tenantMode: true,
|
||||
accountBox: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['userInfo']),
|
||||
},
|
||||
created() {
|
||||
this.getTenant();
|
||||
},
|
||||
mounted() {
|
||||
// 若未登录则弹出框进行绑定
|
||||
if (validatenull(this.userInfo.userId) || this.userInfo.userId < 0) {
|
||||
this.form.name = this.userInfo.userName;
|
||||
this.form.account = this.userInfo.account;
|
||||
this.accountBox = true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleRegister() {
|
||||
if (this.form.tenantId === '') {
|
||||
this.$message.warning('请先输入租户编号');
|
||||
return;
|
||||
}
|
||||
if (this.form.account === '') {
|
||||
this.$message.warning('请先输入账号名称');
|
||||
return;
|
||||
}
|
||||
if (this.form.password === '' || this.form.password2 === '') {
|
||||
this.$message.warning('请先输入密码');
|
||||
return;
|
||||
}
|
||||
if (this.form.password !== this.form.password2) {
|
||||
this.$message.warning('两次密码输入不一致');
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
registerGuest(this.form, this.userInfo.oauthId).then(
|
||||
res => {
|
||||
this.loading = false;
|
||||
const data = res.data;
|
||||
if (data.success) {
|
||||
this.accountBox = false;
|
||||
this.$alert('注册申请已提交,请耐心等待管理员通过!', '注册提示').then(() => {
|
||||
this.$store.dispatch('LogOut').then(() => {
|
||||
resetRouter();
|
||||
this.$router.push({ path: '/login' });
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.$message.error(data.msg || '提交失败');
|
||||
}
|
||||
},
|
||||
error => {
|
||||
window.console.log(error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
},
|
||||
getTenant() {
|
||||
let domain = getTopUrl();
|
||||
// 临时指定域名,方便测试
|
||||
//domain = "https://bladex.cn";
|
||||
info(domain).then(res => {
|
||||
const data = res.data;
|
||||
if (data.success && data.data.tenantId) {
|
||||
this.form.tenantId = data.data.tenantId;
|
||||
this.tenantMode = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
2
src/config/env.js
Normal file
2
src/config/env.js
Normal file
@@ -0,0 +1,2 @@
|
||||
let baseUrl = import.meta.env.VITE_APP_API;
|
||||
export { baseUrl };
|
||||
380
src/config/iconList.js
Normal file
380
src/config/iconList.js
Normal file
@@ -0,0 +1,380 @@
|
||||
export default [
|
||||
{
|
||||
label: '通用图标',
|
||||
list: [
|
||||
'iconfont iconicon_roundadd',
|
||||
'iconfont iconicon_compile',
|
||||
'iconfont iconicon_glass',
|
||||
'iconfont iconicon_roundclose',
|
||||
'iconfont iconicon_roundreduce',
|
||||
'iconfont iconicon_delete',
|
||||
'iconfont iconicon_shakehands',
|
||||
'iconfont iconicon_task_done',
|
||||
'iconfont iconicon_voipphone',
|
||||
'iconfont iconicon_safety',
|
||||
'iconfont iconicon_work',
|
||||
'iconfont iconicon_study',
|
||||
'iconfont iconicon_task',
|
||||
'iconfont iconicon_subordinate',
|
||||
'iconfont iconicon_star',
|
||||
'iconfont iconicon_setting',
|
||||
'iconfont iconicon_sms',
|
||||
'iconfont iconicon_share',
|
||||
'iconfont iconicon_secret',
|
||||
'iconfont iconicon_scan_namecard',
|
||||
'iconfont iconicon_principal',
|
||||
'iconfont iconicon_group',
|
||||
'iconfont iconicon_send',
|
||||
'iconfont iconicon_scan',
|
||||
'iconfont iconicon_search',
|
||||
'iconfont iconicon_refresh',
|
||||
'iconfont iconicon_savememo',
|
||||
'iconfont iconicon_QRcode',
|
||||
'iconfont iconicon_im_keyboard',
|
||||
'iconfont iconicon_redpacket',
|
||||
'iconfont iconicon_photo',
|
||||
'iconfont iconicon_qq',
|
||||
'iconfont iconicon_wechat',
|
||||
'iconfont iconicon_phone',
|
||||
'iconfont iconicon_namecard',
|
||||
'iconfont iconicon_notice',
|
||||
'iconfont iconicon_next_arrow',
|
||||
'iconfont iconicon_left',
|
||||
'iconfont iconicon_more',
|
||||
'iconfont iconicon_details',
|
||||
'iconfont iconicon_message',
|
||||
'iconfont iconicon_mobilephone',
|
||||
'iconfont iconicon_im_voice',
|
||||
'iconfont iconicon_GPS',
|
||||
'iconfont iconicon_ding',
|
||||
'iconfont iconicon_exchange',
|
||||
'iconfont iconicon_cspace',
|
||||
'iconfont iconicon_doc',
|
||||
'iconfont iconicon_dispose',
|
||||
'iconfont iconicon_discovery',
|
||||
'iconfont iconicon_community_line',
|
||||
'iconfont iconicon_cloud_history',
|
||||
'iconfont iconicon_coinpurse_line',
|
||||
'iconfont iconicon_airplay',
|
||||
'iconfont iconicon_at',
|
||||
'iconfont iconicon_addressbook',
|
||||
'iconfont iconicon_boss',
|
||||
'iconfont iconicon_addperson',
|
||||
'iconfont iconicon_affiliations_li',
|
||||
'iconfont iconicon_addmessage',
|
||||
'iconfont iconicon_addresslist',
|
||||
'iconfont iconicon_add',
|
||||
'iconfont icongithub',
|
||||
'iconfont icongitee2',
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '系统图标',
|
||||
list: [
|
||||
'iconfont icon-zhongyingwen',
|
||||
'iconfont icon-caidan',
|
||||
'iconfont icon-rizhi1',
|
||||
'iconfont icon-zhuti',
|
||||
'iconfont icon-suoping',
|
||||
'iconfont icon-bug',
|
||||
'iconfont icon-qq1',
|
||||
'iconfont icon-weixin1',
|
||||
'iconfont icon-shouji',
|
||||
'iconfont icon-mima',
|
||||
'iconfont icon-yonghu',
|
||||
'iconfont icon-yanzhengma',
|
||||
'iconfont icon-canshu',
|
||||
'iconfont icon-dongtai',
|
||||
'iconfont icon-iconset0265',
|
||||
'iconfont icon-shujuzhanshi2',
|
||||
'iconfont icon-tuichuquanping',
|
||||
'iconfont icon-rizhi',
|
||||
'iconfont icon-cuowutishitubiao',
|
||||
'iconfont icon-debug',
|
||||
'iconfont icon-iconset0216',
|
||||
'iconfont icon-quanxian',
|
||||
'iconfont icon-quanxian',
|
||||
'iconfont icon-shuaxin',
|
||||
'iconfont icon-bofangqi-suoping',
|
||||
'iconfont icon-quanping',
|
||||
'iconfont icon-navicon',
|
||||
'iconfont icon-biaodan',
|
||||
'iconfont icon-liuliangyunpingtaitubiao08',
|
||||
'iconfont icon-caidanguanli',
|
||||
'iconfont icon-cuowu',
|
||||
'iconfont icon-wxbgongju',
|
||||
'iconfont icon-tuichu',
|
||||
'iconfont icon-daohanglanmoshi02',
|
||||
'iconfont icon-changyonglogo27',
|
||||
'iconfont icon-biaoge',
|
||||
'iconfont icon-baidu1',
|
||||
'iconfont icon-tubiao',
|
||||
'iconfont icon-souhu',
|
||||
'iconfont icon-msnui-360',
|
||||
'iconfont icon-iframe',
|
||||
'iconfont icon-huanyingye',
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '业务图标',
|
||||
list: [
|
||||
'iconfont icon-zidingyi',
|
||||
'iconfont icon-xiajiantou',
|
||||
'iconfont icon-shangjiantou',
|
||||
'iconfont icon-icon_loading',
|
||||
'iconfont icon-biaodanzujian-shurukuang',
|
||||
'iconfont icon-biaodanzujian-biaoge',
|
||||
'iconfont icon-biaodanzujian-xialakuang',
|
||||
'iconfont icon-tubiao-bingtu',
|
||||
'iconfont icon-biaodanzujian-anniu',
|
||||
'iconfont icon-gongyezujian-yibiaopan',
|
||||
'iconfont icon-tubiao-qiapian',
|
||||
'iconfont icon-gongyezujian-zhishideng',
|
||||
'iconfont icon-tubiao-zhexiantu',
|
||||
'iconfont icon-xingzhuang-juxing',
|
||||
'iconfont icon-xingzhuang-jianxing',
|
||||
'iconfont icon-gongyezujian-kaiguan',
|
||||
'iconfont icon-tubiao-zhuzhuangtu',
|
||||
'iconfont icon-xingzhuang-tupian',
|
||||
'iconfont icon-xingzhuang-wenzi',
|
||||
'iconfont icon-xingzhuang-tuoyuanxing',
|
||||
'iconfont icon-xingzhuang-sanjiaoxing',
|
||||
'iconfont icon-xingzhuang-xingxing',
|
||||
'iconfont icon-guize',
|
||||
'iconfont icon-shebeiguanli',
|
||||
'iconfont icon-gongnengdingyi1',
|
||||
'iconfont icon-jishufuwu1',
|
||||
'iconfont icon-yunyingzhongxin',
|
||||
'iconfont icon-yunyingguanli',
|
||||
'iconfont icon-zuzhixiaxia',
|
||||
'iconfont icon-zuzhizhankai',
|
||||
'iconfont icon-zuzhiqunzu',
|
||||
'iconfont icon-dakai',
|
||||
'iconfont icon-yingwen',
|
||||
'iconfont icon-zhongwen',
|
||||
'iconfont icon-miwen',
|
||||
'iconfont icon-xianhao',
|
||||
'iconfont icon-kongxinduigou',
|
||||
'iconfont icon-huixingzhen',
|
||||
'iconfont icon-duigou',
|
||||
'iconfont icon-xiayibu',
|
||||
'iconfont icon-shangyibu',
|
||||
'iconfont icon-kongjianxuanzhong',
|
||||
'iconfont icon-kongjianweixuan',
|
||||
'iconfont icon-kongjianyixuan',
|
||||
'iconfont icon--diangan',
|
||||
'iconfont icon-rongxuejirongjiechi',
|
||||
'iconfont icon-lubiantingchechang',
|
||||
'iconfont icon--lumingpai',
|
||||
'iconfont icon-jietouzuoyi',
|
||||
'iconfont icon--zhongdaweixian',
|
||||
'iconfont icon--jiaotongbiaozhipai',
|
||||
'iconfont icon-gongcezhishipai',
|
||||
'iconfont icon-fangkuai',
|
||||
'iconfont icon-fangkuai-',
|
||||
'iconfont icon-shuaxin',
|
||||
'iconfont icon-baocun',
|
||||
'iconfont icon-fabu',
|
||||
'iconfont icon-xiayibu1',
|
||||
'iconfont icon-shangyibu1',
|
||||
'iconfont icon-xiangxiazhanhang',
|
||||
'iconfont icon-xiangshangzhanhang',
|
||||
'iconfont icon-tupianjiazaishibai',
|
||||
'iconfont icon-fuwudiqiu',
|
||||
'iconfont icon-suoxiao',
|
||||
'iconfont icon-fangda',
|
||||
'iconfont icon-huanyuanhuabu',
|
||||
'iconfont icon-quanping',
|
||||
'iconfont icon-biaodanzujian-biaoge1',
|
||||
'iconfont icon-APIshuchu',
|
||||
'iconfont icon-APIjieru',
|
||||
'iconfont icon-wenjianjia',
|
||||
'iconfont icon-DOC',
|
||||
'iconfont icon-BMP',
|
||||
'iconfont icon-GIF',
|
||||
'iconfont icon-JPG',
|
||||
'iconfont icon-PNG',
|
||||
'iconfont icon-weizhigeshi',
|
||||
'iconfont icon-gengduo',
|
||||
'iconfont icon-yunduanxiazai',
|
||||
'iconfont icon-yunduanshangchuan',
|
||||
'iconfont icon-dian',
|
||||
'iconfont icon-mian',
|
||||
'iconfont icon-xian',
|
||||
'iconfont icon-shebeizhuangtai',
|
||||
'iconfont icon-fenzuguanli',
|
||||
'iconfont icon-kuaisubianpai',
|
||||
'iconfont icon-APPkaifa',
|
||||
'iconfont icon-wentijieda',
|
||||
'iconfont icon-kefu',
|
||||
'iconfont icon-ruanjiankaifabao',
|
||||
'iconfont icon-sousuobianxiao',
|
||||
'iconfont icon-sousuofangda',
|
||||
'iconfont icon-dingwei',
|
||||
'iconfont icon-wumoxing',
|
||||
'iconfont icon-gaojing',
|
||||
'iconfont icon-renwujincheng',
|
||||
'iconfont icon-xiaoxitongzhi',
|
||||
'iconfont icon-youhui',
|
||||
'iconfont icon-gaojing1',
|
||||
'iconfont icon-zhihangfankui',
|
||||
'iconfont icon-gongdanqueren',
|
||||
'iconfont icon-guangbo',
|
||||
'iconfont icon-gongdan',
|
||||
'iconfont icon-xiaoxi',
|
||||
'iconfont icon-xinhao',
|
||||
'iconfont icon-lanya',
|
||||
'iconfont icon-Wi-Fi',
|
||||
'iconfont icon-chaxun',
|
||||
'iconfont icon-dianbiao',
|
||||
'iconfont icon-anquan',
|
||||
'iconfont icon-daibanshixiang',
|
||||
'iconfont icon-bingxiang',
|
||||
'iconfont icon-fanshe',
|
||||
'iconfont icon-fengche',
|
||||
'iconfont icon-guandao',
|
||||
'iconfont icon-guize1',
|
||||
'iconfont icon-guizeyinqing',
|
||||
'iconfont icon-huowudui',
|
||||
'iconfont icon-jianceqi',
|
||||
'iconfont icon-jinggai',
|
||||
'iconfont icon-liujisuan',
|
||||
'iconfont icon-hanshu',
|
||||
'iconfont icon-lianjieliu',
|
||||
'iconfont icon-ludeng',
|
||||
'iconfont icon-shexiangji',
|
||||
'iconfont icon-rentijiance',
|
||||
'iconfont icon-moshubang',
|
||||
'iconfont icon-shujuwajue',
|
||||
'iconfont icon-wangguan',
|
||||
'iconfont icon-shenjing',
|
||||
'iconfont icon-chucun',
|
||||
'iconfont icon-wuguan',
|
||||
'iconfont icon-yunduanshuaxin',
|
||||
'iconfont icon-yunhang',
|
||||
'iconfont icon-luyouqi',
|
||||
'iconfont icon-bug',
|
||||
'iconfont icon-get',
|
||||
'iconfont icon-PIR',
|
||||
'iconfont icon-zhexiantu',
|
||||
'iconfont icon-shuibiao',
|
||||
'iconfont icon-js',
|
||||
'iconfont icon-zihangche',
|
||||
'iconfont icon-liebiao',
|
||||
'iconfont icon-qichedingwei',
|
||||
'iconfont icon-dici',
|
||||
'iconfont icon-mysql',
|
||||
'iconfont icon-qiche',
|
||||
'iconfont icon-shenjing1',
|
||||
'iconfont icon-chengshi',
|
||||
'iconfont icon-tixingshixin',
|
||||
'iconfont icon-menci',
|
||||
'iconfont icon-chazuo',
|
||||
'iconfont icon-ranqijianceqi',
|
||||
'iconfont icon-kaiguan',
|
||||
'iconfont icon-chatou',
|
||||
'iconfont icon-xiyiji',
|
||||
'iconfont icon-yijiankaiguan',
|
||||
'iconfont icon-yanwubaojingqi',
|
||||
'iconfont icon-wuxiandianbo',
|
||||
'iconfont icon-fuzhi',
|
||||
'iconfont icon-shanchu',
|
||||
'iconfont icon-bianjisekuai',
|
||||
'iconfont icon-ishipinshixiao',
|
||||
'iconfont icon-iframetianjia',
|
||||
'iconfont icon-tupiantianjia',
|
||||
'iconfont icon-liebiaomoshi_kuai',
|
||||
'iconfont icon-qiapianmoshi_kuai',
|
||||
'iconfont icon-fenlan',
|
||||
'iconfont icon-fengexian',
|
||||
'iconfont icon-dianzan',
|
||||
'iconfont icon-charulianjie',
|
||||
'iconfont icon-charutupian',
|
||||
'iconfont icon-quxiaolianjie',
|
||||
'iconfont icon-wuxupailie',
|
||||
'iconfont icon-juzhongduiqi',
|
||||
'iconfont icon-yinyong',
|
||||
'iconfont icon-youxupailie',
|
||||
'iconfont icon-youduiqi',
|
||||
'iconfont icon-zitidaima',
|
||||
'iconfont icon-xiaolian',
|
||||
'iconfont icon-zitijiacu',
|
||||
'iconfont icon-zitishanchuxian',
|
||||
'iconfont icon-zitishangbiao',
|
||||
'iconfont icon-zitibiaoti',
|
||||
'iconfont icon-zitixiahuaxian',
|
||||
'iconfont icon-zitixieti',
|
||||
'iconfont icon-zitiyanse',
|
||||
'iconfont icon-zuoduiqi',
|
||||
'iconfont icon-zitiyulan',
|
||||
'iconfont icon-zitixiabiao',
|
||||
'iconfont icon-zuoyouduiqi',
|
||||
'iconfont icon-tianxie',
|
||||
'iconfont icon-huowudui1',
|
||||
'iconfont icon-yingjian',
|
||||
'iconfont icon-shebeikaifa',
|
||||
'iconfont icon-dianzan_kuai',
|
||||
'iconfont icon-zhihuan',
|
||||
'iconfont icon-tuoguan',
|
||||
'iconfont icon-duigoux',
|
||||
'iconfont icon-guanbi1',
|
||||
'iconfont icon-aixin_shixin',
|
||||
'iconfont icon-ranqixieloubaojingqi',
|
||||
'iconfont icon-dianbiao_shiti',
|
||||
'iconfont icon-aixin',
|
||||
'iconfont icon-shuibiao_shiti',
|
||||
'iconfont icon-zhinengxiaofangshuan',
|
||||
'iconfont icon-ranqibiao_shiti',
|
||||
'iconfont icon-shexiangtou_shiti',
|
||||
'iconfont icon-shexiangtou_guanbi',
|
||||
'iconfont icon-shexiangtou',
|
||||
'iconfont icon-shengyin_shiti',
|
||||
'iconfont icon-shengyinkai',
|
||||
'iconfont icon-shoucang_shixin',
|
||||
'iconfont icon-shoucang',
|
||||
'iconfont icon-shengyinwu',
|
||||
'iconfont icon-shengyinjingyin',
|
||||
'iconfont icon-zhunbeiliangchan',
|
||||
'iconfont icon-shebeikaifa1',
|
||||
'iconfont icon-kongxinwenhao',
|
||||
'iconfont icon-cuowukongxin',
|
||||
'iconfont icon-fangkuai1',
|
||||
'iconfont icon-fangkuai2',
|
||||
'iconfont icon-kongjianxuanzhong1',
|
||||
'iconfont icon-kongxinduigou1',
|
||||
'iconfont icon-xinxikongxin',
|
||||
'iconfont icon-kongjian',
|
||||
'iconfont icon-gaojingkongxin',
|
||||
'iconfont icon-duigou_kuai',
|
||||
'iconfont icon-cuocha_kuai',
|
||||
'iconfont icon-jia_sekuai',
|
||||
'iconfont icon-jian_sekuai',
|
||||
'iconfont icon-tiaoshi',
|
||||
'iconfont icon-fenxiangfangshi',
|
||||
'iconfont icon-changjingguanli',
|
||||
'iconfont icon-bianji',
|
||||
'iconfont icon-guanlianshebei',
|
||||
'iconfont icon-guanfangbanben',
|
||||
'iconfont icon-gongnengdingyi',
|
||||
'iconfont icon-jichuguanli',
|
||||
'iconfont icon-jishufuwu',
|
||||
'iconfont icon-hezuohuobanmiyueguanli',
|
||||
'iconfont icon-ceshishenqing',
|
||||
'iconfont icon-jiedianguanli',
|
||||
'iconfont icon-jinggao',
|
||||
'iconfont icon-peiwangyindao',
|
||||
'iconfont icon-renjijiaohu',
|
||||
'iconfont icon-shiyongwendang',
|
||||
'iconfont icon-quanxianshenpi',
|
||||
'iconfont icon-yishouquan',
|
||||
'iconfont icon-tianshenpi',
|
||||
'iconfont icon-shujukanban',
|
||||
'iconfont icon-yingyongguanli',
|
||||
'iconfont icon-yibiaopan',
|
||||
'iconfont icon-zhanghaoquanxianguanli',
|
||||
'iconfont icon-yuanquyunwei',
|
||||
'iconfont icon-jizhanguanli',
|
||||
'iconfont icon-guanbi',
|
||||
],
|
||||
},
|
||||
];
|
||||
82
src/config/website.js
Normal file
82
src/config/website.js
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 全局配置文件
|
||||
*/
|
||||
export default {
|
||||
title: 'Saber',
|
||||
logo: 'X',
|
||||
key: 'saber', //配置主键,目前用于存储
|
||||
indexTitle: 'BladeX 微服务平台',
|
||||
clientId: 'saber3', // 客户端id
|
||||
clientSecret: 'saber3_secret', // 客户端密钥
|
||||
tenantMode: true, // 是否开启租户模式
|
||||
tenantId: '000000', // 管理组租户编号
|
||||
captchaMode: true, // 是否开启验证码模式
|
||||
captchaType: 'behavior', // 验证码类型(image:首屏图形验证码 behavior:登录时弹出点选/滑块/旋转随机行为验证)
|
||||
switchMode: false, // 是否开启登录切换角色部门
|
||||
lockPage: '/lock', // 锁屏页面地址
|
||||
tokenTime: 3000, // 定时刷新token间隔(单位:毫秒)
|
||||
tokenHeader: 'Blade-Auth', // 请求头中携带的token名称
|
||||
tokenKey: 'saber3-access-token', // token存储的key(多个系统部署需要修改以免冲突)
|
||||
refreshTokenKey: 'saber3-refresh-token', // 刷新token存储的key(多个系统部署需要修改以免冲突)
|
||||
//HTTP状态码白名单
|
||||
statusWhiteList: [],
|
||||
//配置首页不可关闭
|
||||
setting: {
|
||||
sidebar: 'vertical',
|
||||
tag: true,
|
||||
debug: true,
|
||||
collapse: true,
|
||||
search: true,
|
||||
color: true,
|
||||
lock: true,
|
||||
screenshot: true,
|
||||
fullscreen: true,
|
||||
theme: true,
|
||||
menu: true,
|
||||
},
|
||||
//首页配置
|
||||
fistPage: {
|
||||
name: '首页',
|
||||
path: '/wel/index',
|
||||
},
|
||||
//配置菜单的属性
|
||||
menu: {
|
||||
iconDefault: 'icon-caidan',
|
||||
label: 'name',
|
||||
path: 'path',
|
||||
icon: 'source',
|
||||
children: 'children',
|
||||
query: 'query',
|
||||
href: 'path',
|
||||
meta: 'meta',
|
||||
},
|
||||
//水印配置
|
||||
watermark: {
|
||||
mode: false,
|
||||
text: 'BladeX',
|
||||
},
|
||||
//oauth2配置
|
||||
oauth2: {
|
||||
// 是否开启注册功能
|
||||
registerMode: true,
|
||||
// 使用后端工程 @org.springblade.test.Sm2KeyGenerator 获取
|
||||
publicKey: '请配置国密sm2公钥',
|
||||
// 第三方系统授权地址
|
||||
authUrl: 'http://localhost/blade-auth/oauth/render',
|
||||
// 单点登录系统认证
|
||||
ssoMode: false, // 是否开启单点登录功能
|
||||
ssoBaseUrl: 'http://localhost:8100', // 单点登录系统地址(cloud端口为8100,boot端口为80)
|
||||
ssoAuthUrl: '/oauth/authorize?client_id=saber3&response_type=code&redirect_uri=', // 单点登录授权地址
|
||||
ssoLogoutUrl: '/oauth/authorize/logout?redirect_uri=', // 单点登录退出地址
|
||||
redirectUri: 'http://localhost:2888/login', // 单点登录回调地址(Saber服务的登录界面地址)
|
||||
},
|
||||
//设计器配置
|
||||
design: {
|
||||
// 流程设计器类型(true->nutflow,false->flowable)
|
||||
designMode: true,
|
||||
// 流程设计器地址(flowable模式)
|
||||
designUrl: 'http://localhost:9999',
|
||||
// 报表设计器地址(cloud端口为8108,boot端口为80)
|
||||
reportUrl: 'http://localhost:8108/ureport',
|
||||
},
|
||||
};
|
||||
344
src/const/tool/model.js
Normal file
344
src/const/tool/model.js
Normal file
@@ -0,0 +1,344 @@
|
||||
export const switchDic = [
|
||||
{
|
||||
label: '',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '',
|
||||
value: 1,
|
||||
},
|
||||
];
|
||||
|
||||
export const entityDic = [
|
||||
{
|
||||
label: 'String',
|
||||
value: 'java.lang.String',
|
||||
},
|
||||
{
|
||||
label: 'Integer',
|
||||
value: 'java.lang.Integer',
|
||||
},
|
||||
{
|
||||
label: 'Long',
|
||||
value: 'java.lang.Long',
|
||||
},
|
||||
{
|
||||
label: 'Double',
|
||||
value: 'java.lang.Double',
|
||||
},
|
||||
{
|
||||
label: 'BigDecimal',
|
||||
value: 'java.math.BigDecimal',
|
||||
},
|
||||
{
|
||||
label: 'Boolean',
|
||||
value: 'java.lang.Boolean',
|
||||
},
|
||||
{
|
||||
label: 'Date',
|
||||
value: 'java.util.Date',
|
||||
},
|
||||
];
|
||||
|
||||
export const componentDic = [
|
||||
{
|
||||
label: '单行文本',
|
||||
value: 'input',
|
||||
},
|
||||
{
|
||||
label: '多行文本',
|
||||
value: 'textarea',
|
||||
},
|
||||
{
|
||||
label: '富文本',
|
||||
value: 'editor',
|
||||
},
|
||||
{
|
||||
label: '下拉选项',
|
||||
value: 'select',
|
||||
},
|
||||
{
|
||||
label: '树形下拉选项',
|
||||
value: 'tree',
|
||||
},
|
||||
{
|
||||
label: '单选框',
|
||||
value: 'radio',
|
||||
},
|
||||
{
|
||||
label: '多选框',
|
||||
value: 'checkbox',
|
||||
},
|
||||
{
|
||||
label: '开关框',
|
||||
value: 'switch',
|
||||
},
|
||||
{
|
||||
label: '日期框',
|
||||
value: 'date',
|
||||
},
|
||||
];
|
||||
|
||||
export const queryDic = [
|
||||
{
|
||||
label: '等于',
|
||||
value: 'equal',
|
||||
},
|
||||
{
|
||||
label: '不等于',
|
||||
value: 'notequal',
|
||||
},
|
||||
{
|
||||
label: '大于',
|
||||
value: 'gt',
|
||||
},
|
||||
{
|
||||
label: '大于等于',
|
||||
value: 'ge',
|
||||
},
|
||||
{
|
||||
label: '小于',
|
||||
value: 'lt',
|
||||
},
|
||||
{
|
||||
label: '小于等于',
|
||||
value: 'le',
|
||||
},
|
||||
{
|
||||
label: '区间',
|
||||
value: 'between',
|
||||
},
|
||||
{
|
||||
label: '模糊',
|
||||
value: 'like',
|
||||
},
|
||||
{
|
||||
label: '左模糊',
|
||||
value: 'likeleft',
|
||||
},
|
||||
{
|
||||
label: '右模糊',
|
||||
value: 'likeright',
|
||||
},
|
||||
];
|
||||
|
||||
export const templateDic = [
|
||||
{
|
||||
label: '单表',
|
||||
value: 'crud',
|
||||
},
|
||||
{
|
||||
label: '主子表',
|
||||
value: 'sub',
|
||||
},
|
||||
{
|
||||
label: '树表',
|
||||
value: 'tree',
|
||||
},
|
||||
];
|
||||
|
||||
export const option = {
|
||||
height: 'auto',
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
tip: false,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
grid: true,
|
||||
selection: true,
|
||||
menuWidth: 250,
|
||||
column: [
|
||||
{
|
||||
label: '数据源',
|
||||
prop: 'datasourceId',
|
||||
search: true,
|
||||
span: 24,
|
||||
type: 'select',
|
||||
dicUrl: '/blade-develop/datasource/select',
|
||||
props: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择数据源',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '物理表名',
|
||||
prop: 'modelTable',
|
||||
type: 'tree',
|
||||
slot: true,
|
||||
filterable: true,
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'comment',
|
||||
value: 'name',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入数据库表名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '模型类名',
|
||||
prop: 'modelClass',
|
||||
addDisabled: true,
|
||||
editDisabled: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入模型类名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '模型名称',
|
||||
prop: 'modelName',
|
||||
search: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入模型名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '模型编号',
|
||||
prop: 'modelCode',
|
||||
search: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入模型编号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '模型备注',
|
||||
prop: 'modelRemark',
|
||||
hide: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const optionModel = {
|
||||
border: true,
|
||||
index: true,
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
addRowBtn: false,
|
||||
cellBtn: false,
|
||||
cancelBtn: false,
|
||||
tip: false,
|
||||
menu: false,
|
||||
selection: true,
|
||||
column: [
|
||||
{
|
||||
label: '物理列名',
|
||||
prop: 'jdbcName',
|
||||
},
|
||||
{
|
||||
label: '物理类型',
|
||||
prop: 'jdbcType',
|
||||
},
|
||||
{
|
||||
label: '实体列名',
|
||||
prop: 'propertyName',
|
||||
cell: true,
|
||||
},
|
||||
{
|
||||
label: '实体类型',
|
||||
prop: 'propertyEntity',
|
||||
type: 'select',
|
||||
dicData: entityDic,
|
||||
cell: true,
|
||||
},
|
||||
{
|
||||
label: '字段说明',
|
||||
prop: 'jdbcComment',
|
||||
cell: true,
|
||||
},
|
||||
{
|
||||
label: '列表显示',
|
||||
prop: 'isList',
|
||||
type: 'switch',
|
||||
dicData: switchDic,
|
||||
align: 'center',
|
||||
width: 60,
|
||||
cell: true,
|
||||
},
|
||||
{
|
||||
label: '表单显示',
|
||||
prop: 'isForm',
|
||||
type: 'switch',
|
||||
dicData: switchDic,
|
||||
align: 'center',
|
||||
width: 60,
|
||||
cell: true,
|
||||
},
|
||||
{
|
||||
label: '独占一行',
|
||||
prop: 'isRow',
|
||||
type: 'switch',
|
||||
dicData: switchDic,
|
||||
align: 'center',
|
||||
width: 60,
|
||||
cell: true,
|
||||
},
|
||||
{
|
||||
label: '必填',
|
||||
prop: 'isRequired',
|
||||
type: 'switch',
|
||||
dicData: switchDic,
|
||||
align: 'center',
|
||||
width: 60,
|
||||
cell: true,
|
||||
},
|
||||
{
|
||||
label: '组件类型',
|
||||
prop: 'componentType',
|
||||
type: 'select',
|
||||
dicData: componentDic,
|
||||
cell: true,
|
||||
},
|
||||
{
|
||||
label: '字典编码',
|
||||
prop: 'dictCode',
|
||||
type: 'select',
|
||||
dicUrl: '/blade-system/dict/select',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'code',
|
||||
},
|
||||
cell: true,
|
||||
},
|
||||
{
|
||||
label: '查询配置',
|
||||
prop: 'isQuery',
|
||||
type: 'switch',
|
||||
dicData: switchDic,
|
||||
align: 'center',
|
||||
width: 60,
|
||||
cell: true,
|
||||
},
|
||||
{
|
||||
label: '查询类型',
|
||||
prop: 'queryType',
|
||||
type: 'select',
|
||||
dicData: queryDic,
|
||||
cell: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
50
src/debug.js
Normal file
50
src/debug.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import DisableDevtool from 'disable-devtool';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
|
||||
/**
|
||||
* 安全警告的HTML模板, 使用Tailwind CSS样式
|
||||
*/
|
||||
const SECURITY_WARNING_TEMPLATE = `
|
||||
<div class='flex flex-col items-center gap-4 p-2'>
|
||||
<div class='text-red-500 dark:text-red-400'>
|
||||
<i class='el-icon-warning text-2xl'></i>
|
||||
</div>
|
||||
<div class='text-center'>
|
||||
<p class='text-base font-medium text-gray-900 dark:text-gray-100'>
|
||||
不合规操作,系统将在5秒后自动关闭退出!
|
||||
</p>
|
||||
<p class='mt-2 text-sm text-gray-600 dark:text-gray-400'>
|
||||
如您频繁此类操作,系统将记录上报。
|
||||
</p>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
/**
|
||||
* Element Plus 消息框配置选项
|
||||
*/
|
||||
const ALERT_OPTIONS = {
|
||||
type: 'error',
|
||||
showClose: false,
|
||||
center: true,
|
||||
closeOnClickModal: false,
|
||||
closeOnPressEscape: false,
|
||||
dangerouslyUseHTMLString: true,
|
||||
};
|
||||
|
||||
export default () => {
|
||||
if (import.meta.env.VITE_APP_DEBUG_SWITCH === 'false') return;
|
||||
|
||||
const debugKey = import.meta.env.VITE_APP_DEBUG_KEY;
|
||||
let flag = false;
|
||||
DisableDevtool({
|
||||
md5: DisableDevtool.md5(debugKey),
|
||||
disableMenu: false,
|
||||
ondevtoolopen: (type, next) => {
|
||||
if (!flag) ElMessageBox.alert(SECURITY_WARNING_TEMPLATE, '安全警告', ALERT_OPTIONS);
|
||||
flag = true;
|
||||
setTimeout(() => {
|
||||
next();
|
||||
}, 5000);
|
||||
},
|
||||
});
|
||||
};
|
||||
6
src/docker/Dockerfile
Normal file
6
src/docker/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
||||
FROM nginx
|
||||
VOLUME /tmp
|
||||
ENV LANG en_US.UTF-8
|
||||
ADD ./dist/ /usr/share/nginx/html/
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
25
src/error.js
Normal file
25
src/error.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import store from './store';
|
||||
|
||||
export default {
|
||||
install: app => {
|
||||
app.config.errorHandler = (err, vm, info) => {
|
||||
store.commit('ADD_LOGS', {
|
||||
type: 'error',
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
info,
|
||||
});
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.group('>>>>>> 错误信息 >>>>>>');
|
||||
console.log(info);
|
||||
console.groupEnd();
|
||||
console.group('>>>>>> Vue 实例 >>>>>>');
|
||||
console.log(vm);
|
||||
console.groupEnd();
|
||||
console.group('>>>>>> Error >>>>>>');
|
||||
console.log(err);
|
||||
console.groupEnd();
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
137
src/lang/en.js
Normal file
137
src/lang/en.js
Normal file
@@ -0,0 +1,137 @@
|
||||
export default {
|
||||
title: 'BladeX Admin',
|
||||
logoutTip: 'Exit the system, do you want to continue?',
|
||||
submitText: 'submit',
|
||||
cancelText: 'cancel',
|
||||
search: 'Please input search content',
|
||||
menuTip: 'none menu list',
|
||||
common: {
|
||||
condition: 'condition',
|
||||
display: 'display',
|
||||
hide: 'hide',
|
||||
},
|
||||
tip: {
|
||||
select: 'Please select',
|
||||
input: 'Please input',
|
||||
},
|
||||
upload: {
|
||||
upload: 'upload',
|
||||
tip: 'Drag files here,/',
|
||||
},
|
||||
date: {
|
||||
start: 'Start date',
|
||||
end: 'End date',
|
||||
t: 'today',
|
||||
y: 'yesterday',
|
||||
n: 'nearly 7',
|
||||
a: 'whole',
|
||||
},
|
||||
form: {
|
||||
printBtn: 'print',
|
||||
mockBtn: 'mock',
|
||||
submitBtn: 'submit',
|
||||
emptyBtn: 'empty',
|
||||
},
|
||||
crud: {
|
||||
filter: {
|
||||
addBtn: 'add',
|
||||
clearBtn: 'clear',
|
||||
resetBtn: 'reset',
|
||||
cancelBtn: 'cancel',
|
||||
submitBtn: 'submit',
|
||||
},
|
||||
column: {
|
||||
name: 'name',
|
||||
hide: 'hide',
|
||||
fixed: 'fixed',
|
||||
filters: 'filters',
|
||||
sortable: 'sortable',
|
||||
index: 'index',
|
||||
width: 'width',
|
||||
},
|
||||
tipStartTitle: 'Currently selected',
|
||||
tipEndTitle: 'items',
|
||||
editTitle: 'edit',
|
||||
copyTitle: 'copy',
|
||||
addTitle: 'add',
|
||||
viewTitle: 'view',
|
||||
filterTitle: 'filter',
|
||||
showTitle: 'showTitle',
|
||||
menu: 'menu',
|
||||
addBtn: 'add',
|
||||
show: 'show',
|
||||
hide: 'hide',
|
||||
open: 'open',
|
||||
shrink: 'shrink',
|
||||
printBtn: 'print',
|
||||
excelBtn: 'excel',
|
||||
updateBtn: 'update',
|
||||
cancelBtn: 'cancel',
|
||||
searchBtn: 'search',
|
||||
emptyBtn: 'empty',
|
||||
menuBtn: 'menu',
|
||||
saveBtn: 'save',
|
||||
viewBtn: 'view',
|
||||
editBtn: 'edit',
|
||||
copyBtn: 'copy',
|
||||
delBtn: 'delete',
|
||||
},
|
||||
login: {
|
||||
title: 'Login ',
|
||||
info: 'BladeX Development Platform',
|
||||
tenantId: 'Please input tenantId',
|
||||
name: 'Please input name',
|
||||
username: 'Please input account',
|
||||
email: 'Please input email',
|
||||
password: 'Please input password',
|
||||
password1: 'Please input confirm password',
|
||||
wechat: 'Wechat',
|
||||
qq: 'QQ',
|
||||
github: 'github',
|
||||
gitee: 'gitee',
|
||||
phone: 'Please input a phone',
|
||||
code: 'Please input a code',
|
||||
submit: 'Login',
|
||||
register: 'Register',
|
||||
back: 'Back',
|
||||
userLogin: 'userLogin',
|
||||
phoneLogin: 'phoneLogin',
|
||||
thirdLogin: 'thirdLogin',
|
||||
ssoLogin: 'ssoLogin',
|
||||
msgText: 'send code',
|
||||
msgSuccess: 'reissued',
|
||||
},
|
||||
navbar: {
|
||||
info: 'info',
|
||||
logOut: 'logout',
|
||||
userinfo: 'userinfo',
|
||||
roleName: 'role name',
|
||||
deptName: 'dept name',
|
||||
currentDeptName: 'current role name',
|
||||
currentRoleName: 'current dept name',
|
||||
switchRoleTo: 'switch role to',
|
||||
switchRole: 'switch role',
|
||||
switchDeptTo: 'switch dept to',
|
||||
switchDept: 'switch dept',
|
||||
switchTip: 'whether to switch identities',
|
||||
switchSuccess: 'switch success',
|
||||
dashboard: 'dashboard',
|
||||
allMenu: 'all menu',
|
||||
lock: 'lock',
|
||||
bug: 'none bug',
|
||||
bugs: 'bug',
|
||||
screenfullF: 'exit screenfull',
|
||||
screenfull: 'screenfull',
|
||||
language: 'language',
|
||||
notice: 'notice',
|
||||
theme: 'theme',
|
||||
color: 'color',
|
||||
},
|
||||
tagsView: {
|
||||
search: 'Search',
|
||||
menu: 'menu',
|
||||
clearCache: 'Clear Cache',
|
||||
closeOthers: 'Close Others',
|
||||
closeAll: 'Close All',
|
||||
},
|
||||
};
|
||||
22
src/lang/index.js
Normal file
22
src/lang/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import Store from '@/store';
|
||||
import elementEnLocale from 'element-plus/es/locale/lang/en';
|
||||
import elementZhLocale from 'element-plus/es/locale/lang/zh-cn';
|
||||
import enLocale from './en';
|
||||
import zhLocale from './zh';
|
||||
|
||||
export const messages = {
|
||||
'en-us': {
|
||||
...enLocale,
|
||||
...elementEnLocale,
|
||||
},
|
||||
'zh-cn': {
|
||||
...zhLocale,
|
||||
...elementZhLocale,
|
||||
},
|
||||
};
|
||||
export const language = Store.getters.language;
|
||||
export default createI18n({
|
||||
locale: language,
|
||||
messages,
|
||||
});
|
||||
137
src/lang/zh.js
Normal file
137
src/lang/zh.js
Normal file
@@ -0,0 +1,137 @@
|
||||
export default {
|
||||
title: 'BladeX微服务平台',
|
||||
confirmTip: '提示',
|
||||
logoutTip: '退出系统, 是否继续?',
|
||||
submitText: '确定',
|
||||
cancelText: '取消',
|
||||
search: '请输入搜索内容',
|
||||
menuTip: '没有发现菜单',
|
||||
common: {
|
||||
condition: '条件',
|
||||
display: '显示',
|
||||
hide: '隐藏',
|
||||
},
|
||||
tip: {
|
||||
select: '请选择',
|
||||
input: '请输入',
|
||||
},
|
||||
upload: {
|
||||
upload: '点击上传',
|
||||
tip: '将文件拖到此处,或',
|
||||
},
|
||||
date: {
|
||||
start: '开始日期',
|
||||
end: '结束日期',
|
||||
t: '今日',
|
||||
y: '昨日',
|
||||
n: '近7天',
|
||||
a: '全部',
|
||||
},
|
||||
form: {
|
||||
printBtn: '打 印',
|
||||
mockBtn: '模 拟',
|
||||
submitBtn: '提 交',
|
||||
emptyBtn: '清 空',
|
||||
},
|
||||
crud: {
|
||||
filter: {
|
||||
addBtn: '新增条件',
|
||||
clearBtn: '清空数据',
|
||||
resetBtn: '清空条件',
|
||||
cancelBtn: '取 消',
|
||||
submitBtn: '确 定',
|
||||
},
|
||||
column: {
|
||||
name: '列名',
|
||||
hide: '隐藏',
|
||||
fixed: '冻结',
|
||||
filters: '过滤',
|
||||
sortable: '排序',
|
||||
index: '顺序',
|
||||
width: '宽度',
|
||||
},
|
||||
tipStartTitle: '当前表格已选择',
|
||||
tipEndTitle: '项',
|
||||
editTitle: '编 辑',
|
||||
copyTitle: '复 制',
|
||||
addTitle: '新 增',
|
||||
viewTitle: '查 看',
|
||||
filterTitle: '过滤条件',
|
||||
showTitle: '列显隐',
|
||||
menu: '操作',
|
||||
addBtn: '新 增',
|
||||
show: '显 示',
|
||||
hide: '隐 藏',
|
||||
open: '展 开',
|
||||
shrink: '收 缩',
|
||||
printBtn: '打 印',
|
||||
excelBtn: '导 出',
|
||||
updateBtn: '修 改',
|
||||
cancelBtn: '取 消',
|
||||
searchBtn: '搜 索',
|
||||
emptyBtn: '清 空',
|
||||
menuBtn: '功 能',
|
||||
saveBtn: '保 存',
|
||||
viewBtn: '查 看',
|
||||
editBtn: '编 辑',
|
||||
copyBtn: '复 制',
|
||||
delBtn: '删 除',
|
||||
},
|
||||
login: {
|
||||
title: '登录 ',
|
||||
info: 'BladeX 微服务平台',
|
||||
tenantId: '请输入租户ID',
|
||||
name: '请输入姓名',
|
||||
username: '请输入账号',
|
||||
email: '请输入邮箱',
|
||||
password: '请输入密码',
|
||||
password1: '请输入确认密码',
|
||||
wechat: '微信',
|
||||
qq: 'QQ',
|
||||
github: 'github',
|
||||
gitee: '码云',
|
||||
phone: '请输入手机号',
|
||||
code: '请输入验证码',
|
||||
submit: '登录',
|
||||
register: '注册',
|
||||
back: '返回',
|
||||
userLogin: '账号密码登录',
|
||||
phoneLogin: '手机号登录',
|
||||
thirdLogin: '第三方登录',
|
||||
ssoLogin: '单点登录',
|
||||
msgText: '发送验证码',
|
||||
msgSuccess: '秒后重发',
|
||||
},
|
||||
navbar: {
|
||||
logOut: '退出登录',
|
||||
userinfo: '个人信息',
|
||||
roleName: '所属角色',
|
||||
deptName: '所属部门',
|
||||
currentDeptName: '当前部门',
|
||||
currentRoleName: '当前角色',
|
||||
switchRoleTo: '角色切换至',
|
||||
switchRole: '角色切换',
|
||||
switchDeptTo: '部门切换至',
|
||||
switchDept: '部门切换',
|
||||
switchTip: '是否进行身份切换',
|
||||
switchSuccess: '切换成功',
|
||||
dashboard: '首页',
|
||||
allMenu: '全量菜单',
|
||||
lock: '锁屏',
|
||||
bug: '没有错误日志',
|
||||
bugs: '条错误日志',
|
||||
screenfullF: '退出全屏',
|
||||
screenfull: '全屏',
|
||||
language: '中英文',
|
||||
notice: '消息通知',
|
||||
theme: '主题',
|
||||
color: '换色',
|
||||
},
|
||||
tagsView: {
|
||||
search: '搜索',
|
||||
menu: '更多',
|
||||
clearCache: '清除缓存',
|
||||
closeOthers: '关闭其它',
|
||||
closeAll: '关闭所有',
|
||||
},
|
||||
};
|
||||
388
src/mac/index.vue
Normal file
388
src/mac/index.vue
Normal file
@@ -0,0 +1,388 @@
|
||||
<template>
|
||||
<div class="mac_bg"></div>
|
||||
<div class="desktop">
|
||||
<div class="top">
|
||||
<el-dropdown trigger="click">
|
||||
<div class="logo"><i class="iconfont iconicon_setting"></i></div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>
|
||||
<div>{{ userInfo.userName }}</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<div @click="switchTheme">退出主题</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">{{ $t('navbar.logOut') }}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<div class="space"></div>
|
||||
<div class="status">
|
||||
<div class="audio">
|
||||
<i class="iconfont icon-changyongtubiao-xianxingdaochu-zhuanqu-39"></i>
|
||||
</div>
|
||||
<div class="datetime">{{ timeString }}</div>
|
||||
<div class="notification">
|
||||
<i class="iconfont icon-changyongtubiao-xianxingdaochu-zhuanqu-25"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="desktop-app">
|
||||
<template v-for="item in deskTopAppList" :key="item.key">
|
||||
<div class="app-item" @click="openApp(item)" v-if="!item.hideInDesktop">
|
||||
<div class="icon" :style="{ backgroundColor: item.iconBgColor, color: item.iconColor }">
|
||||
<i class="iconfont" :class="item[iconKey]"></i>
|
||||
</div>
|
||||
<div class="title">{{ item[labelKey] }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="space"></div>
|
||||
<div class="dock">
|
||||
<template v-for="item in openAppList" :key="item.key">
|
||||
<div class="item" @click="openApp(item)">
|
||||
<i
|
||||
:style="{ backgroundColor: item.iconBgColor, color: item.iconColor }"
|
||||
class="iconfont"
|
||||
:class="item[iconKey]"
|
||||
></i>
|
||||
<small style="margin-top: 5px; font-size: 10px">{{ item[labelKey] }}</small>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="space"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import $Mode from './mode/index';
|
||||
import index from '@/mixins/index';
|
||||
import topLock from '@/page/index/top/top-lock.vue';
|
||||
|
||||
export default {
|
||||
mixins: [index],
|
||||
components: {
|
||||
topLock,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
app: false,
|
||||
timeString: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['menu', 'tagList', 'tagWel', 'tag', 'userInfo', 'isMacOs']),
|
||||
labelKey() {
|
||||
return this.website.menu.label;
|
||||
},
|
||||
pathKey() {
|
||||
return this.website.menu.path;
|
||||
},
|
||||
hrefKey() {
|
||||
return this.website.menu.href;
|
||||
},
|
||||
childrenKey() {
|
||||
return this.website.menu.children;
|
||||
},
|
||||
queryKey() {
|
||||
return this.website.menu.query;
|
||||
},
|
||||
iconKey() {
|
||||
return this.website.menu.icon;
|
||||
},
|
||||
menuList() {
|
||||
let result = [];
|
||||
const findMenu = list => {
|
||||
list.forEach(ele => {
|
||||
if (ele[this.childrenKey] && ele[this.childrenKey].length !== 0) {
|
||||
findMenu(ele[this.childrenKey]);
|
||||
} else {
|
||||
result.push(ele);
|
||||
}
|
||||
});
|
||||
};
|
||||
findMenu(this.menu);
|
||||
return result;
|
||||
},
|
||||
deskTopAppList() {
|
||||
return this.menuList;
|
||||
},
|
||||
openAppList() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.startTimer();
|
||||
this.$store.dispatch('GetMenu');
|
||||
},
|
||||
methods: {
|
||||
switchTheme() {
|
||||
this.$store.commit('SET_THEME_NAME', '');
|
||||
this.$router.push(this.tagWel);
|
||||
setTimeout(() => location.reload());
|
||||
},
|
||||
logout() {
|
||||
this.$store.commit('SET_THEME_NAME', '');
|
||||
this.$store.dispatch('LogOut').then(() => {
|
||||
this.$router.push({ path: '/login' });
|
||||
setTimeout(() => location.reload());
|
||||
});
|
||||
},
|
||||
startTimer() {
|
||||
setInterval(() => {
|
||||
this.timeString = this.$dayjs().format('YYYY年MM月DD日 HH:mm');
|
||||
}, 1000);
|
||||
},
|
||||
openApp(item) {
|
||||
$Mode({
|
||||
title: item[this.labelKey],
|
||||
path: item[this.hrefKey] ? item[this.hrefKey] : item[this.pathKey],
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.top .el-dropdown {
|
||||
color: white !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
</style>
|
||||
<style scoped>
|
||||
.desktop {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
text-shadow: 0px 2px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.top {
|
||||
height: 28px;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(20px);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 14px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0px 5px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.space {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0px 15px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logo .el-select {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.logo:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.logo .iconfont {
|
||||
font-size: 16px;
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
.menu .item:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.status {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.audio .iconfont,
|
||||
.notification .iconfont {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.datetime,
|
||||
.audio,
|
||||
.notification {
|
||||
cursor: pointer;
|
||||
padding: 0px 10px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.datetime:hover,
|
||||
.audio:hover,
|
||||
.notification:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.body {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.footer .dock {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 10px;
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.dock .item {
|
||||
padding: 3px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dock .dot {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.dock .item .iconfont {
|
||||
cursor: pointer;
|
||||
border-radius: 20px;
|
||||
padding: 2px;
|
||||
display: inline-block;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.3s, margin 0.3s;
|
||||
}
|
||||
|
||||
.dock .item:hover .iconfont {
|
||||
transform: scale(2) translateY(-10px);
|
||||
margin: 0px 15px;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.dock .nearby .iconfont {
|
||||
transform: scale(1.6) translateY(-8px);
|
||||
margin: 0px 12px;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.dock .nearby1 .iconfont {
|
||||
transform: scale(1.2) translateY(-6px);
|
||||
margin: 0px 9px;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.dock .nearby2 .iconfont {
|
||||
transform: scale(1.1) translateY(-5px);
|
||||
margin: 0px 7px;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.desktop-app {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-end;
|
||||
padding: 20px;
|
||||
flex-wrap: wrap-reverse;
|
||||
}
|
||||
|
||||
.app-item {
|
||||
margin: 5px 10px;
|
||||
padding: 5px;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
text-shadow: 0px 0px 2px rgb(0 0 0 / 50%);
|
||||
cursor: pointer;
|
||||
border-radius: 10px;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.app-item .icon {
|
||||
border-radius: 10px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.app-item .iconfont {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.app-item .title {
|
||||
font-size: 12px;
|
||||
width: 50px;
|
||||
margin-top: 5px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-item:hover {
|
||||
border: 2px solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
</style>
|
||||
62
src/mac/lock.vue
Normal file
62
src/mac/lock.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="mac_bg"></div>
|
||||
<div class="login animate__animated" :class="{ animate__bounceOut: pass }">
|
||||
<div class="head">
|
||||
<img
|
||||
src="https://bladex.cn/images/logo-small.png"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div class="message">{{ userInfo.username }}</div>
|
||||
<div class="form">
|
||||
<div class="item" style="width: 320px" :class="passwdError ? 'error' : ''">
|
||||
<input class="password" placeholder="password here..." v-model="passwd" type="password" />
|
||||
<i class="iconfont el-icon-unlock" @click="handleLogin"></i>
|
||||
<i class="iconfont icon-tuichu" @click="handleLogout"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
passwdError: false,
|
||||
passwd: '',
|
||||
pass: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['userInfo', 'tag', 'lockPasswd']),
|
||||
},
|
||||
methods: {
|
||||
handleLogout() {
|
||||
this.$store.dispatch('LogOut').then(() => {
|
||||
this.$router.push({ path: '/login' });
|
||||
});
|
||||
},
|
||||
handleLogin() {
|
||||
if (this.passwd !== this.lockPasswd) {
|
||||
this.passwd = '';
|
||||
this.passwdError = true;
|
||||
setTimeout(() => {
|
||||
this.passwdError = false;
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
this.pass = true;
|
||||
setTimeout(() => {
|
||||
this.$store.commit('CLEAR_LOCK');
|
||||
this.$router.push({
|
||||
path: this.tag.value,
|
||||
});
|
||||
}, 1000);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@use './login.scss';
|
||||
</style>
|
||||
134
src/mac/login.scss
Normal file
134
src/mac/login.scss
Normal file
@@ -0,0 +1,134 @@
|
||||
@keyframes loginErrorAnimation {
|
||||
0% {
|
||||
margin-left: -30px;
|
||||
}
|
||||
50% {
|
||||
margin-left: 30px;
|
||||
}
|
||||
100% {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.login {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
margin-top: -100px;
|
||||
z-index: 99999;
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
.head {
|
||||
padding: 3px;
|
||||
background-size: 40% auto;
|
||||
background-position: center center;
|
||||
height: 150px;
|
||||
width: 150px;
|
||||
border-radius: 100%;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0px 0px 5px 5px rgba(0, 0, 0, 0.1);
|
||||
margin-top: -50px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.head img {
|
||||
border-radius: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 20px;
|
||||
font-size: 20px;
|
||||
text-shadow: 0px 0px 2px 2px rgba(0, 0, 0, 0.3);
|
||||
color: #eee;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.password {
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
.password-in {
|
||||
width: 155px;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: -50px;
|
||||
transition: right 0.3s;
|
||||
}
|
||||
|
||||
.click-enable {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.error {
|
||||
animation: loginErrorAnimation 0.2s ease 3;
|
||||
}
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
::-moz-placeholder {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
:-ms-input-placeholder {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
width: 280px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item input {
|
||||
color: white;
|
||||
outline: none;
|
||||
border: none;
|
||||
margin: 5px 0;
|
||||
font-size: 16px;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
padding: 8px 24px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item .iconfont {
|
||||
margin-left: 10px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
font-size: 18px;
|
||||
border-radius: 100%;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
text-align: center;
|
||||
line-height: 36px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.item .iconfont:hover {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
72
src/mac/login.vue
Normal file
72
src/mac/login.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div class="mac_bg"></div>
|
||||
<div class="login animate__animated" :class="{ animate__bounceOut: pass }">
|
||||
<div class="head">
|
||||
<img
|
||||
src="https://bladex.cn/images/logo-small.png"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div class="message">Login Please</div>
|
||||
<div class="form">
|
||||
<div class="item" :class="isUserNameError ? 'error' : ''">
|
||||
<input class="account" placeholder="account here..." v-model="form.username" type="email" />
|
||||
</div>
|
||||
<div class="item" :class="isUserPasswordError ? 'error' : ''">
|
||||
<input
|
||||
class="password"
|
||||
placeholder="password here..."
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
/>
|
||||
<i class="iconfont icon-send" @click="handleLogin"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
pass: false,
|
||||
isUserNameError: false,
|
||||
isUserPasswordError: false,
|
||||
form: {
|
||||
username: '',
|
||||
password: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['tagWel']),
|
||||
},
|
||||
methods: {
|
||||
handleLogin() {
|
||||
if (this.form.username === '') {
|
||||
this.isUserNameError = true;
|
||||
setTimeout(() => {
|
||||
this.isUserNameError = false;
|
||||
}, 1000);
|
||||
return;
|
||||
} else if (this.form.password === '') {
|
||||
this.isUserPasswordError = true;
|
||||
setTimeout(() => {
|
||||
this.isUserPasswordError = false;
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {
|
||||
this.pass = true;
|
||||
setTimeout(() => {
|
||||
this.$router.push(this.tagWel);
|
||||
}, 1000);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@use './login.scss';
|
||||
</style>
|
||||
18
src/mac/mode/index.js
Normal file
18
src/mac/mode/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import MessageConstructor from './index.vue';
|
||||
import { createVNode, render } from 'vue';
|
||||
|
||||
export default (function () {
|
||||
return (opts = {}) => {
|
||||
let options = {
|
||||
app: opts,
|
||||
};
|
||||
const parent = document.createElement('div');
|
||||
let instance = createVNode(MessageConstructor, options);
|
||||
instance.props.onDestroy = () => {
|
||||
render(null, parent);
|
||||
};
|
||||
render(instance, parent);
|
||||
document.body.appendChild(parent.firstElementChild);
|
||||
return instance;
|
||||
};
|
||||
})();
|
||||
483
src/mac/mode/index.vue
Normal file
483
src/mac/mode/index.vue
Normal file
@@ -0,0 +1,483 @@
|
||||
<template>
|
||||
<div
|
||||
class="moveBg"
|
||||
v-show="isShow"
|
||||
@click="handleFocus"
|
||||
@mousemove="mouseMove"
|
||||
@mouseup="mouseUp"
|
||||
@mouseleave.stop="mouseLeave"
|
||||
:style="{ pointerEvents: isBoxResizing || isBoxMoving ? 'auto' : 'none' }"
|
||||
>
|
||||
<div
|
||||
class="box"
|
||||
:style="{
|
||||
left: nowRect.left + 'px',
|
||||
top: nowRect.top + 'px',
|
||||
bottom: nowRect.bottom + 'px',
|
||||
right: nowRect.right + 'px',
|
||||
}"
|
||||
:class="getExtBoxClasses()"
|
||||
>
|
||||
<div class="box-top">
|
||||
<div class="box-top-left" @mousedown="resizeMouseDown"></div>
|
||||
<div class="box-top-center" @mousedown="resizeMouseDown"></div>
|
||||
<div class="box-top-right" @mousedown="resizeMouseDown"></div>
|
||||
</div>
|
||||
<div class="box-center">
|
||||
<div class="box-center-left" @mousedown="resizeMouseDown"></div>
|
||||
<div class="box-center-center loader">
|
||||
<div
|
||||
class="app-bar"
|
||||
:style="{ backgroundColor: app.tabbarBgColor }"
|
||||
@mousedown.stop="positionMouseDown"
|
||||
v-on:dblclick="appBarDoubleClicked"
|
||||
>
|
||||
<div class="controll">
|
||||
<div class="close" @click.stop="close"></div>
|
||||
<div
|
||||
class="full"
|
||||
:class="app.disableResize ? 'full-disabled' : ''"
|
||||
@click.stop="switchFullScreen"
|
||||
></div>
|
||||
</div>
|
||||
<div class="title" :style="{ color: app.tabbarTextColor }">{{ app.title }}</div>
|
||||
</div>
|
||||
<iframe :src="location + app.path" frameborder="0" class="iframe"></iframe>
|
||||
</div>
|
||||
<div class="box-center-right" @mousedown="resizeMouseDown"></div>
|
||||
</div>
|
||||
<div class="box-bottom">
|
||||
<div class="box-bottom-left" @mousedown="resizeMouseDown"></div>
|
||||
<div class="box-bottom-center" @mousedown="resizeMouseDown"></div>
|
||||
<div class="box-bottom-right" @mousedown="resizeMouseDown"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isURL } from 'utils/validate';
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
app: Object,
|
||||
onDestroy: Function,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShow: true,
|
||||
defaultIndex: 10,
|
||||
activeIndex: 20,
|
||||
isBoxMoving: false,
|
||||
startPosition: { x: 0, y: 0 },
|
||||
nowRect: {
|
||||
left: 100,
|
||||
right: 100,
|
||||
top: 100,
|
||||
bottom: 100,
|
||||
},
|
||||
startRect: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
isBoxResizing: false,
|
||||
moveDirection: false,
|
||||
isMaxShowing: false,
|
||||
isFullScreen: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
location() {
|
||||
return isURL(this.app.path) ? '' : window.location.origin;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (this.app.width) {
|
||||
this.nowRect.left = this.nowRect.right = (document.body.clientWidth - this.app.width) / 2;
|
||||
}
|
||||
if (this.app.height) {
|
||||
this.nowRect.bottom = (document.body.clientHeight - this.app.height) / 2 + 50;
|
||||
this.nowRect.top = (document.body.clientHeight - this.app.height) / 2 - 50;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleFocus() {
|
||||
let list = document.getElementsByClassName('moveBg');
|
||||
if (list.length == 1) return;
|
||||
let max = 0;
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
let ele = list[i];
|
||||
let box = ele.getElementsByClassName('box')[0].style;
|
||||
let zIndex = Number(box.zIndex);
|
||||
if (max < zIndex) {
|
||||
max = zIndex;
|
||||
}
|
||||
}
|
||||
max = max + 1;
|
||||
this.$el.getElementsByClassName('box')[0].style.zIndex = max;
|
||||
},
|
||||
close() {
|
||||
this.isShow = false;
|
||||
this.onDestroy();
|
||||
},
|
||||
switchFullScreen() {
|
||||
if (this.app.disableResize) {
|
||||
return;
|
||||
}
|
||||
this.isFullScreen = !this.isFullScreen;
|
||||
if (this.isFullScreen) {
|
||||
this.isMaxShowing = true;
|
||||
} else {
|
||||
this.isMaxShowing = false;
|
||||
}
|
||||
},
|
||||
getExtBoxClasses() {
|
||||
let str = '';
|
||||
if (!this.isBoxResizing && !this.isBoxMoving) {
|
||||
str += 'box-animation ';
|
||||
}
|
||||
if (this.isMaxShowing) {
|
||||
str += 'isMaxShowing ';
|
||||
}
|
||||
if (this.isFullScreen) {
|
||||
str += 'isFullScreen ';
|
||||
}
|
||||
if (this.app.disableResize) {
|
||||
str += 'resize-disabled ';
|
||||
}
|
||||
if (this.app.isTop) {
|
||||
str += 'isTop ';
|
||||
}
|
||||
return str;
|
||||
},
|
||||
appBarDoubleClicked() {
|
||||
if (this.app.disableResize) {
|
||||
return;
|
||||
}
|
||||
this.isMaxShowing = !this.isMaxShowing;
|
||||
if (!this.isMaxShowing) {
|
||||
this.isFullScreen = false;
|
||||
}
|
||||
},
|
||||
positionMouseDown(e) {
|
||||
if (this.isFullScreen || this.isMaxShowing) {
|
||||
return;
|
||||
}
|
||||
this.startRect = {
|
||||
left: this.nowRect.left,
|
||||
right: this.nowRect.right,
|
||||
top: this.nowRect.top,
|
||||
bottom: this.nowRect.bottom,
|
||||
};
|
||||
this.startPosition.x = e.clientX;
|
||||
this.startPosition.y = e.clientY;
|
||||
this.isBoxMoving = true;
|
||||
},
|
||||
mouseUp() {
|
||||
this.isBoxMoving = false;
|
||||
this.isBoxResizing = false;
|
||||
this.moveDirection = false;
|
||||
},
|
||||
mouseLeave() {
|
||||
this.isBoxMoving = false;
|
||||
this.isBoxResizing = false;
|
||||
this.moveDirection = false;
|
||||
},
|
||||
mouseMove(e) {
|
||||
if (this.isBoxResizing) {
|
||||
this.isFullScreen = false;
|
||||
this.isMaxShowing = false;
|
||||
switch (this.moveDirection) {
|
||||
case 'box-top-left':
|
||||
this.nowRect.top = this.startRect.top + (e.clientY - this.startPosition.y);
|
||||
this.nowRect.left = this.startRect.left + (e.clientX - this.startPosition.x);
|
||||
break;
|
||||
case 'box-top-center':
|
||||
this.nowRect.top = this.startRect.top + (e.clientY - this.startPosition.y);
|
||||
break;
|
||||
case 'box-top-right':
|
||||
this.nowRect.top = this.startRect.top + (e.clientY - this.startPosition.y);
|
||||
this.nowRect.right = this.startRect.right - (e.clientX - this.startPosition.x);
|
||||
break;
|
||||
case 'box-center-left':
|
||||
this.nowRect.left = this.startRect.left + (e.clientX - this.startPosition.x);
|
||||
break;
|
||||
case 'box-bottom-left':
|
||||
this.nowRect.left = this.startRect.left + (e.clientX - this.startPosition.x);
|
||||
this.nowRect.bottom = this.startRect.bottom - (e.clientY - this.startPosition.y);
|
||||
break;
|
||||
case 'box-bottom-center':
|
||||
this.nowRect.bottom = this.startRect.bottom - (e.clientY - this.startPosition.y);
|
||||
break;
|
||||
case 'box-center-right':
|
||||
this.nowRect.right = this.startRect.right - (e.clientX - this.startPosition.x);
|
||||
break;
|
||||
case 'box-bottom-right':
|
||||
this.nowRect.right = this.startRect.right - (e.clientX - this.startPosition.x);
|
||||
this.nowRect.bottom = this.startRect.bottom - (e.clientY - this.startPosition.y);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.isBoxMoving) {
|
||||
this.isFullScreen = false;
|
||||
this.isMaxShowing = false;
|
||||
this.nowRect.left = this.startRect.left + (e.clientX - this.startPosition.x);
|
||||
this.nowRect.right = this.startRect.right - (e.clientX - this.startPosition.x);
|
||||
this.nowRect.top = this.startRect.top + (e.clientY - this.startPosition.y);
|
||||
this.nowRect.bottom = this.startRect.bottom - (e.clientY - this.startPosition.y);
|
||||
return;
|
||||
}
|
||||
},
|
||||
resizeMouseDown(e) {
|
||||
if (this.app.disableResize) {
|
||||
return;
|
||||
}
|
||||
if (this.isFullScreen || this.isMaxShowing) {
|
||||
return;
|
||||
}
|
||||
this.startRect = {
|
||||
left: this.nowRect.left,
|
||||
top: this.nowRect.top,
|
||||
right: this.nowRect.right,
|
||||
bottom: this.nowRect.bottom,
|
||||
};
|
||||
this.startPosition.x = e.clientX;
|
||||
this.startPosition.y = e.clientY;
|
||||
this.isBoxResizing = true;
|
||||
this.moveDirection = e.target.className;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.iframe {
|
||||
width: 100%;
|
||||
height: calc(100% - 50px);
|
||||
margin-top: 5px;
|
||||
border-right: 5px;
|
||||
}
|
||||
|
||||
.moveBg {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.box {
|
||||
--resize: 5px;
|
||||
--resize-bg: transparent;
|
||||
--resize-main: transparent;
|
||||
--resize-bg-main: transparent;
|
||||
}
|
||||
|
||||
.box {
|
||||
position: absolute;
|
||||
pointer-events: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.box-top {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.box-top-left {
|
||||
width: var(--resize);
|
||||
height: var(--resize);
|
||||
background: var(--resize-bg);
|
||||
cursor: nw-resize;
|
||||
}
|
||||
|
||||
.box-top-center {
|
||||
height: var(--resize);
|
||||
background: var(--resize-bg-main);
|
||||
cursor: n-resize;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.box-top-right {
|
||||
width: var(--resize);
|
||||
height: var(--resize);
|
||||
background: var(--resize-bg);
|
||||
cursor: ne-resize;
|
||||
}
|
||||
|
||||
.box-center {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.box-center-left {
|
||||
width: var(--resize);
|
||||
height: 100%;
|
||||
background: var(--resize-bg-main);
|
||||
cursor: w-resize;
|
||||
}
|
||||
|
||||
.box-center-center {
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 0px 3px #999;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(20px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.isTop .box-center-center {
|
||||
box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.5);
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.box-animation {
|
||||
transition: width 0.1s, height 0.1s, left 0.1s, right 0.1s, top 0.1s, bottom 0.1s;
|
||||
}
|
||||
|
||||
.isMaxShowing {
|
||||
left: -5px !important;
|
||||
right: -5px !important;
|
||||
top: 24px !important;
|
||||
bottom: 47px !important;
|
||||
height: calc(100% - 24px);
|
||||
}
|
||||
|
||||
.isFullScreen {
|
||||
position: fixed !important;
|
||||
z-index: 999 !important;
|
||||
bottom: -5px !important;
|
||||
}
|
||||
|
||||
.isMaxShowing .box-center-center,
|
||||
.isFullScreen .box-center-center {
|
||||
border-radius: 0px !important;
|
||||
}
|
||||
|
||||
.box-center-right {
|
||||
width: var(--resize);
|
||||
height: 100%;
|
||||
background: var(--resize-bg-main);
|
||||
cursor: e-resize;
|
||||
}
|
||||
|
||||
.box-bottom {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.box-bottom-left {
|
||||
width: var(--resize);
|
||||
height: var(--resize);
|
||||
background: var(--resize-bg);
|
||||
cursor: sw-resize;
|
||||
}
|
||||
|
||||
.box-bottom-center {
|
||||
height: var(--resize);
|
||||
background: var(--resize-bg-main);
|
||||
cursor: s-resize;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.box-bottom-right {
|
||||
width: var(--resize);
|
||||
height: var(--resize);
|
||||
background: var(--resize-bg);
|
||||
cursor: se-resize;
|
||||
}
|
||||
|
||||
.loader {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.app-bar {
|
||||
height: 40px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
backdrop-filter: blur(20px);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-bar .controll {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.app-bar .controll div {
|
||||
border-radius: 100%;
|
||||
height: 11px;
|
||||
width: 11px;
|
||||
margin-right: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.app-bar .title {
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
margin-right: 80px;
|
||||
font-weight: 500;
|
||||
text-shadow: none;
|
||||
font-size: 13px;
|
||||
cursor: move;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.controll .close {
|
||||
background: #fc605c;
|
||||
border: 1px solid #fc635d;
|
||||
}
|
||||
|
||||
.controll .min {
|
||||
background: #fcbb40;
|
||||
border: 1px solid #f8b438;
|
||||
}
|
||||
|
||||
.controll .full {
|
||||
background: #34c648;
|
||||
border: 1px solid #2bc03f;
|
||||
}
|
||||
|
||||
.full-disabled {
|
||||
background: #ccc !important;
|
||||
border: 1px solid #ccc !important;
|
||||
}
|
||||
|
||||
.resize-disabled .box-top-left,
|
||||
.resize-disabled .box-top-center,
|
||||
.resize-disabled .box-top-right,
|
||||
.resize-disabled .box-center-left,
|
||||
.resize-disabled .box-center-right,
|
||||
.resize-disabled .box-bottom-left,
|
||||
.resize-disabled .box-bottom-center,
|
||||
.resize-disabled .box-bottom-right {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.app-body {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
81
src/main.js
Normal file
81
src/main.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import { createApp } from 'vue';
|
||||
import website from './config/website';
|
||||
import axios from './axios';
|
||||
import router from './router/';
|
||||
import store from './store';
|
||||
import i18n from './lang/';
|
||||
import { language, messages } from './lang/';
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
|
||||
import ElementPlus from 'element-plus';
|
||||
import 'element-plus/dist/index.css';
|
||||
import Avue from '@smallwei/avue';
|
||||
import '@smallwei/avue/lib/index.css';
|
||||
import avueUeditor from 'avue-plugin-ueditor';
|
||||
import crudCommon from '@/mixins/crud.js';
|
||||
import { getScreen, findColumn } from './utils/util';
|
||||
import './permission';
|
||||
import error from './error';
|
||||
import App from './App.vue';
|
||||
import 'animate.css';
|
||||
import dayjs from 'dayjs';
|
||||
import 'styles/common.scss';
|
||||
// 系统组件
|
||||
import debug from './debug';
|
||||
import VueClipboard from 'vue3-clipboard';
|
||||
import highlight from './components/highlight/main.vue';
|
||||
import codeEditor from './components/code-editor/main.vue';
|
||||
import cronEditor from './components/cron-editor/main.vue';
|
||||
import basicBlock from './components/basic-block/main.vue';
|
||||
import basicContainer from './components/basic-container/main.vue';
|
||||
import thirdRegister from './components/third-register/main.vue';
|
||||
import flowDesign from './components/flow-design/main.vue';
|
||||
import flowDesignStep from './components/flow-design-step/main.vue';
|
||||
// 业务组件
|
||||
import codeSetting from './views/tool/codesetting.vue';
|
||||
import formSetting from './views/tool/formsetting.vue';
|
||||
import tenantPackage from './views/system/tenantpackage.vue';
|
||||
import tenantDatasource from './views/system/tenantdatasource.vue';
|
||||
|
||||
window.$crudCommon = crudCommon;
|
||||
debug();
|
||||
window.axios = axios;
|
||||
const app = createApp(App);
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component);
|
||||
}
|
||||
app.component('basicContainer', basicContainer);
|
||||
app.component('basicBlock', basicBlock);
|
||||
app.component('highlight', highlight);
|
||||
app.component('codeEditor', codeEditor);
|
||||
app.component('cronEditor', cronEditor);
|
||||
app.component('thirdRegister', thirdRegister);
|
||||
app.component('flowDesign', flowDesign);
|
||||
app.component('flowDesignStep', flowDesignStep);
|
||||
app.component('codeSetting', codeSetting);
|
||||
app.component('formSetting', formSetting);
|
||||
app.component('tenantPackage', tenantPackage);
|
||||
app.component('tenantDatasource', tenantDatasource);
|
||||
app.config.globalProperties.$app = app;
|
||||
app.config.globalProperties.$dayjs = dayjs;
|
||||
app.config.globalProperties.website = website;
|
||||
app.config.globalProperties.getScreen = getScreen;
|
||||
app.config.globalProperties.findColumn = findColumn;
|
||||
app.use(error);
|
||||
app.use(i18n);
|
||||
app.use(store);
|
||||
app.use(router);
|
||||
app.use(ElementPlus, {
|
||||
locale: messages[language],
|
||||
});
|
||||
app.use(Avue, {
|
||||
axios,
|
||||
calcHeight: 10,
|
||||
locale: language,
|
||||
i18n: (key, options) => i18n.global.t(key, options),
|
||||
});
|
||||
app.use(avueUeditor, { axios });
|
||||
app.use(VueClipboard, {
|
||||
autoSetContainer: true,
|
||||
appendToBody: true, // 这可以帮助解决一些更复杂的使用场景下的问题
|
||||
});
|
||||
app.mount('#app');
|
||||
217
src/mixins/crud.js
Normal file
217
src/mixins/crud.js
Normal file
@@ -0,0 +1,217 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default (app, option = {}) => {
|
||||
let optionObj = import.meta.glob(`../option/**/**`)[`../option/${option.name}.js`];
|
||||
let apiObj = import.meta.glob(`../api/**/**`)[`../api/${option.name}.js`];
|
||||
let mixins = {
|
||||
data() {
|
||||
return {
|
||||
data: [],
|
||||
form: {},
|
||||
params: {},
|
||||
option: {},
|
||||
api: {},
|
||||
loading: false,
|
||||
page: {},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
optionObj().then(mode => (this.option = mode.default));
|
||||
apiObj().then(mode => {
|
||||
this.api = mode;
|
||||
this.getList();
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['userInfo', 'permission', 'roles']),
|
||||
ids() {
|
||||
const ids = [];
|
||||
this.selectionList.forEach(ele => {
|
||||
ids.push(ele[this.rowKey]);
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
bindVal() {
|
||||
return {
|
||||
ref: 'crud',
|
||||
option: this.option,
|
||||
data: this.data,
|
||||
tableLoading: this.loading,
|
||||
};
|
||||
},
|
||||
onEvent() {
|
||||
return {
|
||||
'size-change': this.sizeChange,
|
||||
'current-change': this.currentChange,
|
||||
'row-save': this.rowSave,
|
||||
'row-update': this.rowUpdate,
|
||||
'row-del': this.rowDel,
|
||||
'refresh-change': this.refreshChange,
|
||||
'search-reset': this.searchChange,
|
||||
'search-change': this.searchChange,
|
||||
};
|
||||
},
|
||||
rowKey() {
|
||||
return option.rowKey || 'id';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
const callback = () => {
|
||||
this.loading = true;
|
||||
this.data = [];
|
||||
this.api[option.list || 'getList'](
|
||||
this.page.currentPage,
|
||||
this.page.pageSize,
|
||||
this.params
|
||||
).then(res => {
|
||||
this.loading = false;
|
||||
let data;
|
||||
if (option.res) {
|
||||
data = option.res(res.data);
|
||||
} else {
|
||||
data = res.data.data;
|
||||
}
|
||||
this.page.total = data[option.total || 'total'];
|
||||
const result = data[option.data || 'records'];
|
||||
this.data = result;
|
||||
if (this.listAfter) {
|
||||
this.listAfter(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
if (this.listBefore) {
|
||||
this.listBefore();
|
||||
}
|
||||
callback();
|
||||
},
|
||||
rowSave(row, done, loading) {
|
||||
const callback = () => {
|
||||
delete this.form.params;
|
||||
this.api[option.add || 'add'](this.form)
|
||||
.then(data => {
|
||||
this.getList();
|
||||
if (this.addAfter) {
|
||||
this.addAfter(data);
|
||||
} else {
|
||||
this.$message.success('新增成功');
|
||||
}
|
||||
done();
|
||||
})
|
||||
.catch(() => {
|
||||
loading();
|
||||
});
|
||||
};
|
||||
if (this.addBefore) {
|
||||
this.addBefore();
|
||||
}
|
||||
callback();
|
||||
},
|
||||
rowUpdate(row, index, done, loading) {
|
||||
const callback = () => {
|
||||
delete this.form.params;
|
||||
this.api[option.update || 'update'](this.form)
|
||||
.then(data => {
|
||||
this.getList();
|
||||
if (this.updateAfter) {
|
||||
this.updateAfter(data);
|
||||
} else {
|
||||
this.$message.success('更新成功');
|
||||
}
|
||||
done();
|
||||
})
|
||||
.catch(() => {
|
||||
loading();
|
||||
});
|
||||
};
|
||||
if (this.updateBefore) {
|
||||
this.updateBefore();
|
||||
}
|
||||
callback();
|
||||
},
|
||||
rowDel(row, index) {
|
||||
const callback = () => {
|
||||
this.api[option.del || 'del'](row[this.rowKey], row).then(data => {
|
||||
this.getList();
|
||||
if (this.delAfter) {
|
||||
this.delAfter(data, row, index);
|
||||
} else {
|
||||
this.$message.success('删除成功');
|
||||
}
|
||||
});
|
||||
};
|
||||
if (this.delBefore) {
|
||||
this.delBefore();
|
||||
callback();
|
||||
} else {
|
||||
this.$confirm('确定将选择数据删除?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
this.api[option.del || 'remove'](this.ids).then(data => {
|
||||
this.getList();
|
||||
if (this.delMultiAfter) {
|
||||
this.delMultiAfter(data, this.ids);
|
||||
} else {
|
||||
this.$message.success('删除成功');
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
searchChange(params, done) {
|
||||
if (done) done();
|
||||
this.params = params;
|
||||
this.page.currentPage = 1;
|
||||
this.getList();
|
||||
},
|
||||
dateChange(date) {
|
||||
if (date) {
|
||||
this.params.createTime_dategt = date[0];
|
||||
this.params.createTime_datelt = date[1];
|
||||
} else {
|
||||
delete this.params.createTime_dategt;
|
||||
delete this.params.createTime_datelt;
|
||||
}
|
||||
this.page.currentPage = 1;
|
||||
this.getList();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud.toggleSelection();
|
||||
},
|
||||
refreshChange() {
|
||||
this.getList();
|
||||
},
|
||||
sizeChange(val) {
|
||||
this.page.currentPage = 1;
|
||||
this.page.pageSize = val;
|
||||
this.getList();
|
||||
},
|
||||
currentChange(val) {
|
||||
this.page.currentPage = val;
|
||||
this.getList();
|
||||
},
|
||||
},
|
||||
};
|
||||
app.mixins = app.mixins || [];
|
||||
app.mixins.push(mixins);
|
||||
return app;
|
||||
};
|
||||
45
src/mixins/index.js
Normal file
45
src/mixins/index.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { validatenull } from '@/utils/validate';
|
||||
import { getStore } from '@/utils/store.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
//刷新token锁
|
||||
refreshLock: false,
|
||||
//刷新token的时间
|
||||
refreshTime: '',
|
||||
};
|
||||
},
|
||||
created() {
|
||||
//实时检测刷新token
|
||||
//this.refreshToken();
|
||||
},
|
||||
methods: {
|
||||
//实时检测刷新token
|
||||
refreshToken() {
|
||||
this.refreshTime = setInterval(() => {
|
||||
const token =
|
||||
getStore({
|
||||
name: 'token',
|
||||
debug: true,
|
||||
}) || {};
|
||||
let date1 = this.$dayjs(token.datetime);
|
||||
let date2 = this.$dayjs();
|
||||
const date = date2.diff(date1, 'seconds');
|
||||
if (validatenull(date)) return;
|
||||
if (date >= this.website.tokenTime && !this.refreshLock) {
|
||||
this.refreshLock = true;
|
||||
this.$store
|
||||
.dispatch('RefreshToken')
|
||||
.then(() => {
|
||||
this.refreshLock = false;
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
this.refreshLock = false;
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
},
|
||||
};
|
||||
9
src/mockProdServer.js
Normal file
9
src/mockProdServer.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
|
||||
|
||||
import menuModule from '../mock/menu';
|
||||
import userModule from '../mock/user';
|
||||
import crudModule from '../mock/crud';
|
||||
|
||||
export const setupProdMockServer = () => {
|
||||
createProdMockServer([...menuModule, ...userModule, ...crudModule]);
|
||||
};
|
||||
515
src/option/job/jobinfo.js
Normal file
515
src/option/job/jobinfo.js
Normal file
@@ -0,0 +1,515 @@
|
||||
export const timeExpressionTypeDic = [
|
||||
{
|
||||
label: 'API',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: 'CRON',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '固定频率(毫秒)',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: '固定延迟(毫秒)',
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
label: '工作流',
|
||||
value: 5,
|
||||
},
|
||||
];
|
||||
|
||||
export const executeTypeDic = [
|
||||
{
|
||||
label: '单机执行',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '广播执行',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: 'MapReduce',
|
||||
value: 3,
|
||||
},
|
||||
];
|
||||
|
||||
export const processorTypeDic = [
|
||||
{
|
||||
label: '内建处理器',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '外部处理器(动态加载)',
|
||||
value: 4,
|
||||
},
|
||||
];
|
||||
|
||||
export const dispatchStrategyDic = [
|
||||
{
|
||||
label: 'HEALTH_FIRST',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: 'RANDOM',
|
||||
value: 2,
|
||||
},
|
||||
];
|
||||
|
||||
export const enableDic = [
|
||||
{
|
||||
label: '暂停',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '启用',
|
||||
value: 1,
|
||||
},
|
||||
];
|
||||
|
||||
export const logTypeDic = [
|
||||
{
|
||||
label: 'ONLINE',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: 'LOCAL',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: 'STDOUT',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: 'LOCAL_AND_ONLINE',
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
label: 'NULL',
|
||||
value: 999,
|
||||
},
|
||||
];
|
||||
|
||||
export const logLevelDic = [
|
||||
{
|
||||
label: 'DEBUG',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: 'INFO',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: 'WARN',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: 'ERROR',
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
label: 'OFF',
|
||||
value: 99,
|
||||
},
|
||||
];
|
||||
|
||||
export default {
|
||||
height: 'auto',
|
||||
calcHeight: 30,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
grid: true,
|
||||
labelWidth: 180,
|
||||
menuWidth: 350,
|
||||
dialogWidth: 1200,
|
||||
dialogClickModal: false,
|
||||
tabs: true,
|
||||
column: [
|
||||
{
|
||||
label: '任务应用',
|
||||
prop: 'jobServerId',
|
||||
type: 'select',
|
||||
dicUrl: '/blade-job/job-server/select',
|
||||
props: {
|
||||
label: 'jobAppName',
|
||||
value: 'id',
|
||||
},
|
||||
search: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '任务ID',
|
||||
prop: 'jobId',
|
||||
type: 'input',
|
||||
search: true,
|
||||
width: 80,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '任务名称',
|
||||
prop: 'jobName',
|
||||
type: 'input',
|
||||
search: true,
|
||||
width: 200,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '定时信息',
|
||||
labelTip: '时间表达式类型',
|
||||
prop: 'timeExpressionType',
|
||||
type: 'select',
|
||||
dicData: timeExpressionTypeDic,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择定时信息',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
width: 120,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '执行类型',
|
||||
labelTip: '枚举值',
|
||||
prop: 'executeType',
|
||||
type: 'select',
|
||||
dicData: executeTypeDic,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择执行器类型',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
width: 110,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '处理器类型',
|
||||
labelTip: '枚举值',
|
||||
prop: 'processorType',
|
||||
type: 'select',
|
||||
dicData: processorTypeDic,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择处理器类型',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
width: 180,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '任务状态',
|
||||
labelTip: '未启用的任务不会被调度',
|
||||
prop: 'enable',
|
||||
type: 'switch',
|
||||
dicData: enableDic,
|
||||
slot: true,
|
||||
width: 100,
|
||||
display: false,
|
||||
},
|
||||
],
|
||||
group: [
|
||||
{
|
||||
label: '基础配置',
|
||||
prop: 'modelSetting',
|
||||
icon: 'el-icon-tickets',
|
||||
column: [
|
||||
{
|
||||
label: '任务应用',
|
||||
prop: 'jobServerId',
|
||||
type: 'select',
|
||||
dicUrl: '/blade-job/job-server/select',
|
||||
props: {
|
||||
label: 'jobAppName',
|
||||
value: 'id',
|
||||
},
|
||||
editDisabled: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择任务应用',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '任务状态',
|
||||
labelTip: '未启用的任务不会被调度',
|
||||
prop: 'enable',
|
||||
type: 'switch',
|
||||
value: 1,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择是否开启',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '任务名称',
|
||||
prop: 'jobName',
|
||||
type: 'input',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入任务名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '生命周期',
|
||||
labelTip: '预留,用于指定定时调度任务的生效时间范围)',
|
||||
prop: 'lifecycle',
|
||||
type: 'datetimerange',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
startPlaceholder: '任务开始时间',
|
||||
endPlaceholder: '任务结束时间',
|
||||
},
|
||||
{
|
||||
label: '定时类型',
|
||||
labelTip: '时间表达式类型',
|
||||
prop: 'timeExpressionType',
|
||||
type: 'select',
|
||||
dicData: timeExpressionTypeDic,
|
||||
value: 2,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择定时信息',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '时间表达式',
|
||||
labelTip: '填写类型由 定时类型 决定,比如 CRON 需要填写 CRON 表达式',
|
||||
prop: 'timeExpression',
|
||||
type: 'input',
|
||||
},
|
||||
{
|
||||
label: '执行类型',
|
||||
labelTip: '枚举值',
|
||||
prop: 'executeType',
|
||||
type: 'select',
|
||||
dicData: executeTypeDic,
|
||||
value: 1,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择执行器类型',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '运行时配置',
|
||||
labelTip: '目前支持随机(RANDOM)和健康度优先(HEALTH_FIRST)',
|
||||
prop: 'dispatchStrategy',
|
||||
type: 'select',
|
||||
dicData: dispatchStrategyDic,
|
||||
value: 1,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择运行时配置',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '处理器类型',
|
||||
labelTip: '枚举值',
|
||||
prop: 'processorType',
|
||||
type: 'select',
|
||||
dicData: processorTypeDic,
|
||||
value: 1,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择处理器类型',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '处理器参数',
|
||||
labelTip:
|
||||
'如Java处理器需要填写全限定类名,如:org.springblade.demo.MapReduceProcessorDemo',
|
||||
prop: 'processorInfo',
|
||||
type: 'input',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入处理器参数',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '任务参数',
|
||||
labelTip: '方法入参 TaskContext 对象的 json 字段',
|
||||
prop: 'jobParams',
|
||||
type: 'input',
|
||||
span: 24,
|
||||
},
|
||||
{
|
||||
label: '任务描述',
|
||||
prop: 'jobDescription',
|
||||
type: 'textarea',
|
||||
minRows: 3,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '高级配置',
|
||||
prop: 'templateSetting',
|
||||
icon: 'el-icon-copy-document',
|
||||
column: [
|
||||
{
|
||||
label: '最大实例数',
|
||||
labelTip:
|
||||
'该任务同时执行的数量(任务和实例就像是类和对象的关系,任务被调度执行后被称为实例)',
|
||||
prop: 'maxInstanceNum',
|
||||
type: 'number',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '单机线程并发数',
|
||||
labelTip: '该实例执行过程中每个 Worker 使用的线程数量',
|
||||
prop: 'concurrency',
|
||||
type: 'number',
|
||||
value: 5,
|
||||
},
|
||||
{
|
||||
label: '任务实例运行时间限制',
|
||||
labelTip: '0 代表无任何限制,超时会被打断并判定为执行失败',
|
||||
prop: 'instanceTimeLimit',
|
||||
type: 'number',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '任务实例重试次数',
|
||||
labelTip: '任务实例重试次数,整个任务失败时重试,代价大,不推荐使用',
|
||||
prop: 'instanceRetryNum',
|
||||
type: 'number',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '任务作业重试次数',
|
||||
labelTip: 'Task 重试次数,每个子 Task 失败后单独重试,代价小,推荐使用',
|
||||
prop: 'taskRetryNum',
|
||||
type: 'number',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '最低CPU核心',
|
||||
labelTip:
|
||||
'最小可用 CPU 核心数,CPU 可用核心数小于该值的 Worker 将不会执行该任务,0 代表无任何限制',
|
||||
prop: 'minCpuCores',
|
||||
type: 'number',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '最低内存(GB)',
|
||||
labelTip: '可用内存小于该值的Worker 将不会执行该任务,0 代表无任何限制',
|
||||
prop: 'minMemorySpace',
|
||||
type: 'number',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '最低磁盘空间(GB)',
|
||||
labelTip: '可用磁盘空间小于该值的Worker 将不会执行该任务,0 代表无任何限制',
|
||||
prop: 'minDiskSpace',
|
||||
type: 'number',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '执行机器地址',
|
||||
labelTip: '设置该参数后只有列表中的机器允许执行该任务,空代表不指定机器',
|
||||
prop: 'designatedWorkers',
|
||||
type: 'input',
|
||||
},
|
||||
{
|
||||
label: '最大执行机器数量',
|
||||
labelTip: '限定调动执行的机器数量,0代表无限制',
|
||||
prop: 'maxWorkerCount',
|
||||
type: 'number',
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '其他配置',
|
||||
prop: 'codingSetting',
|
||||
icon: 'el-icon-printer',
|
||||
column: [
|
||||
{
|
||||
label: '报警人员ID列表',
|
||||
labelTip: '接收报警的用户ID列表',
|
||||
prop: 'notifyUserIds',
|
||||
type: 'input',
|
||||
},
|
||||
{
|
||||
label: '错误阈值',
|
||||
labelTip: '错误阈值,0代表不限制',
|
||||
prop: 'alertThreshold',
|
||||
type: 'number',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '统计窗口(s)',
|
||||
labelTip: '统计的窗口长度(s),0代表不限制',
|
||||
prop: 'statisticWindowLen',
|
||||
type: 'number',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '沉默窗口(s)',
|
||||
labelTip: '沉默时间窗口(s),0代表不限制',
|
||||
prop: 'silenceWindowLen',
|
||||
type: 'number',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '日志配置',
|
||||
labelTip: '日志配置',
|
||||
prop: 'logType',
|
||||
type: 'select',
|
||||
value: 1,
|
||||
dicData: logTypeDic,
|
||||
},
|
||||
{
|
||||
label: '日志级别',
|
||||
labelTip: '日志级别',
|
||||
prop: 'logLevel',
|
||||
type: 'select',
|
||||
value: 2,
|
||||
dicData: logLevelDic,
|
||||
},
|
||||
{
|
||||
label: '扩展字段',
|
||||
labelTip: '供开发者使用,用于功能扩展,powerjob 自身不会使用该字段',
|
||||
prop: 'extra',
|
||||
type: 'textarea',
|
||||
minRows: 3,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
78
src/option/job/jobserver.js
Normal file
78
src/option/job/jobserver.js
Normal file
@@ -0,0 +1,78 @@
|
||||
export default {
|
||||
height: 'auto',
|
||||
calcHeight: 30,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
selection: true,
|
||||
grid: true,
|
||||
labelWidth: 100,
|
||||
menuWidth: 350,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '服务名称',
|
||||
prop: 'jobServerName',
|
||||
type: 'input',
|
||||
span: 24,
|
||||
search: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入服务名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '服务器地址',
|
||||
prop: 'jobServerUrl',
|
||||
type: 'input',
|
||||
span: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入服务器地址',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '应用名称',
|
||||
prop: 'jobAppName',
|
||||
type: 'input',
|
||||
span: 24,
|
||||
search: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入应用名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '应用密码',
|
||||
prop: 'jobAppPassword',
|
||||
type: 'input',
|
||||
span: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入应用密码',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '任务备注',
|
||||
prop: 'jobRemark',
|
||||
type: 'textarea',
|
||||
minRows: 3,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
};
|
||||
111
src/option/system/authlock.js
Normal file
111
src/option/system/authlock.js
Normal file
@@ -0,0 +1,111 @@
|
||||
export const LOCK_TYPE_DIC = [
|
||||
{ label: '系统锁定', value: 'SYSTEM' },
|
||||
{ label: '手动锁定', value: 'MANUAL' },
|
||||
];
|
||||
|
||||
export const LOCK_STATUS_DIC = [
|
||||
{ label: '待锁定', value: 'PRE_LOCKED' },
|
||||
{ label: '锁定中', value: 'LOCKED' },
|
||||
{ label: '已解锁', value: 'UN_LOCKED' },
|
||||
];
|
||||
|
||||
export const authLockOption = {
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
addBtn: false,
|
||||
viewBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
menuWidth: 160,
|
||||
dialogType: 'drawer',
|
||||
dialogWidth: 800,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '锁定目标',
|
||||
prop: 'lockTarget',
|
||||
slot: true,
|
||||
search: true,
|
||||
span: 24,
|
||||
searchPlaceholder: '请输入账号或IP',
|
||||
},
|
||||
{
|
||||
label: '用户姓名',
|
||||
prop: 'userName',
|
||||
slot: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
label: '请求IP',
|
||||
prop: 'remoteIp',
|
||||
slot: true,
|
||||
search: true,
|
||||
searchPlaceholder: '请输入IP地址',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
label: '失败次数',
|
||||
prop: 'failCount',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
hide: true,
|
||||
},
|
||||
{
|
||||
label: '用户代理',
|
||||
prop: 'userAgent',
|
||||
hide: true,
|
||||
overHidden: true,
|
||||
},
|
||||
{
|
||||
label: '锁定类型',
|
||||
prop: 'lockType',
|
||||
slot: true,
|
||||
type: 'select',
|
||||
dicData: LOCK_TYPE_DIC,
|
||||
width: 90,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '锁定状态',
|
||||
prop: 'lockStatus',
|
||||
slot: true,
|
||||
type: 'select',
|
||||
search: true,
|
||||
dicData: LOCK_STATUS_DIC,
|
||||
width: 90,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '锁定开始',
|
||||
prop: 'lockBeginTime',
|
||||
slot: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
label: '锁定结束',
|
||||
prop: 'lockEndTime',
|
||||
slot: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
label: '锁定原因',
|
||||
prop: 'lockReason',
|
||||
type: 'textarea',
|
||||
hide: true,
|
||||
overHidden: true,
|
||||
minWidth: 160,
|
||||
span: 24,
|
||||
},
|
||||
{
|
||||
label: '解锁原因',
|
||||
prop: 'unlockReason',
|
||||
type: 'textarea',
|
||||
hide: true,
|
||||
overHidden: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
};
|
||||
94
src/option/system/authlog.js
Normal file
94
src/option/system/authlog.js
Normal file
@@ -0,0 +1,94 @@
|
||||
export const GRANT_TYPE_DIC = [
|
||||
{ label: '密码登录', value: 'password' },
|
||||
{ label: '验证码登录', value: 'captcha' },
|
||||
{ label: '短信登录', value: 'sms' },
|
||||
{ label: '社交登录', value: 'social' },
|
||||
{ label: '客户端凭证', value: 'client_credentials' },
|
||||
{ label: '刷新令牌', value: 'refresh_token' },
|
||||
];
|
||||
|
||||
export const authLogOption = {
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
addBtn: false,
|
||||
viewBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
menu: true,
|
||||
menuWidth: 100,
|
||||
dialogType: 'drawer',
|
||||
dialogWidth: 800,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '授权账号',
|
||||
prop: 'account',
|
||||
slot: true,
|
||||
search: true,
|
||||
searchPlaceholder: '请输入账号',
|
||||
},
|
||||
{
|
||||
label: '用户姓名',
|
||||
prop: 'realName',
|
||||
},
|
||||
{
|
||||
label: '授权类型',
|
||||
prop: 'grantType',
|
||||
type: 'select',
|
||||
search: true,
|
||||
dicData: GRANT_TYPE_DIC,
|
||||
width: 110,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '请求IP',
|
||||
prop: 'remoteIp',
|
||||
slot: true,
|
||||
search: true,
|
||||
searchPlaceholder: '请输入IP地址',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
label: '服务ID',
|
||||
prop: 'serviceId',
|
||||
hide: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
label: '服务器IP',
|
||||
prop: 'serverIp',
|
||||
hide: true,
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
label: '环境',
|
||||
prop: 'env',
|
||||
slot: true,
|
||||
width: 80,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
label: '服务器名',
|
||||
prop: 'serverHost',
|
||||
hide: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
label: '登录时间',
|
||||
prop: 'loginTime',
|
||||
span: 24,
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
label: '用户代理',
|
||||
prop: 'userAgent',
|
||||
type: 'textarea',
|
||||
span: 24,
|
||||
hide: true,
|
||||
overHidden: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
210
src/option/system/dict.js
Normal file
210
src/option/system/dict.js
Normal file
@@ -0,0 +1,210 @@
|
||||
export const optionParent = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 10,
|
||||
border: true,
|
||||
index: true,
|
||||
selection: true,
|
||||
viewBtn: true,
|
||||
menuWidth: 250,
|
||||
dialogWidth: 880,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '字典编号',
|
||||
prop: 'code',
|
||||
search: true,
|
||||
slot: true,
|
||||
span: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典编号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '字典名称',
|
||||
prop: 'dictValue',
|
||||
search: true,
|
||||
align: 'center',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '字典排序',
|
||||
prop: 'sort',
|
||||
type: 'number',
|
||||
align: 'right',
|
||||
width: 100,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典排序',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '封存',
|
||||
prop: 'isSealed',
|
||||
type: 'switch',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
dicData: [
|
||||
{
|
||||
label: '否',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '是',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
value: 0,
|
||||
slot: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择封存',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '字典备注',
|
||||
prop: 'remark',
|
||||
hide: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const optionChild = {
|
||||
height: 'auto',
|
||||
calcHeight: 95,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 10,
|
||||
tree: true,
|
||||
border: true,
|
||||
index: true,
|
||||
selection: true,
|
||||
viewBtn: true,
|
||||
menuWidth: 300,
|
||||
dialogWidth: 880,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '字典编号',
|
||||
prop: 'code',
|
||||
addDisabled: true,
|
||||
editDisabled: true,
|
||||
search: true,
|
||||
span: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典编号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '字典名称',
|
||||
prop: 'dictValue',
|
||||
search: true,
|
||||
align: 'center',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '上级字典',
|
||||
prop: 'parentId',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
hide: true,
|
||||
props: {
|
||||
label: 'title',
|
||||
},
|
||||
addDisabled: true,
|
||||
editDisabled: true,
|
||||
rules: [
|
||||
{
|
||||
required: false,
|
||||
message: '请选择上级字典',
|
||||
trigger: 'click',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '字典键值',
|
||||
prop: 'dictKey',
|
||||
width: 85,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典键值',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '字典排序',
|
||||
prop: 'sort',
|
||||
type: 'number',
|
||||
align: 'right',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典排序',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '封存',
|
||||
prop: 'isSealed',
|
||||
type: 'switch',
|
||||
align: 'center',
|
||||
width: 80,
|
||||
dicData: [
|
||||
{
|
||||
label: '否',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '是',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
value: 0,
|
||||
slot: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择封存',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '字典备注',
|
||||
prop: 'remark',
|
||||
hide: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
211
src/option/system/dictbiz.js
Normal file
211
src/option/system/dictbiz.js
Normal file
@@ -0,0 +1,211 @@
|
||||
export const optionParent = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 10,
|
||||
border: true,
|
||||
index: true,
|
||||
selection: true,
|
||||
viewBtn: true,
|
||||
menuWidth: 250,
|
||||
dialogWidth: 880,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '字典编号',
|
||||
prop: 'code',
|
||||
search: true,
|
||||
slot: true,
|
||||
span: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典编号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '字典名称',
|
||||
prop: 'dictValue',
|
||||
search: true,
|
||||
align: 'center',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '字典排序',
|
||||
prop: 'sort',
|
||||
type: 'number',
|
||||
align: 'right',
|
||||
width: 100,
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典排序',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '封存',
|
||||
prop: 'isSealed',
|
||||
type: 'switch',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
dicData: [
|
||||
{
|
||||
label: '否',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '是',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
value: 0,
|
||||
slot: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择封存',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '字典备注',
|
||||
prop: 'remark',
|
||||
hide: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const optionChild = {
|
||||
height: 'auto',
|
||||
calcHeight: 95,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 10,
|
||||
tree: true,
|
||||
border: true,
|
||||
index: true,
|
||||
selection: true,
|
||||
viewBtn: true,
|
||||
menuWidth: 300,
|
||||
dialogWidth: 880,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '字典编号',
|
||||
prop: 'code',
|
||||
addDisabled: true,
|
||||
editDisabled: true,
|
||||
search: true,
|
||||
span: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典编号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '字典名称',
|
||||
prop: 'dictValue',
|
||||
search: true,
|
||||
align: 'center',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '上级字典',
|
||||
prop: 'parentId',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
hide: true,
|
||||
props: {
|
||||
label: 'title',
|
||||
},
|
||||
addDisabled: true,
|
||||
editDisabled: true,
|
||||
rules: [
|
||||
{
|
||||
required: false,
|
||||
message: '请选择上级字典',
|
||||
trigger: 'click',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '字典键值',
|
||||
prop: 'dictKey',
|
||||
width: 85,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典键值',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '字典排序',
|
||||
prop: 'sort',
|
||||
type: 'number',
|
||||
align: 'right',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典排序',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '封存',
|
||||
prop: 'isSealed',
|
||||
type: 'switch',
|
||||
align: 'center',
|
||||
width: 80,
|
||||
dicData: [
|
||||
{
|
||||
label: '否',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '是',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
value: 0,
|
||||
slot: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择封存',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '字典备注',
|
||||
prop: 'remark',
|
||||
hide: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
534
src/option/system/user.js
Normal file
534
src/option/system/user.js
Normal file
@@ -0,0 +1,534 @@
|
||||
import website from '@/config/website';
|
||||
import { getDeptLazyTree } from '@/api/system/dept';
|
||||
|
||||
export const userOption = safe => {
|
||||
const validatePass = (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
callback(new Error('请输入密码'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const validatePass2 = (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
callback(new Error('请再次输入密码'));
|
||||
} else if (value !== safe.form.password) {
|
||||
callback(new Error('两次输入密码不一致!'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
height: 'auto',
|
||||
calcHeight: 150,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
selection: true,
|
||||
addBtn: false,
|
||||
viewBtn: true,
|
||||
dialogType: 'drawer',
|
||||
dialogWidth: 1000,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '登录账号',
|
||||
prop: 'account',
|
||||
search: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '所属租户',
|
||||
prop: 'tenantName',
|
||||
slot: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '用户姓名',
|
||||
prop: 'realName',
|
||||
search: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '所属角色',
|
||||
prop: 'roleName',
|
||||
slot: true,
|
||||
overHidden: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '所属部门',
|
||||
prop: 'deptName',
|
||||
slot: true,
|
||||
overHidden: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '用户平台',
|
||||
prop: 'userTypeName',
|
||||
width: 100,
|
||||
slot: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '用户平台',
|
||||
type: 'select',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=user_type',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
dataType: 'number',
|
||||
search: true,
|
||||
hide: true,
|
||||
display: false,
|
||||
prop: 'userType',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择用户平台',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
group: [
|
||||
{
|
||||
label: '基础信息',
|
||||
prop: 'baseInfo',
|
||||
icon: 'el-icon-user-solid',
|
||||
column: [
|
||||
{
|
||||
label: '所属租户',
|
||||
prop: 'tenantId',
|
||||
type: 'tree',
|
||||
dicUrl: '/blade-system/tenant/select',
|
||||
props: {
|
||||
label: 'tenantName',
|
||||
value: 'tenantId',
|
||||
},
|
||||
hide: !website.tenantMode,
|
||||
addDisplay: website.tenantMode,
|
||||
editDisplay: website.tenantMode,
|
||||
viewDisplay: website.tenantMode,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入所属租户',
|
||||
trigger: 'click',
|
||||
},
|
||||
],
|
||||
span: 24,
|
||||
},
|
||||
{
|
||||
label: '登录账号',
|
||||
prop: 'account',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入登录账号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '用户平台',
|
||||
type: 'select',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=user_type',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
dataType: 'number',
|
||||
slot: true,
|
||||
prop: 'userType',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择用户平台',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '密码',
|
||||
prop: 'password',
|
||||
hide: true,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
rules: [{ required: true, validator: validatePass, trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '确认密码',
|
||||
prop: 'password2',
|
||||
hide: true,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
rules: [{ required: true, validator: validatePass2, trigger: 'blur' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '详细信息',
|
||||
prop: 'detailInfo',
|
||||
icon: 'el-icon-s-order',
|
||||
column: [
|
||||
{
|
||||
label: '用户昵称',
|
||||
prop: 'name',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户昵称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '用户姓名',
|
||||
prop: 'realName',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户姓名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 10,
|
||||
message: '姓名长度在2到5个字符',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '手机号码',
|
||||
prop: 'phone',
|
||||
overHidden: true,
|
||||
},
|
||||
{
|
||||
label: '电子邮箱',
|
||||
prop: 'email',
|
||||
hide: true,
|
||||
overHidden: true,
|
||||
},
|
||||
{
|
||||
label: '用户性别',
|
||||
prop: 'sex',
|
||||
type: 'select',
|
||||
dicData: [
|
||||
{
|
||||
label: '男',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '女',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '未知',
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
hide: true,
|
||||
},
|
||||
{
|
||||
label: '用户生日',
|
||||
type: 'date',
|
||||
prop: 'birthday',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
hide: true,
|
||||
},
|
||||
{
|
||||
label: '账号状态',
|
||||
prop: 'statusName',
|
||||
hide: true,
|
||||
display: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '职责信息',
|
||||
prop: 'dutyInfo',
|
||||
icon: 'el-icon-s-custom',
|
||||
column: [
|
||||
{
|
||||
label: '用户编号',
|
||||
prop: 'code',
|
||||
},
|
||||
{
|
||||
label: '所属角色',
|
||||
prop: 'roleId',
|
||||
multiple: true,
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'title',
|
||||
},
|
||||
checkStrictly: true,
|
||||
slot: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所属角色',
|
||||
trigger: 'click',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '所属部门',
|
||||
prop: 'deptId',
|
||||
type: 'tree',
|
||||
multiple: true,
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'title',
|
||||
},
|
||||
checkStrictly: true,
|
||||
slot: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所属部门',
|
||||
trigger: 'click',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '所属岗位',
|
||||
prop: 'postId',
|
||||
type: 'tree',
|
||||
multiple: true,
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'postName',
|
||||
value: 'id',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所属岗位',
|
||||
trigger: 'click',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '直属主管',
|
||||
prop: 'leaderId',
|
||||
type: 'tree',
|
||||
multiple: true,
|
||||
filterable: true,
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'realName',
|
||||
value: 'id',
|
||||
},
|
||||
clearable: true,
|
||||
},
|
||||
{
|
||||
label: '是否主管',
|
||||
prop: 'isLeader',
|
||||
type: 'switch',
|
||||
dicData: [
|
||||
{
|
||||
label: '否',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '是',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
export const platformOption = {
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
selection: true,
|
||||
viewBtn: true,
|
||||
dialogClickModal: false,
|
||||
menuWidth: 120,
|
||||
editBtnText: '配置',
|
||||
column: [
|
||||
{
|
||||
label: '登录账号',
|
||||
prop: 'account',
|
||||
search: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '所属租户',
|
||||
prop: 'tenantName',
|
||||
slot: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '用户姓名',
|
||||
prop: 'realName',
|
||||
search: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '用户平台',
|
||||
prop: 'userTypeName',
|
||||
slot: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '用户平台',
|
||||
type: 'select',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=user_type',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
dataType: 'number',
|
||||
search: true,
|
||||
hide: true,
|
||||
display: false,
|
||||
prop: 'userType',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择用户平台',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '用户拓展',
|
||||
prop: 'userExt',
|
||||
type: 'textarea',
|
||||
minRows: 8,
|
||||
span: 24,
|
||||
overHidden: true,
|
||||
row: true,
|
||||
hide: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const excelOption = {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
column: [
|
||||
{
|
||||
label: '模板上传',
|
||||
prop: 'excelFile',
|
||||
type: 'upload',
|
||||
drag: true,
|
||||
loadText: '模板上传中,请稍等',
|
||||
span: 24,
|
||||
propsHttp: {
|
||||
res: 'data',
|
||||
},
|
||||
tip: '请上传 .xls,.xlsx 标准格式文件',
|
||||
action: '/blade-system/user/import-user',
|
||||
},
|
||||
{
|
||||
label: '数据覆盖',
|
||||
prop: 'isCovered',
|
||||
type: 'switch',
|
||||
align: 'center',
|
||||
width: 80,
|
||||
dicData: [
|
||||
{
|
||||
label: '否',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '是',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
value: 0,
|
||||
slot: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择是否覆盖',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '模板下载',
|
||||
prop: 'excelTemplate',
|
||||
formslot: true,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const platformFormOption = {
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
labelWidth: 100,
|
||||
column: [
|
||||
{
|
||||
label: '用户账号',
|
||||
prop: 'account',
|
||||
disabled: true,
|
||||
span: 24,
|
||||
},
|
||||
{
|
||||
label: '用户平台',
|
||||
type: 'select',
|
||||
prop: 'userType',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=user_type',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
dataType: 'number',
|
||||
span: 24,
|
||||
rules: [{ required: true, message: '请选择用户平台', trigger: 'blur' }],
|
||||
},
|
||||
{
|
||||
label: '用户拓展',
|
||||
prop: 'userExt',
|
||||
type: 'textarea',
|
||||
minRows: 8,
|
||||
span: 24,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const treeOption = {
|
||||
nodeKey: 'id',
|
||||
lazy: true,
|
||||
treeLoad: function (node, resolve) {
|
||||
const parentId = node.level === 0 ? 0 : node.data.id;
|
||||
getDeptLazyTree(parentId).then(res => {
|
||||
resolve(
|
||||
res.data.data.map(item => {
|
||||
return {
|
||||
...item,
|
||||
leaf: !item.hasChildren,
|
||||
};
|
||||
})
|
||||
);
|
||||
});
|
||||
},
|
||||
addBtn: false,
|
||||
menu: false,
|
||||
size: 'small',
|
||||
props: {
|
||||
labelText: '标题',
|
||||
label: 'title',
|
||||
value: 'value',
|
||||
children: 'children',
|
||||
},
|
||||
};
|
||||
665
src/option/tool/code.js
Normal file
665
src/option/tool/code.js
Normal file
@@ -0,0 +1,665 @@
|
||||
import { templateDic } from '@/const/tool/model';
|
||||
|
||||
export const codeOption = {
|
||||
height: 'auto',
|
||||
calcHeight: 32,
|
||||
dialogWidth: 900,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
selection: true,
|
||||
labelWidth: 120,
|
||||
menuWidth: 350,
|
||||
viewBtn: true,
|
||||
dialogClickModal: false,
|
||||
tabs: true,
|
||||
column: [
|
||||
{
|
||||
label: '模块名',
|
||||
prop: 'codeName',
|
||||
search: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '模版类型',
|
||||
prop: 'templateType',
|
||||
type: 'select',
|
||||
dicData: templateDic,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '表名',
|
||||
prop: 'tableName',
|
||||
search: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '服务名',
|
||||
prop: 'serviceName',
|
||||
search: true,
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '包名',
|
||||
prop: 'packageName',
|
||||
display: false,
|
||||
},
|
||||
],
|
||||
group: [
|
||||
{
|
||||
label: '模型配置',
|
||||
prop: 'modelSetting',
|
||||
icon: 'el-icon-tickets',
|
||||
column: [
|
||||
{
|
||||
label: '上级菜单',
|
||||
prop: 'menuId',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
span: 24,
|
||||
hide: true,
|
||||
addDisabled: false,
|
||||
props: {
|
||||
label: 'title',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择上级菜单',
|
||||
trigger: 'click',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '数据模型',
|
||||
prop: 'modelId',
|
||||
search: true,
|
||||
span: 24,
|
||||
type: 'select',
|
||||
dicUrl: '/blade-develop/model/select',
|
||||
props: {
|
||||
label: 'modelName',
|
||||
value: 'id',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择数据模型',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '模块名',
|
||||
prop: 'codeName',
|
||||
search: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入模块名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '表名',
|
||||
prop: 'tableName',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入表名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '表前缀',
|
||||
prop: 'tablePrefix',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入表前缀',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '主键名',
|
||||
prop: 'pkName',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入主键名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '服务名',
|
||||
prop: 'serviceName',
|
||||
search: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入服务名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '包名',
|
||||
prop: 'packageName',
|
||||
overHidden: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入包名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '模版配置',
|
||||
prop: 'templateSetting',
|
||||
icon: 'el-icon-copy-document',
|
||||
column: [
|
||||
{
|
||||
label: '模版类型',
|
||||
prop: 'templateType',
|
||||
type: 'select',
|
||||
dicData: templateDic,
|
||||
value: 'crud',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择模版类型',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '作者信息',
|
||||
prop: 'author',
|
||||
value: 'BladeX',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入作者',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '子表模型',
|
||||
prop: 'subModelId',
|
||||
type: 'select',
|
||||
dicUrl: '/blade-develop/model/select',
|
||||
props: {
|
||||
label: 'modelName',
|
||||
value: 'id',
|
||||
},
|
||||
display: false,
|
||||
hide: true,
|
||||
},
|
||||
{
|
||||
label: '子表外键',
|
||||
prop: 'subFkId',
|
||||
display: false,
|
||||
hide: true,
|
||||
},
|
||||
{
|
||||
label: '树主键字段',
|
||||
prop: 'treeId',
|
||||
type: 'select',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'jdbcComment',
|
||||
value: 'jdbcName',
|
||||
},
|
||||
display: false,
|
||||
hide: true,
|
||||
},
|
||||
{
|
||||
label: '树父主键字段',
|
||||
prop: 'treePid',
|
||||
type: 'select',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'jdbcComment',
|
||||
value: 'jdbcName',
|
||||
},
|
||||
display: false,
|
||||
hide: true,
|
||||
},
|
||||
{
|
||||
label: '树名称字段',
|
||||
prop: 'treeName',
|
||||
type: 'select',
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'jdbcComment',
|
||||
value: 'jdbcName',
|
||||
},
|
||||
display: false,
|
||||
hide: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '生成配置',
|
||||
prop: 'codingSetting',
|
||||
icon: 'el-icon-printer',
|
||||
column: [
|
||||
{
|
||||
label: '基础业务',
|
||||
labelTip: '配置是否使用BladeX封装的BaseService解锁更多功能',
|
||||
prop: 'baseMode',
|
||||
type: 'radio',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=yes_no',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
value: 2,
|
||||
dataType: 'number',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择基础业务',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '包装器',
|
||||
labelTip: '配置是否使用Wrapper包装器来拓展Controller返回列表的字段',
|
||||
prop: 'wrapMode',
|
||||
type: 'radio',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=yes_no',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
value: 2,
|
||||
dataType: 'number',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择包装器',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '远程调用',
|
||||
labelTip: '配置是否使用Feign远程调用',
|
||||
prop: 'feignMode',
|
||||
type: 'radio',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=yes_no',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
value: 1,
|
||||
dataType: 'number',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择基础业务',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '代码风格',
|
||||
labelTip: '选择不同底层实现的代码模版',
|
||||
prop: 'codeStyle',
|
||||
type: 'radio',
|
||||
dicData: [
|
||||
{
|
||||
label: 'saber3',
|
||||
value: 'saber3',
|
||||
},
|
||||
{
|
||||
label: 'element-plus',
|
||||
value: 'element-plus',
|
||||
},
|
||||
],
|
||||
value: 'saber3',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择代码风格',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '后端生成路径',
|
||||
prop: 'apiPath',
|
||||
span: 24,
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入后端生成路径',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '前端生成路径',
|
||||
prop: 'webPath',
|
||||
span: 24,
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入前端生成路径',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const genOption = {
|
||||
labelWidth: 120,
|
||||
column: [
|
||||
{
|
||||
label: '上级菜单',
|
||||
prop: 'menuId',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
span: 24,
|
||||
hide: true,
|
||||
addDisabled: false,
|
||||
props: {
|
||||
label: 'title',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择上级菜单',
|
||||
trigger: 'click',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '数据源',
|
||||
prop: 'datasourceId',
|
||||
search: true,
|
||||
span: 24,
|
||||
type: 'select',
|
||||
dicUrl: '/blade-develop/datasource/select',
|
||||
props: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择数据源',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '物理表名',
|
||||
prop: 'modelTable',
|
||||
type: 'tree',
|
||||
slot: true,
|
||||
filterable: true,
|
||||
span: 24,
|
||||
display: true,
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'comment',
|
||||
value: 'name',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入数据库表名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '表单设计',
|
||||
prop: 'modelForm',
|
||||
type: 'select',
|
||||
props: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
dicData: [],
|
||||
filterable: true,
|
||||
display: false,
|
||||
span: 24,
|
||||
},
|
||||
{
|
||||
label: '模型类名',
|
||||
prop: 'modelClass',
|
||||
display: false,
|
||||
disabled: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入模型类名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '模型编号',
|
||||
prop: 'modelCode',
|
||||
display: false,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入模型编号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '模块名',
|
||||
prop: 'codeName',
|
||||
display: false,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入模块名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '表名',
|
||||
prop: 'tableName',
|
||||
display: false,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入表名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '表前缀',
|
||||
prop: 'tablePrefix',
|
||||
display: false,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入表前缀',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '主键名',
|
||||
prop: 'pkName',
|
||||
display: false,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入主键名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '服务名',
|
||||
prop: 'serviceName',
|
||||
search: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入服务名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '包名',
|
||||
prop: 'packageName',
|
||||
overHidden: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入包名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '基础业务',
|
||||
labelTip: '配置是否使用BladeX封装的BaseService解锁更多功能',
|
||||
prop: 'baseMode',
|
||||
type: 'radio',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=yes_no',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
value: 2,
|
||||
dataType: 'number',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择基础业务',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '包装器',
|
||||
labelTip: '配置是否使用Wrapper包装器来拓展Controller返回列表的字段',
|
||||
prop: 'wrapMode',
|
||||
type: 'radio',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=yes_no',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
value: 2,
|
||||
dataType: 'number',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择包装器',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '远程调用',
|
||||
labelTip: '配置是否使用Feign远程调用',
|
||||
prop: 'feignMode',
|
||||
type: 'radio',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=yes_no',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
value: 1,
|
||||
dataType: 'number',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择基础业务',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '代码风格',
|
||||
labelTip: '选择不同底层实现的代码模版',
|
||||
prop: 'codeStyle',
|
||||
type: 'radio',
|
||||
dicData: [
|
||||
{
|
||||
label: 'saber3',
|
||||
value: 'saber3',
|
||||
},
|
||||
{
|
||||
label: 'element-plus',
|
||||
value: 'element-plus',
|
||||
},
|
||||
],
|
||||
value: 'saber3',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择代码风格',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '后端生成路径',
|
||||
prop: 'apiPath',
|
||||
span: 24,
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入后端生成路径',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '前端生成路径',
|
||||
prop: 'webPath',
|
||||
span: 24,
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入前端生成路径',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
229
src/option/tool/codesetting.js
Normal file
229
src/option/tool/codesetting.js
Normal file
@@ -0,0 +1,229 @@
|
||||
export default {
|
||||
height: 'auto',
|
||||
calcHeight: 50,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
grid: true,
|
||||
selection: true,
|
||||
labelWidth: 120,
|
||||
searchLabelWidth: 100,
|
||||
menuWidth: 320,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '配置名称',
|
||||
labelTip: '用于定义本配置的代表名称,实际不参与代码生成',
|
||||
prop: 'name',
|
||||
gridRow: true,
|
||||
search: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入配置名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '配置编号',
|
||||
labelTip: '用于定义本配置的代表编号,实际不参与代码生成',
|
||||
prop: 'code',
|
||||
gridRow: true,
|
||||
search: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入配置编号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '上级菜单',
|
||||
prop: 'menuId',
|
||||
type: 'tree',
|
||||
dicData: [],
|
||||
span: 24,
|
||||
hide: true,
|
||||
addDisabled: false,
|
||||
props: {
|
||||
label: 'title',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择上级菜单',
|
||||
trigger: 'click',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '是否启用',
|
||||
prop: 'status',
|
||||
span: 24,
|
||||
width: 85,
|
||||
align: 'center',
|
||||
slot: true,
|
||||
gridRow: true,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '配置参数',
|
||||
prop: 'settings',
|
||||
display: false,
|
||||
gridRow: true,
|
||||
overHidden: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入服务名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '服务名',
|
||||
prop: 'serviceName',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入服务名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '包名',
|
||||
prop: 'packageName',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入包名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '基础业务',
|
||||
labelTip: '配置是否使用BladeX封装的BaseService解锁更多功能',
|
||||
prop: 'baseMode',
|
||||
type: 'radio',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=yes_no',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
value: 2,
|
||||
dataType: 'number',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择基础业务',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '包装器',
|
||||
labelTip: '配置是否使用Wrapper包装器来拓展Controller返回列表的字段',
|
||||
prop: 'wrapMode',
|
||||
type: 'radio',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=yes_no',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
value: 2,
|
||||
dataType: 'number',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择包装器',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '远程调用',
|
||||
labelTip: '配置是否使用Feign远程调用',
|
||||
prop: 'feignMode',
|
||||
type: 'radio',
|
||||
dicUrl: '/blade-system/dict/dictionary?code=yes_no',
|
||||
props: {
|
||||
label: 'dictValue',
|
||||
value: 'dictKey',
|
||||
},
|
||||
value: 1,
|
||||
dataType: 'number',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择基础业务',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '代码风格',
|
||||
labelTip: '选择不同底层实现的代码模版',
|
||||
prop: 'codeStyle',
|
||||
type: 'radio',
|
||||
dicData: [
|
||||
{
|
||||
label: 'saber3',
|
||||
value: 'saber3',
|
||||
},
|
||||
{
|
||||
label: 'element-plus',
|
||||
value: 'element-plus',
|
||||
},
|
||||
],
|
||||
value: 'saber3',
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择代码风格',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '后端生成路径',
|
||||
prop: 'apiPath',
|
||||
span: 24,
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入后端生成路径',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '前端生成路径',
|
||||
prop: 'webPath',
|
||||
span: 24,
|
||||
hide: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入前端生成路径',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
95
src/option/tool/formsetting.js
Normal file
95
src/option/tool/formsetting.js
Normal file
@@ -0,0 +1,95 @@
|
||||
export default {
|
||||
height: 'auto',
|
||||
calcHeight: 50,
|
||||
tip: false,
|
||||
searchShow: true,
|
||||
searchMenuSpan: 6,
|
||||
border: true,
|
||||
index: true,
|
||||
viewBtn: true,
|
||||
grid: true,
|
||||
selection: true,
|
||||
labelWidth: 120,
|
||||
searchLabelWidth: 100,
|
||||
menuWidth: 320,
|
||||
dialogWidth: 500,
|
||||
dialogClickModal: false,
|
||||
column: [
|
||||
{
|
||||
label: '数据源',
|
||||
labelTip: '数据源管理配置的数据源列表',
|
||||
prop: 'datasourceId',
|
||||
hide: true,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
span: 24,
|
||||
type: 'select',
|
||||
dicUrl: '/blade-develop/datasource/select',
|
||||
props: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择数据源',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '物理表名',
|
||||
labelTip: '从数据源列表选择的物理表名',
|
||||
filterable: true,
|
||||
prop: 'modelTable',
|
||||
type: 'tree',
|
||||
span: 24,
|
||||
hide: true,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
dicData: [],
|
||||
props: {
|
||||
label: 'comment',
|
||||
value: 'name',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入数据库表名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '表单名称',
|
||||
labelTip: '用于定义本配置的代表名称,默认为数据库表说明',
|
||||
prop: 'name',
|
||||
gridRow: true,
|
||||
search: true,
|
||||
span: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入表单名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '关联表名',
|
||||
labelTip: '用于定义本配置的代表编号,默认为数据库表名,在代码快速生成时会关联匹配',
|
||||
prop: 'code',
|
||||
gridRow: true,
|
||||
search: true,
|
||||
disabled: true,
|
||||
span: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入表单编号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
111
src/page/index/index.vue
Normal file
111
src/page/index/index.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<el-watermark :content="watermark" style="height: 100%">
|
||||
<div class="avue-contail" :class="{ 'avue--collapse': isCollapse }">
|
||||
<div class="avue-layout" :class="{ 'avue-layout--horizontal': isHorizontal }">
|
||||
<div class="avue-sidebar" v-show="validSidebar">
|
||||
<!-- 左侧导航栏 -->
|
||||
<logo />
|
||||
<sidebar />
|
||||
</div>
|
||||
<div class="avue-main">
|
||||
<!-- 顶部导航栏 -->
|
||||
<top ref="top" />
|
||||
<!-- 顶部标签卡 -->
|
||||
<tags />
|
||||
<search class="avue-view" v-show="isSearch"></search>
|
||||
<!-- 主体视图层 -->
|
||||
<div id="avue-view" v-show="!isSearch" v-if="isRefresh">
|
||||
<router-view #="{ Component }">
|
||||
<keep-alive :include="$store.getters.tagsKeep">
|
||||
<component :is="tabView($route, Component)" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <wechat></wechat> -->
|
||||
</div>
|
||||
</el-watermark>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import index from '@/mixins/index';
|
||||
import wechat from './wechat.vue';
|
||||
//import { validatenull } from 'utils/validate';
|
||||
import { mapGetters } from 'vuex';
|
||||
import tags from './tags.vue';
|
||||
import search from './search.vue';
|
||||
import logo from './logo.vue';
|
||||
import top from './top/index.vue';
|
||||
import sidebar from './sidebar/index.vue';
|
||||
import website from '@/config/website';
|
||||
import { validatenull } from '@/utils/validate';
|
||||
import { getMainMenu } from '@/api/system/menu';
|
||||
import { tabView } from '@/router/tab';
|
||||
|
||||
export default {
|
||||
mixins: [index],
|
||||
components: {
|
||||
top,
|
||||
logo,
|
||||
tags,
|
||||
search,
|
||||
sidebar,
|
||||
wechat,
|
||||
},
|
||||
name: 'index',
|
||||
provide() {
|
||||
return {
|
||||
index: this,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'isHorizontal',
|
||||
'isRefresh',
|
||||
'isLock',
|
||||
'isCollapse',
|
||||
'isSearch',
|
||||
'menu',
|
||||
'setting',
|
||||
]),
|
||||
validSidebar() {
|
||||
return !(
|
||||
(this.$route.meta || {}).menu === false || (this.$route.query || {}).menu === 'false'
|
||||
);
|
||||
},
|
||||
watermark() {
|
||||
return website.watermark.mode ? website.watermark.text : '';
|
||||
},
|
||||
},
|
||||
props: [],
|
||||
methods: {
|
||||
tabView,
|
||||
//打开菜单
|
||||
openMenu(item = {}, skipMainMenu = false) {
|
||||
const doOpen = menuItem => {
|
||||
this.$store.dispatch('GetMenu', menuItem.id).then(data => {
|
||||
if (data.length !== 0) {
|
||||
this.$router.$avueRouter.formatRoutes(data, true);
|
||||
if (!validatenull(menuItem.path)) {
|
||||
this.$router.push({ path: menuItem.path });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
if (!item.id && !skipMainMenu) {
|
||||
getMainMenu()
|
||||
.then(res => {
|
||||
const mainMenu = res.data.data;
|
||||
doOpen(mainMenu && mainMenu.id ? { id: mainMenu.id } : item);
|
||||
})
|
||||
.catch(() => {
|
||||
doOpen(item);
|
||||
});
|
||||
} else {
|
||||
doOpen(item);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
7
src/page/index/layout.vue
Normal file
7
src/page/index/layout.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<router-view #="{ Component }">
|
||||
<keep-alive :include="$store.getters.tagsKeep">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</template>
|
||||
44
src/page/index/logo.vue
Normal file
44
src/page/index/logo.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div class="avue-logo">
|
||||
<transition name="fade">
|
||||
<span v-if="getScreen(isCollapse)" class="avue-logo_subtitle" key="0">
|
||||
<img class="logo-img" src="/img/logo.png" />
|
||||
</span>
|
||||
</transition>
|
||||
<transition-group name="fade">
|
||||
<template v-if="getScreen(!isCollapse)">
|
||||
<span class="logo-title" key="1">{{ website.indexTitle }} </span>
|
||||
</template>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'logo',
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
created() {},
|
||||
computed: {
|
||||
...mapGetters(['isCollapse']),
|
||||
},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.logo-title {
|
||||
font-size: 20px;
|
||||
background-image: linear-gradient(120deg, #54b6d0 16%, #3f8bdb, #2c77f1);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
font-weight: 700;
|
||||
padding-left: 30px;
|
||||
}
|
||||
.logo-img {
|
||||
width: 40px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
178
src/page/index/search.vue
Normal file
178
src/page/index/search.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div class="avue-searchs" @click.self="handleEsc">
|
||||
<div class="avue-searchs__title">菜单搜索</div>
|
||||
<div class="avue-searchs__content">
|
||||
<div class="avue-searchs__form">
|
||||
<el-input :placeholder="$t('search')" v-model="value" @keydown.esc="handleEsc">
|
||||
<template #append>
|
||||
<el-button icon="el-icon-search"></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<p>
|
||||
<el-tag>你可以使用快捷键esc 关闭</el-tag>
|
||||
</p>
|
||||
</div>
|
||||
<div class="avue-searchs__list">
|
||||
<el-scrollbar class="avue-searchs__scrollbar">
|
||||
<div
|
||||
class="avue-searchs__item"
|
||||
v-for="(item, index) in menus"
|
||||
:key="index"
|
||||
@click="handleSelect(item)"
|
||||
>
|
||||
<i :class="[item[iconKey], 'avue-searchs__item-icon']"></i>
|
||||
<span class="avue-searchs__item-title">{{ item[labelKey] }}</span>
|
||||
<div class="avue-searchs__item-path">
|
||||
{{ item[pathKey] }}
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
value: '',
|
||||
menus: [],
|
||||
menuList: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getMenuList();
|
||||
},
|
||||
watch: {
|
||||
value() {
|
||||
this.querySearch();
|
||||
},
|
||||
menu() {
|
||||
this.getMenuList();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
labelKey() {
|
||||
return this.website.menu.label;
|
||||
},
|
||||
pathKey() {
|
||||
return this.website.menu.path;
|
||||
},
|
||||
iconKey() {
|
||||
return this.website.menu.icon;
|
||||
},
|
||||
childrenKey() {
|
||||
return this.website.menu.children;
|
||||
},
|
||||
...mapGetters(['menu']),
|
||||
},
|
||||
methods: {
|
||||
handleEsc() {
|
||||
this.$store.commit('SET_IS_SEARCH', false);
|
||||
},
|
||||
getMenuList() {
|
||||
const findMenu = list => {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const ele = Object.assign({}, list[i]);
|
||||
if (this.validatenull(ele[this.childrenKey])) {
|
||||
this.menuList.push(ele);
|
||||
} else {
|
||||
findMenu(ele[this.childrenKey]);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.menuList = [];
|
||||
findMenu(this.menu);
|
||||
this.menus = this.menuList;
|
||||
},
|
||||
querySearch() {
|
||||
var restaurants = this.menuList;
|
||||
var queryString = this.value;
|
||||
this.menus = queryString ? this.menuList.filter(this.createFilter(queryString)) : restaurants;
|
||||
},
|
||||
createFilter(queryString) {
|
||||
return restaurant => {
|
||||
return restaurant[this.labelKey].toLowerCase().indexOf(queryString.toLowerCase()) === 0;
|
||||
};
|
||||
},
|
||||
handleSelect(item) {
|
||||
this.value = '';
|
||||
this.$router.push({
|
||||
path: item[this.pathKey],
|
||||
query: item.query,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.avue-searchs {
|
||||
padding-top: 50px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
z-index: 1024;
|
||||
|
||||
&__title {
|
||||
margin-bottom: 60px;
|
||||
text-align: center;
|
||||
font-size: 42px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
text-indent: 2px;
|
||||
}
|
||||
|
||||
&__form {
|
||||
margin: 0 auto 50px auto;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&__scrollbar {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
&__list {
|
||||
box-sizing: border-box;
|
||||
padding: 20px 30px;
|
||||
margin: 0 auto;
|
||||
width: 70%;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ebeef5;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
color: #303133;
|
||||
transition: 0.3s;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&__item {
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px dashed #eee;
|
||||
|
||||
&-icon {
|
||||
margin-right: 5px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&-path {
|
||||
line-height: 30px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
173
src/page/index/setting.vue
Normal file
173
src/page/index/setting.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<!-- <el-button
|
||||
@click="show = true"
|
||||
class="setting-icon"
|
||||
type="primary"
|
||||
icon="el-icon-setting"
|
||||
circle
|
||||
></el-button>-->
|
||||
<el-drawer append-to-body :with-header="false" v-model="show" size="30%">
|
||||
<div class="setting">
|
||||
<h5>导航模式</h5>
|
||||
<div class="setting-checkbox">
|
||||
<el-tooltip class="item" effect="dark" content="侧边菜单布局" placement="top">
|
||||
<div
|
||||
@click="setting.sidebar = 'vertical'"
|
||||
class="setting-checkbox-item setting-checkbox-item--side"
|
||||
></div>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" effect="dark" content="顶部菜单布局" placement="top">
|
||||
<div
|
||||
@click="setting.sidebar = 'horizontal'"
|
||||
class="setting-checkbox-item setting-checkbox-item--top"
|
||||
></div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<h5>页面布局</h5>
|
||||
<div class="setting-checkbox">
|
||||
<div class="setting-item" v-for="(item, index) in list1" :key="index">
|
||||
{{ item.label }}:
|
||||
<el-switch v-model="setting[item.value]"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<h5>功能调试</h5>
|
||||
<div class="setting-checkbox">
|
||||
<div class="setting-item" v-for="(item, index) in list2" :key="index">
|
||||
{{ item.label }}:
|
||||
<el-switch v-model="setting[item.value]"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
list1: [
|
||||
{
|
||||
label: '导航标签',
|
||||
value: 'tag',
|
||||
},
|
||||
{
|
||||
label: '菜单折叠',
|
||||
value: 'collapse',
|
||||
},
|
||||
{
|
||||
label: '菜单搜索',
|
||||
value: 'search',
|
||||
},
|
||||
{
|
||||
label: '屏幕全屏',
|
||||
value: 'fullscreen',
|
||||
},
|
||||
{
|
||||
label: '主题选择',
|
||||
value: 'theme',
|
||||
},
|
||||
{
|
||||
label: '顶部菜单',
|
||||
value: 'menu',
|
||||
},
|
||||
],
|
||||
list2: [
|
||||
{
|
||||
label: '日志调试',
|
||||
value: 'debug',
|
||||
},
|
||||
{
|
||||
label: '屏幕锁定',
|
||||
value: 'lock',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isHorizontal', 'setting']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.setting {
|
||||
&-icon {
|
||||
color: #666;
|
||||
position: fixed;
|
||||
bottom: 200px;
|
||||
right: 20px;
|
||||
z-index: 2048;
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&-checkbox {
|
||||
&--check {
|
||||
position: absolute;
|
||||
color: var(--el-color-primary);
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 44px;
|
||||
height: 36px;
|
||||
margin-right: 16px;
|
||||
overflow: hidden;
|
||||
background-color: #f0f2f5;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 2.5px 0 rgba(0, 0, 0, 0.18);
|
||||
cursor: pointer;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 33%;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 25%;
|
||||
background-color: #fff;
|
||||
content: '';
|
||||
}
|
||||
|
||||
&--side {
|
||||
&:before {
|
||||
z-index: 1;
|
||||
background-color: #001529;
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&--top {
|
||||
&:after {
|
||||
background-color: #001529;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
63
src/page/index/sidebar/index.vue
Normal file
63
src/page/index/sidebar/index.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<el-scrollbar class="avue-menu">
|
||||
<div v-if="menu && menu.length == 0 && !isHorizontal" class="avue-sidebar--tip">
|
||||
{{ $t('menuTip') }}
|
||||
</div>
|
||||
<el-menu
|
||||
unique-opened
|
||||
:default-active="activeMenu"
|
||||
:mode="setting.sidebar"
|
||||
:collapse="getScreen(isCollapse)"
|
||||
>
|
||||
<sidebar-item :menu="menu"></sidebar-item>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import website from '@/config/website';
|
||||
import sidebarItem from './sidebarItem.vue';
|
||||
|
||||
export default {
|
||||
name: 'sidebar',
|
||||
components: { sidebarItem },
|
||||
inject: ['index'],
|
||||
created() {
|
||||
this.index.openMenu();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isHorizontal', 'setting', 'menu', 'tag', 'isCollapse', 'menuId']),
|
||||
// 带参菜单的索引集合,用于激活态的精确匹配
|
||||
menuIndexes() {
|
||||
const props = website.menu;
|
||||
const indexes = new Set();
|
||||
const collect = list => {
|
||||
(list || []).forEach(item => {
|
||||
const query = item[props.query];
|
||||
if (query && Object.keys(query).length) {
|
||||
indexes.add(this.$router.resolve({ path: item[props.path], query }).fullPath);
|
||||
}
|
||||
collect(item[props.children]);
|
||||
});
|
||||
};
|
||||
collect(this.menu);
|
||||
return indexes;
|
||||
},
|
||||
activeMenu() {
|
||||
const route = this.$route;
|
||||
const { meta, path } = route;
|
||||
if (meta.activeMenu) {
|
||||
return meta.activeMenu;
|
||||
}
|
||||
// 完整地址命中带参菜单索引时精确高亮,其余场景按路径高亮
|
||||
const fullPath = route.fullPath;
|
||||
if (fullPath !== path && this.menuIndexes.has(fullPath)) {
|
||||
return fullPath;
|
||||
}
|
||||
return path;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
105
src/page/index/sidebar/sidebarItem.vue
Normal file
105
src/page/index/sidebar/sidebarItem.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<template v-for="item in menu">
|
||||
<el-menu-item
|
||||
v-if="validatenull(item[childrenKey]) && validRoles(item)"
|
||||
:index="getIndex(item)"
|
||||
@click="open(item)"
|
||||
:key="item[labelKey]"
|
||||
>
|
||||
<i :class="item[iconKey]"></i>
|
||||
<template #title>
|
||||
<span :alt="item[pathKey]">{{ getTitle(item) }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-sub-menu
|
||||
v-else-if="!validatenull(item[childrenKey]) && validRoles(item)"
|
||||
:index="getPath(item)"
|
||||
:key="item[labelKey]"
|
||||
>
|
||||
<template #title>
|
||||
<i :class="item[iconKey]"></i>
|
||||
<span>{{ getTitle(item) }}</span>
|
||||
</template>
|
||||
<template v-for="(child, cindex) in item[childrenKey]" :key="child[labelKey]">
|
||||
<el-menu-item
|
||||
:index="getIndex(child)"
|
||||
@click="open(child)"
|
||||
v-if="validatenull(child[childrenKey])"
|
||||
>
|
||||
<i :class="child[iconKey]"></i>
|
||||
<template #title>
|
||||
<span>{{ getTitle(child) }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<sidebar-item v-else :menu="[child]" :key="cindex"></sidebar-item>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
</template>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { validatenull } from 'utils/validate';
|
||||
import website from '@/config/website';
|
||||
import { generateIframePath, generateIframeQuery, isIframeRoute } from '@/router/router';
|
||||
|
||||
export default {
|
||||
name: 'sidebarItem',
|
||||
data() {
|
||||
return {
|
||||
props: website.menu,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
menu: Array,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['roles']),
|
||||
labelKey() {
|
||||
return this.props.label;
|
||||
},
|
||||
pathKey() {
|
||||
return this.props.path;
|
||||
},
|
||||
queryKey() {
|
||||
return this.props.query;
|
||||
},
|
||||
iconKey() {
|
||||
return this.props.icon;
|
||||
},
|
||||
childrenKey() {
|
||||
return this.props.children;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
validatenull,
|
||||
getPath(item) {
|
||||
return generateIframePath(item, this.pathKey);
|
||||
},
|
||||
getIndex(item) {
|
||||
// 带参菜单以完整地址为索引,保证同路径多菜单的激活态互不干扰
|
||||
const { path, query } = this.menuRoute(item);
|
||||
if (!query || !Object.keys(query).length) return path;
|
||||
return this.$router.resolve({ path, query }).fullPath;
|
||||
},
|
||||
menuRoute(item) {
|
||||
const path = this.getPath(item);
|
||||
const originalPath = item[this.pathKey];
|
||||
const isIframe = isIframeRoute(path, originalPath);
|
||||
const query = isIframe
|
||||
? generateIframeQuery(item, this.pathKey, this.queryKey)
|
||||
: item[this.queryKey];
|
||||
return { path, query };
|
||||
},
|
||||
getTitle(item) {
|
||||
return this.$router.$avueRouter.generateTitle(item, this.props);
|
||||
},
|
||||
validRoles(item) {
|
||||
item.meta = item.meta || {};
|
||||
return item.meta.roles ? item.meta.roles.includes(this.roles) : true;
|
||||
},
|
||||
open(item) {
|
||||
this.$router.push(this.menuRoute(item));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
192
src/page/index/tags.vue
Normal file
192
src/page/index/tags.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div class="avue-tags" v-if="setting.tag" @click="contextmenuFlag = false">
|
||||
<!-- tag盒子 -->
|
||||
<div
|
||||
v-if="contextmenuFlag"
|
||||
class="avue-tags__contentmenu"
|
||||
:style="{ left: contentmenuX + 'px', top: contentmenuY + 'px' }"
|
||||
>
|
||||
<div class="item" @click="closeOthersTags">{{ $t('tagsView.closeOthers') }}</div>
|
||||
<div class="item" @click="closeAllTags">{{ $t('tagsView.closeAll') }}</div>
|
||||
<div class="item" @click="clearCacheTags">{{ $t('tagsView.clearCache') }}</div>
|
||||
</div>
|
||||
<div class="avue-tags__box">
|
||||
<el-tabs
|
||||
v-model="active"
|
||||
type="card"
|
||||
@contextmenu="handleContextmenu"
|
||||
:closable="tagLen !== 1"
|
||||
@tab-click="openTag"
|
||||
@edit="menuTag"
|
||||
>
|
||||
<el-tab-pane
|
||||
v-for="(item, index) in tagList"
|
||||
:key="index"
|
||||
:label="generateTitle(item)"
|
||||
:name="item.fullPath"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
{{ generateTitle(item) }}
|
||||
<i
|
||||
class="el-icon-refresh"
|
||||
:class="{ turn: refresh }"
|
||||
@click="handleRefresh"
|
||||
v-if="active === item.fullPath"
|
||||
></i>
|
||||
</span>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-dropdown class="avue-tags__menu">
|
||||
<el-button type="primary">
|
||||
{{ $t('tagsView.menu') }}
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="openSearch"
|
||||
><i class="icon-fangda" /> {{ $t('tagsView.search') }}</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item @click="closeOthersTags"
|
||||
><i class="icon-fangkuai1" /> {{ $t('tagsView.closeOthers') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="closeAllTags"
|
||||
><i class="icon-cuowukongxin" /> {{ $t('tagsView.closeAll') }}</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item @click="clearCacheTags"
|
||||
><i class="icon-dingwei" /> {{ $t('tagsView.clearCache') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { clearCache } from '@/api/user';
|
||||
|
||||
export default {
|
||||
name: 'tags',
|
||||
data() {
|
||||
return {
|
||||
refresh: false,
|
||||
active: '',
|
||||
contentmenuX: '',
|
||||
contentmenuY: '',
|
||||
contextmenuFlag: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
tag: {
|
||||
handler(val) {
|
||||
this.active = val.fullPath;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
contextmenuFlag() {
|
||||
window.addEventListener('mousedown', this.watchContextmenu);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['tagWel', 'tagList', 'tag', 'setting']),
|
||||
tagLen() {
|
||||
return this.tagList.length || 0;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openSearch() {
|
||||
this.$store.commit('SET_IS_SEARCH', true);
|
||||
},
|
||||
handleRefresh() {
|
||||
this.refresh = true;
|
||||
this.$store.commit('SET_IS_REFRESH', false);
|
||||
setTimeout(() => {
|
||||
this.$store.commit('SET_IS_REFRESH', true);
|
||||
}, 100);
|
||||
setTimeout(() => {
|
||||
this.refresh = false;
|
||||
}, 500);
|
||||
},
|
||||
generateTitle(item) {
|
||||
return this.$router.$avueRouter.generateTitle({
|
||||
...item,
|
||||
...{
|
||||
label: item.name,
|
||||
},
|
||||
});
|
||||
},
|
||||
watchContextmenu(event) {
|
||||
if (!this.$el.contains(event.target) || event.button !== 0) {
|
||||
this.contextmenuFlag = false;
|
||||
}
|
||||
window.removeEventListener('mousedown', this.watchContextmenu);
|
||||
},
|
||||
handleContextmenu(event) {
|
||||
let target = event.target;
|
||||
let flag = false;
|
||||
if (target.className.indexOf('el-tabs__item') > -1) flag = true;
|
||||
else if (target.parentNode.className.indexOf('el-tabs__item') > -1) {
|
||||
target = target.parentNode;
|
||||
flag = true;
|
||||
}
|
||||
if (flag) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.contentmenuX = event.clientX;
|
||||
this.contentmenuY = event.clientY;
|
||||
this.tagName = target.getAttribute('aria-controls').slice(5);
|
||||
this.contextmenuFlag = true;
|
||||
}
|
||||
},
|
||||
menuTag(value, action) {
|
||||
if (action === 'remove') {
|
||||
let { tag, key } = this.findTag(value);
|
||||
this.$store.commit('DEL_TAG', tag);
|
||||
if (tag.fullPath === this.tag.fullPath) {
|
||||
tag = this.tagList[key - 1];
|
||||
this.$router.push({
|
||||
path: tag.path,
|
||||
query: tag.query,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
openTag(item) {
|
||||
let value = item.props.name;
|
||||
let { tag } = this.findTag(value);
|
||||
this.$router.push({
|
||||
path: tag.path,
|
||||
query: tag.query,
|
||||
});
|
||||
},
|
||||
findTag(fullPath) {
|
||||
let tag = this.tagList.find(item => item.fullPath === fullPath);
|
||||
let key = this.tagList.findIndex(item => item.fullPath === fullPath);
|
||||
return { tag, key };
|
||||
},
|
||||
closeOthersTags() {
|
||||
this.contextmenuFlag = false;
|
||||
this.$store.commit('DEL_TAG_OTHER');
|
||||
},
|
||||
closeAllTags() {
|
||||
this.contextmenuFlag = false;
|
||||
this.$store.commit('DEL_ALL_TAG');
|
||||
this.$router.push(this.tagWel);
|
||||
},
|
||||
clearCacheTags() {
|
||||
this.$confirm('是否需要清除缓存?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
clearCache().then(() => {
|
||||
this.contextmenuFlag = false;
|
||||
this.$message.success('清除完毕');
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user