@siven-li/elpis 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +3 -0
- package/.eslintrc +55 -0
- package/README.md +201 -0
- package/app/controller/base.js +37 -0
- package/app/controller/project.js +65 -0
- package/app/controller/view.js +20 -0
- package/app/extend/logger.js +35 -0
- package/app/middleware/api-params-verify.js +97 -0
- package/app/middleware/api-sign-verify.js +30 -0
- package/app/middleware/error-handler.js +34 -0
- package/app/middleware/project-handler.js +22 -0
- package/app/middleware.js +37 -0
- package/app/pages/assets/custom.css +16 -0
- package/app/pages/boot.js +53 -0
- package/app/pages/common/curl.js +74 -0
- package/app/pages/common/utils.js +2 -0
- package/app/pages/dashboard/complex-view/header-view/complex-view/sub-menu/sub-menu.vue +35 -0
- package/app/pages/dashboard/complex-view/header-view/header-view.vue +132 -0
- package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +45 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/search-panel/search-panel.vue +37 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/table-panel/table-panel.vue +130 -0
- package/app/pages/dashboard/complex-view/schema-view/components/component-config.js +23 -0
- package/app/pages/dashboard/complex-view/schema-view/components/create-form/create-form.vue +90 -0
- package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +110 -0
- package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +133 -0
- package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +125 -0
- package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +97 -0
- package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +35 -0
- package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +127 -0
- package/app/pages/dashboard/dashboard.vue +94 -0
- package/app/pages/dashboard/entry.dashboard.js +43 -0
- package/app/pages/store/index.js +3 -0
- package/app/pages/store/menu.js +59 -0
- package/app/pages/store/project.js +17 -0
- package/app/pages/widgets/header-container/assets/avatar.png +0 -0
- package/app/pages/widgets/header-container/assets/logo.png +0 -0
- package/app/pages/widgets/header-container/header-container.vue +124 -0
- package/app/pages/widgets/schema-form/complex-view/input/input.vue +143 -0
- package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +141 -0
- package/app/pages/widgets/schema-form/complex-view/select/select.vue +127 -0
- package/app/pages/widgets/schema-form/form-item-config.js +22 -0
- package/app/pages/widgets/schema-form/schema-form.vue +129 -0
- package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +49 -0
- package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +65 -0
- package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +43 -0
- package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +50 -0
- package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +133 -0
- package/app/pages/widgets/schema-search-bar/search-item-config.js +26 -0
- package/app/pages/widgets/schema-table/schema-table.vue +234 -0
- package/app/pages/widgets/sider-container/sider-container.vue +32 -0
- package/app/public/static/logo.png +0 -0
- package/app/public/static/normalize.css +239 -0
- package/app/router/project.js +6 -0
- package/app/router/view.js +9 -0
- package/app/router-schema/project.js +30 -0
- package/app/service/base.js +12 -0
- package/app/service/project.js +44 -0
- package/app/view/entry.tpl +26 -0
- package/app/webpack/config/webpack.base.js +194 -0
- package/app/webpack/config/webpack.dev.js +58 -0
- package/app/webpack/config/webpack.prod.js +114 -0
- package/app/webpack/dev.js +48 -0
- package/app/webpack/libs/blank.js +1 -0
- package/app/webpack/prod.js +18 -0
- package/config/config.default.js +3 -0
- package/elpis-core/env.js +20 -0
- package/elpis-core/index.js +88 -0
- package/elpis-core/loader/config.js +55 -0
- package/elpis-core/loader/controller.js +56 -0
- package/elpis-core/loader/extend.js +45 -0
- package/elpis-core/loader/middleware.js +58 -0
- package/elpis-core/loader/router-schema.js +38 -0
- package/elpis-core/loader/router.js +40 -0
- package/elpis-core/loader/service.js +56 -0
- package/index.js +39 -0
- package/model/index.js +104 -0
- package/package.json +93 -0
- package/test/controll/project.test.js +203 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const glob = require('glob');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { sep } = path;
|
|
4
|
+
/*
|
|
5
|
+
* extend loader
|
|
6
|
+
* @param {object} app Koa实例
|
|
7
|
+
*
|
|
8
|
+
* 加载所有extend,最终实现可通过”app.extend.${文件}“访问
|
|
9
|
+
* 例子:
|
|
10
|
+
app/extend
|
|
11
|
+
|
|
|
12
|
+
| -- custom-extend.js
|
|
13
|
+
=> app.extend.customExtend
|
|
14
|
+
*
|
|
15
|
+
*/
|
|
16
|
+
module.exports = (app) => {
|
|
17
|
+
// 读取 elpis/app/extend/**.js 下所有文件
|
|
18
|
+
const elpisExtendPath = path.resolve(__dirname, `..${sep}..${sep}app${sep}extend`);
|
|
19
|
+
const elpisFileList = glob.sync(path.resolve(elpisExtendPath, `.${sep}**${sep}**.js`));
|
|
20
|
+
elpisFileList.forEach(file => handleFile(file));
|
|
21
|
+
|
|
22
|
+
// 读取 业务/app/extend/**.js 下所有文件
|
|
23
|
+
const businessExtendPath = path.resolve(app.businessPath, `.${sep}extend`);
|
|
24
|
+
const businessFileList = glob.sync(path.resolve(businessExtendPath, `.${sep}**${sep}**.js`));
|
|
25
|
+
businessFileList.forEach(file => handleFile(file));
|
|
26
|
+
|
|
27
|
+
// 把内容加载到 app.extend下
|
|
28
|
+
function handleFile(file) {
|
|
29
|
+
// 提取文件名称
|
|
30
|
+
let name = path.resolve(file);
|
|
31
|
+
// 截取路径 app/extend/custom-extend.js => custom-extend
|
|
32
|
+
name = name.substring(name.lastIndexOf(`extend${sep}`) + `extend${sep}`.length, name.lastIndexOf('.'));
|
|
33
|
+
// 把 '-' 统一改成驼峰式,custom-extend.js => customExtend
|
|
34
|
+
name = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());
|
|
35
|
+
// 过滤 app 已存在的 key
|
|
36
|
+
for (const key in app) {
|
|
37
|
+
if (name === key) {
|
|
38
|
+
console.log(`[extend loader error] name:${name} is already in app`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// 挂载 extend 到 app 上
|
|
43
|
+
app[name] = require(path.resolve(file))(app);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const glob = require('glob');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { sep } = path;
|
|
4
|
+
/*
|
|
5
|
+
* middleware loader
|
|
6
|
+
* @param {object} app Koa实例
|
|
7
|
+
*
|
|
8
|
+
* 加载所有middleware,最终实现可通过”app.middlewares.${目录}.${文件}“访问
|
|
9
|
+
* 例子:
|
|
10
|
+
app/middleware
|
|
11
|
+
|
|
|
12
|
+
| -- custom-module
|
|
13
|
+
|
|
|
14
|
+
| -- custom-middleware.js
|
|
15
|
+
=> app.middlewares.customModule.customMiddleware
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
18
|
+
module.exports = (app) => {
|
|
19
|
+
const middlewares = {};
|
|
20
|
+
|
|
21
|
+
// 读取 elpis/app/middleware/**/**.js 下所有文件
|
|
22
|
+
const elpisMiddlewarePath = path.resolve(__dirname, `..${sep}..${sep}app${sep}middleware`);
|
|
23
|
+
const elpisFileList = glob.sync(path.resolve(elpisMiddlewarePath, `.${sep}**${sep}**.js`));
|
|
24
|
+
elpisFileList.forEach(file => handleFile(file));
|
|
25
|
+
|
|
26
|
+
// 读取 业务/app/middleware/**/**.js 下所有文件
|
|
27
|
+
const businessMiddlewarePath = path.resolve(app.businessPath, `.${sep}middleware`);
|
|
28
|
+
const businessFileList = glob.sync(path.resolve(businessMiddlewarePath, `.${sep}**${sep}**.js`));
|
|
29
|
+
businessFileList.forEach(file => handleFile(file));
|
|
30
|
+
|
|
31
|
+
// 把内容加载到 app.middlewares下
|
|
32
|
+
function handleFile(file) {
|
|
33
|
+
// 提取文件名称
|
|
34
|
+
let name = path.resolve(file);
|
|
35
|
+
// 截取路径 app/middleware/custom-module/custom-middleware.js => custom-module/custom-middleware
|
|
36
|
+
name = name.substring(name.lastIndexOf(`middleware${sep}`) + `middleware${sep}`.length, name.lastIndexOf('.'));
|
|
37
|
+
// 把 '-' 统一改成驼峰式,custom-module/custom-middleware.js => customModule.customMiddleware
|
|
38
|
+
name = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());
|
|
39
|
+
// 把middleware挂在到内存 app 对象中
|
|
40
|
+
let tempMiddleware = middlewares;
|
|
41
|
+
const names = name.split(sep);
|
|
42
|
+
for (let i = 0, len = names.length; i < len; ++i) {
|
|
43
|
+
if (i === len - 1) {
|
|
44
|
+
tempMiddleware[names[i]] = require(path.resolve(file))(app);
|
|
45
|
+
} else {
|
|
46
|
+
if (!tempMiddleware[names[i]]) {
|
|
47
|
+
tempMiddleware[names[i]] = {};
|
|
48
|
+
}
|
|
49
|
+
tempMiddleware = tempMiddleware[names[i]]
|
|
50
|
+
// 以上逻辑涉及js对象引用 --- 首先 tempMiddleware = middlewares 使得tempMiddleware和middlewares指向同一对象;
|
|
51
|
+
// for循环里面使tempMiddleware添加新的属性为{},此时middlewares同时也添加了相同的属性,然后又tempMiddleware = tempMiddleware[names[i]],此时tempMiddleware指向新的属性的对象(也就是middlewares新添加属性的对象);
|
|
52
|
+
// 这样middlewares就可以层层增加新的属性;
|
|
53
|
+
// 最终得到的数据格式 => middlewares为{ customModule: { customMiddleware: {} } }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
app.middlewares = middlewares;
|
|
58
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const glob = require('glob');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { sep } = path;
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* router-schema loader
|
|
7
|
+
* @param {Object} app koa实例
|
|
8
|
+
*
|
|
9
|
+
* 通过 ’json-schema & ajv'对 API 规则进行约束,配合 api-params-verify 中间件使用
|
|
10
|
+
*
|
|
11
|
+
* 读取 app/router-schema/**.js
|
|
12
|
+
* 输出:
|
|
13
|
+
app.routerSchema = {
|
|
14
|
+
'${api1}': ${jsonSchema},
|
|
15
|
+
'${api2}': ${jsonSchema},
|
|
16
|
+
'${api3}': ${jsonSchema}
|
|
17
|
+
}
|
|
18
|
+
*/
|
|
19
|
+
module.exports = (app) => {
|
|
20
|
+
let routerSchema = {};
|
|
21
|
+
|
|
22
|
+
// 读取 elpis/app/router-schema/**/**.js 下所有文件
|
|
23
|
+
const elpisRouterSchemaPath = path.resolve(__dirname, `..${sep}..${sep}app${sep}router-schema`);
|
|
24
|
+
const elpisFileList = glob.sync(path.resolve(elpisRouterSchemaPath, `.${sep}**${sep}**.js`));
|
|
25
|
+
elpisFileList.forEach(file => handleFile(file));
|
|
26
|
+
|
|
27
|
+
// 读取 业务/app/router-schema/**/**.js 下所有文件
|
|
28
|
+
const businessRouterSchemaPath = path.resolve(app.businessPath, `.${sep}router-schema`);
|
|
29
|
+
const businessFileList = glob.sync(path.resolve(businessRouterSchemaPath, `.${sep}**${sep}**.js`));
|
|
30
|
+
businessFileList.forEach(file => handleFile(file));
|
|
31
|
+
|
|
32
|
+
// 注册所有 routerSchema,使得可以用 app.routerSchema 访问
|
|
33
|
+
function handleFile(file) {
|
|
34
|
+
routerSchema = {...routerSchema, ...require(path.resolve(file)) };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
app.routerSchema = routerSchema;
|
|
38
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const KoaRouter = require('koa-router');
|
|
2
|
+
const glob = require('glob');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { sep } = path;
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* router loader
|
|
8
|
+
* @param {object} app koa 实例
|
|
9
|
+
*
|
|
10
|
+
* 解析 app/router/ 下所有 js 文件,加载到 KoaRouter 下
|
|
11
|
+
*/
|
|
12
|
+
module.exports = (app) => {
|
|
13
|
+
// 实例化 KoaRouter
|
|
14
|
+
const router = new KoaRouter();
|
|
15
|
+
|
|
16
|
+
// 找到 elpis 路由文件路径
|
|
17
|
+
const elpisRouterPath = path.resolve(__dirname, `..${sep}..${sep}app${sep}router`);
|
|
18
|
+
// 注册所有 elpis 路由
|
|
19
|
+
const elpisFileList = glob.sync(path.resolve(elpisRouterPath, `.${sep}**${sep}**.js`));
|
|
20
|
+
elpisFileList.forEach(file => {
|
|
21
|
+
require(path.resolve(file))(app, router);
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// 找到业务路由文件路径
|
|
25
|
+
const businessFouterPath = path.resolve(app.businessPath, `.${sep}router`);
|
|
26
|
+
// 注册所有业务路由
|
|
27
|
+
const businessFileList = glob.sync(path.resolve(businessFouterPath, `.${sep}**${sep}**.js`));
|
|
28
|
+
businessFileList.forEach(file => {
|
|
29
|
+
require(path.resolve(file))(app, router);
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// 路由兜底(健壮性)
|
|
33
|
+
router.get('*', async (ctx, next) => {
|
|
34
|
+
ctx.status = 302; // 临时重定向
|
|
35
|
+
ctx.redirect(`${app?.options?.homePage ?? '/'}`);
|
|
36
|
+
})
|
|
37
|
+
// 路由注册到 app 上
|
|
38
|
+
app.use(router.routes());
|
|
39
|
+
app.use(router.allowedMethods());
|
|
40
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const glob = require('glob');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { sep } = path;
|
|
4
|
+
/*
|
|
5
|
+
* service loader
|
|
6
|
+
* @param {object} app Koa实例
|
|
7
|
+
*
|
|
8
|
+
* 加载所有service,最终实现可通过”app.service.${目录}.${文件}“访问
|
|
9
|
+
* 例子:
|
|
10
|
+
app/service
|
|
11
|
+
|
|
|
12
|
+
| -- custom-module
|
|
13
|
+
|
|
|
14
|
+
| -- custom-service.js
|
|
15
|
+
=> app.service.customModule.customService
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
18
|
+
module.exports = (app) => {
|
|
19
|
+
const service = {};
|
|
20
|
+
|
|
21
|
+
// 读取 elpis/app/service/**/**.js 下所有文件
|
|
22
|
+
const elpisServicePath = path.resolve(__dirname, `..${sep}..${sep}app${sep}service`);
|
|
23
|
+
const elpisFileList = glob.sync(path.resolve(elpisServicePath, `.${sep}**${sep}**.js`));
|
|
24
|
+
elpisFileList.forEach(file => handleFile(file));
|
|
25
|
+
|
|
26
|
+
// 读取 业务/app/service/**/**.js 下所有文件
|
|
27
|
+
const businessServicePath = path.resolve(app.businessPath, `.${sep}service`);
|
|
28
|
+
const businessFileList = glob.sync(path.resolve(businessServicePath, `.${sep}**${sep}**.js`));
|
|
29
|
+
businessFileList.forEach(file => handleFile(file));
|
|
30
|
+
|
|
31
|
+
// 把内容加载到 app.service下
|
|
32
|
+
function handleFile(file) {
|
|
33
|
+
// 提取文件名称
|
|
34
|
+
let name = path.resolve(file);
|
|
35
|
+
// 截取路径 app/service/custom-module/custom-service.js => custom-module/custom-service
|
|
36
|
+
name = name.substring(name.lastIndexOf(`service${sep}`) + `service${sep}`.length, name.lastIndexOf('.'));
|
|
37
|
+
// 把 '-' 统一改成驼峰式,custom-module/custom-service.js => customModule.customService
|
|
38
|
+
name = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());
|
|
39
|
+
// 把service挂在到内存 app 对象中
|
|
40
|
+
let tempService = service;
|
|
41
|
+
const names = name.split(sep);
|
|
42
|
+
for (let i = 0, len = names.length; i < len; ++i) {
|
|
43
|
+
if (i === len - 1) {
|
|
44
|
+
const ServiceModule = require(path.resolve(file))(app);
|
|
45
|
+
tempService[names[i]] = new ServiceModule();
|
|
46
|
+
} else {
|
|
47
|
+
if (!tempService[names[i]]) {
|
|
48
|
+
tempService[names[i]] = {};
|
|
49
|
+
}
|
|
50
|
+
tempService = tempService[names[i]]
|
|
51
|
+
// 最终得到的数据格式 => service为{ customModule: { customService: {} } }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
app.service = service;
|
|
56
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// 引入elpis-core
|
|
2
|
+
const ElpisCore = require('./elpis-core');
|
|
3
|
+
// 引入 前端工程化构建方法
|
|
4
|
+
const FEBuildDev = require('./app/webpack/dev.js');
|
|
5
|
+
const FEBuildProd = require('./app/webpack/prod.js');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
/**
|
|
10
|
+
* 启动 elpis
|
|
11
|
+
* @param {*} options 项目配置,透传到 elpis-core
|
|
12
|
+
*/
|
|
13
|
+
serverStart (options = {}) {
|
|
14
|
+
const app = ElpisCore.start(options);
|
|
15
|
+
return app; // 返回启动后的 Koa 实例
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 编译构建前端工程
|
|
20
|
+
* @param {*} env 环境变量 local/prod
|
|
21
|
+
*/
|
|
22
|
+
frontendBuild (env) {
|
|
23
|
+
if (env.trim() === 'local') {
|
|
24
|
+
FEBuildDev();
|
|
25
|
+
} else if (env.trim() === 'prod') {
|
|
26
|
+
FEBuildProd();
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 服务端基础
|
|
32
|
+
*/
|
|
33
|
+
Controller: {
|
|
34
|
+
Base: require('./app/controller/base.js')
|
|
35
|
+
},
|
|
36
|
+
Service: {
|
|
37
|
+
Base: require('./app/service/base.js')
|
|
38
|
+
}
|
|
39
|
+
}
|
package/model/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const glob = require('glob');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { sep } = path;
|
|
4
|
+
const _ = require('lodash');
|
|
5
|
+
|
|
6
|
+
// project 继承 model 方法
|
|
7
|
+
const projectExtendModel = (model, project) => {
|
|
8
|
+
return _.mergeWith({}, model, project, (modelValue, projValue) => {
|
|
9
|
+
// 处理数组合并特殊情况
|
|
10
|
+
if (Array.isArray(modelValue) && Array.isArray(projValue)) {
|
|
11
|
+
let result = [];
|
|
12
|
+
// 因为 project 要继承 model,所以要处理一下几种情况
|
|
13
|
+
// 1.project 和 model 配置的相同的键值 => 修改(重载)
|
|
14
|
+
// 2.project 有的键值 model 没有 => 新增(拓展)
|
|
15
|
+
// 3.model 有的键值 project 没有 => 保留(继承)
|
|
16
|
+
|
|
17
|
+
// 处理修改和保留
|
|
18
|
+
for (let i = 0; i < modelValue.length; ++i) {
|
|
19
|
+
let modelItem = modelValue[i];
|
|
20
|
+
const projItem = projValue.find(projItem => projItem.key === modelItem.key);
|
|
21
|
+
if (projItem) {
|
|
22
|
+
// project有的键值,model也有,则递归调用 projectExtendModel 覆盖修改
|
|
23
|
+
result.push(projectExtendModel(modelItem, projItem))
|
|
24
|
+
} else {
|
|
25
|
+
// model 有的键值 project 没有 => 保留(继承)
|
|
26
|
+
result.push(modelItem)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// 处理新增
|
|
30
|
+
for (let i = 0; i < projValue.length; ++i) {
|
|
31
|
+
let projItem = projValue[i];
|
|
32
|
+
const modelItem = modelValue.find(modelItem => modelItem.key === projItem.key);
|
|
33
|
+
if (!modelItem) {
|
|
34
|
+
result.push(projItem);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 解析 model 配置,并返回组织且继承后的数据结构
|
|
45
|
+
* [{
|
|
46
|
+
* model: ${model},
|
|
47
|
+
* project: {
|
|
48
|
+
* proj1: ${proj1},
|
|
49
|
+
* proj2: ${proj2}
|
|
50
|
+
* }
|
|
51
|
+
* }, ...]
|
|
52
|
+
*/
|
|
53
|
+
module.exports = (app) => {
|
|
54
|
+
const modelList = [];
|
|
55
|
+
|
|
56
|
+
// 遍历当前文件夹,构造模型数据结构,挂载到 modelList 上
|
|
57
|
+
const modelPath = path.resolve(app.baseDir, `.${sep}model`);
|
|
58
|
+
const modelFiles = glob.sync(path.resolve(modelPath, `.${sep}**${sep}**.js`));
|
|
59
|
+
modelFiles.forEach(file => {
|
|
60
|
+
if (file.indexOf('index.js') > -1) return;
|
|
61
|
+
|
|
62
|
+
// 统一将路径分隔符转换为正斜杠,便于匹配
|
|
63
|
+
const normalizedFile = file.replace(/\\/g, '/');
|
|
64
|
+
|
|
65
|
+
// 区分配置类型(model / project)
|
|
66
|
+
const type = normalizedFile.includes('/project/') ? 'project' : 'model';
|
|
67
|
+
if (type === 'project') {
|
|
68
|
+
const modelKey = file.match(/\/model\/(.*?)\/project/)?.[1];
|
|
69
|
+
const projKey = file.match(/\/project\/(.*?)\.js/)?.[1];
|
|
70
|
+
let modelItem = modelList.find(item => item.model?.key === modelKey);
|
|
71
|
+
if (!modelItem) { // 初始化 model 数据结构
|
|
72
|
+
modelItem = {};
|
|
73
|
+
modelList.push(modelItem)
|
|
74
|
+
}
|
|
75
|
+
if (!modelItem.project) { // 初始化 project 数据结构
|
|
76
|
+
modelItem.project = {}
|
|
77
|
+
}
|
|
78
|
+
modelItem.project[projKey] = require(path.resolve(file));
|
|
79
|
+
modelItem.project[projKey].key = projKey; // 注入 projectKey
|
|
80
|
+
modelItem.project[projKey].modelKey = modelKey; // 注入 modelKey
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (type === 'model') {
|
|
84
|
+
const modelKey = file.match(/\/model\/(.*?)\/model\.js/)?.[1];
|
|
85
|
+
let modelItem = modelList.find(item => item.model?.key === modelKey);
|
|
86
|
+
if (!modelItem) { // 初始化 model 数据结构
|
|
87
|
+
modelItem = {};
|
|
88
|
+
modelList.push(modelItem);
|
|
89
|
+
}
|
|
90
|
+
modelItem.model = require(path.resolve(file));
|
|
91
|
+
modelItem.model.key = modelKey; // 注入 modelKey
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// 数据进一步处理:project => 继承 model
|
|
96
|
+
modelList.forEach(item => {
|
|
97
|
+
const { model, project } = item;
|
|
98
|
+
for (const key in project) {
|
|
99
|
+
project[key] = projectExtendModel(model, project[key])
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return modelList;
|
|
104
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@siven-li/elpis",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "set _ENV=local && mocha 'test/**/*.js'",
|
|
8
|
+
"lint": "eslint --quiet --ext js,vue ."
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://git.code.tencent.com/sunflower-li/Elpis.git"
|
|
13
|
+
},
|
|
14
|
+
"author": "lsw",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@babel/core": "^7.24.0",
|
|
18
|
+
"@babel/runtime": "^7.24.0",
|
|
19
|
+
"@element-plus/icons-vue": "^2.3.2",
|
|
20
|
+
"ajv": "^6.10.2",
|
|
21
|
+
"axios": "^0.19.2",
|
|
22
|
+
"echarts": "^5.5.0",
|
|
23
|
+
"element-plus": "^2.3.7",
|
|
24
|
+
"generate-password": "^1.7.1",
|
|
25
|
+
"glob": "^7.1.4",
|
|
26
|
+
"jsonwebtoken": "^9.0.2",
|
|
27
|
+
"knex": "^0.19.0",
|
|
28
|
+
"koa": "2.7.0",
|
|
29
|
+
"koa-bodyparser": "^4.2.1",
|
|
30
|
+
"koa-nunjucks-2": "^3.0.2",
|
|
31
|
+
"koa-router": "^7.4.0",
|
|
32
|
+
"koa-static": "^5.0.0",
|
|
33
|
+
"koa-useragent": "2.0.0",
|
|
34
|
+
"koa2-cors": "^2.0.6",
|
|
35
|
+
"less": "^3.8.1",
|
|
36
|
+
"lodash": "^4.17.21",
|
|
37
|
+
"log4js": "^6.9.1",
|
|
38
|
+
"md5": "^2.2.1",
|
|
39
|
+
"moment": "^2.29.4",
|
|
40
|
+
"mysql": "^2.18.1",
|
|
41
|
+
"node-schedule": "^2.1.1",
|
|
42
|
+
"nodemon": "^1.19.2",
|
|
43
|
+
"path": "^0.12.7",
|
|
44
|
+
"pinia": "^2.1.6",
|
|
45
|
+
"regenerator-runtime": "^0.14.0",
|
|
46
|
+
"superagent": "^8.1.2",
|
|
47
|
+
"vue": "^3.3.4",
|
|
48
|
+
"vue-json-viewer": "^3.0.4",
|
|
49
|
+
"vue-router": "^4.2.4",
|
|
50
|
+
"vuex": "^4.1.0",
|
|
51
|
+
"@babel/plugin-transform-runtime": "^7.1.0",
|
|
52
|
+
"@babel/preset-env": "^7.4.5",
|
|
53
|
+
"babel-loader": "^8.0.4",
|
|
54
|
+
"clean-webpack-plugin": "^0.1.19",
|
|
55
|
+
"consoler": "^0.2.0",
|
|
56
|
+
"css-loader": "^0.23.1",
|
|
57
|
+
"css-minimizer-webpack-plugin": "^5.0.1",
|
|
58
|
+
"directory-named-webpack-plugin": "^4.0.1",
|
|
59
|
+
"express": "^4.18.2",
|
|
60
|
+
"file-loader": "^6.2.0",
|
|
61
|
+
"happypack": "^5.0.1",
|
|
62
|
+
"html-webpack-inject-attributes-plugin": "^1.0.1",
|
|
63
|
+
"html-webpack-plugin": "^5.5.3",
|
|
64
|
+
"less-loader": "^11.1.3",
|
|
65
|
+
"mini-css-extract-plugin": "^2.7.6",
|
|
66
|
+
"style-loader": "^0.14.1",
|
|
67
|
+
"terser-webpack-plugin": "^5.3.10",
|
|
68
|
+
"url-loader": "^4.1.1",
|
|
69
|
+
"vue-loader": "^17.2.2",
|
|
70
|
+
"vue-style-loader": "^4.1.2",
|
|
71
|
+
"webpack": "^5.88.1",
|
|
72
|
+
"webpack-dev-middleware": "^6.1.1",
|
|
73
|
+
"webpack-hot-middleware": "^2.25.4",
|
|
74
|
+
"webpack-merge": "^4.2.1"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"assert": "^2.0.0",
|
|
78
|
+
"babel-eslint": "^10.0.2",
|
|
79
|
+
"eslint": "^7.32.0",
|
|
80
|
+
"eslint-plugin-import": "^2.28.1",
|
|
81
|
+
"eslint-plugin-vue": "^9.17.0",
|
|
82
|
+
"ghooks": "~1.0.3",
|
|
83
|
+
"mocha": "^6.1.4",
|
|
84
|
+
"supertest": "^4.0.2",
|
|
85
|
+
"validate-commit-msg": "~2.14.0"
|
|
86
|
+
},
|
|
87
|
+
"config": {
|
|
88
|
+
"ghooks": {
|
|
89
|
+
"commit-msg": "validate-commit-msg",
|
|
90
|
+
"pre-commit": "npm run lint"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const supertest = require('supertest');
|
|
3
|
+
const md5 = require('md5');
|
|
4
|
+
const elpisCore = require('../../elpis-core');
|
|
5
|
+
|
|
6
|
+
const signKey = 'li11shi08z2cx3shi0wen920';
|
|
7
|
+
const st = Date.now();
|
|
8
|
+
|
|
9
|
+
describe('测试 project 相关接口', function() {
|
|
10
|
+
this.timeout(60000);
|
|
11
|
+
|
|
12
|
+
let modelList;
|
|
13
|
+
const projectList = [];
|
|
14
|
+
|
|
15
|
+
let request;
|
|
16
|
+
|
|
17
|
+
it('启动服务', async () => {
|
|
18
|
+
const app = await elpisCore.start();
|
|
19
|
+
|
|
20
|
+
modelList = require('../../model/index.js')(app);
|
|
21
|
+
modelList.forEach(modelItem => {
|
|
22
|
+
const { project } = modelItem;
|
|
23
|
+
for(const projKey in project) {
|
|
24
|
+
projectList.push(project[projKey]);
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
request = supertest(app.listen());
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('GET /api/project without proj_key', async () => {
|
|
32
|
+
let temRequest = request.get('/api/project');
|
|
33
|
+
temRequest = temRequest.set('s_t', st);
|
|
34
|
+
temRequest = temRequest.set('s_sign', md5(`${signKey}_${st}`));
|
|
35
|
+
const res = await temRequest;
|
|
36
|
+
assert(res.body.success === false);
|
|
37
|
+
|
|
38
|
+
const resBody = res.body;
|
|
39
|
+
assert(resBody.code === 442);
|
|
40
|
+
assert(resBody.message.indexOf(`request validate fail: data should have required property 'proj_key'`) > -1);
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('GET /api/project fail', async () => {
|
|
44
|
+
let temRequest = request.get('/api/project');
|
|
45
|
+
temRequest = temRequest.set('s_t', st);
|
|
46
|
+
temRequest = temRequest.set('s_sign', md5(`${signKey}_${st}`));
|
|
47
|
+
temRequest = temRequest.query({ proj_key: 'not_exist_proj_key' });
|
|
48
|
+
const res = await temRequest;
|
|
49
|
+
assert(res.body.success === false);
|
|
50
|
+
|
|
51
|
+
const resBody = res.body;
|
|
52
|
+
assert(resBody.code === 50000);
|
|
53
|
+
assert(resBody.message.indexOf(`项目配置不存在`) > -1);
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('GET /api/project with proj_key', async () => {
|
|
57
|
+
for (let i = 0; i < projectList.length; ++i) {
|
|
58
|
+
const projItem = projectList[i];
|
|
59
|
+
const { key: projKey } = projItem;
|
|
60
|
+
|
|
61
|
+
console.log(`---- GET /api/project with proj_key: ${projKey}`);
|
|
62
|
+
|
|
63
|
+
let temRequest = request.get('/api/project');
|
|
64
|
+
temRequest = temRequest.set('s_t', st);
|
|
65
|
+
temRequest = temRequest.set('s_sign', md5(`${signKey}_${st}`));
|
|
66
|
+
temRequest = temRequest.query({ proj_key: projKey });
|
|
67
|
+
const res = await temRequest;
|
|
68
|
+
assert(res.body.success === true);
|
|
69
|
+
|
|
70
|
+
const resData = res.body.data;
|
|
71
|
+
assert(resData.key === projKey);
|
|
72
|
+
assert(resData.modelKey);
|
|
73
|
+
assert(resData.name);
|
|
74
|
+
assert(resData.desc !== undefined);
|
|
75
|
+
assert(resData.homePage !== undefined);
|
|
76
|
+
|
|
77
|
+
const { menu } = resData;
|
|
78
|
+
menu.forEach(menuItem => {
|
|
79
|
+
checkMenuItem(menuItem);
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 校验 menu 菜单
|
|
85
|
+
function checkMenuItem(menuItem) {
|
|
86
|
+
console.log(`--- GET /api/project with proj_key--menuKey: ${menuItem.key}`);
|
|
87
|
+
assert(menuItem.key);
|
|
88
|
+
assert(menuItem.name);
|
|
89
|
+
assert(menuItem.menuType);
|
|
90
|
+
|
|
91
|
+
if (menuItem.menuType === 'group'){
|
|
92
|
+
assert(menuItem.subMenu !== undefined)
|
|
93
|
+
menuItem.subMenu.forEach(subMenuItem => {
|
|
94
|
+
checkMenuItem(subMenuItem);
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (menuItem.menuType === 'module'){
|
|
99
|
+
checkModule(menuItem);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 检查 module 菜单配置
|
|
104
|
+
function checkModule(menuItem) {
|
|
105
|
+
const { moduleType } = menuItem;
|
|
106
|
+
assert(moduleType);
|
|
107
|
+
|
|
108
|
+
if (moduleType === 'sider') {
|
|
109
|
+
const { siderConfig } = menuItem;
|
|
110
|
+
assert(siderConfig);
|
|
111
|
+
assert(siderConfig.menu)
|
|
112
|
+
siderConfig.menu.forEach(siderMenuItem => {
|
|
113
|
+
checkMenuItem(siderMenuItem);
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (moduleType === 'iframe') {
|
|
118
|
+
const { iframeConfig } = menuItem;
|
|
119
|
+
assert(iframeConfig);
|
|
120
|
+
assert(iframeConfig.path !== undefined);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (moduleType === 'custom') {
|
|
124
|
+
const { customConfig } = menuItem;
|
|
125
|
+
assert(customConfig);
|
|
126
|
+
assert(customConfig.path !== undefined);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (moduleType === 'schema') {
|
|
130
|
+
const { schemaConfig } = menuItem;
|
|
131
|
+
assert(schemaConfig);
|
|
132
|
+
assert(schemaConfig.api !== undefined);
|
|
133
|
+
assert(schemaConfig.schema);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
it('GET /api/project/list without proj_key', async () => {
|
|
140
|
+
let temRequest = request.get('/api/project/list');
|
|
141
|
+
temRequest = temRequest.set('s_t', st);
|
|
142
|
+
temRequest = temRequest.set('s_sign', md5(`${signKey}_${st}`));
|
|
143
|
+
const res = await temRequest;
|
|
144
|
+
assert(res.body.success === true);
|
|
145
|
+
|
|
146
|
+
const resData = res.body.data;
|
|
147
|
+
assert(resData.length === projectList.length);
|
|
148
|
+
for (let i = 0; i < resData.length; ++i) {
|
|
149
|
+
const item = resData[i];
|
|
150
|
+
assert(item.modelKey);
|
|
151
|
+
assert(item.key);
|
|
152
|
+
assert(item.name);
|
|
153
|
+
assert(item.desc !== undefined);
|
|
154
|
+
assert(item.homePage !== undefined);
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('GET /api/project/list with proj_key', async () => {
|
|
159
|
+
const { key: projKey } = projectList[Math.floor(Math.random() * projectList.length)];
|
|
160
|
+
const { modelKey } = projectList.find(item => item.key === projKey);
|
|
161
|
+
|
|
162
|
+
let temRequest = request.get('/api/project/list');
|
|
163
|
+
temRequest = temRequest.set('s_t', st);
|
|
164
|
+
temRequest = temRequest.set('s_sign', md5(`${signKey}_${st}`));
|
|
165
|
+
temRequest = temRequest.query({ proj_key: projKey });
|
|
166
|
+
const res = await temRequest;
|
|
167
|
+
assert(res.body.success === true);
|
|
168
|
+
|
|
169
|
+
const resData = res.body.data;
|
|
170
|
+
assert(projectList.filter(item => item.modelKey === modelKey).length === resData.length);
|
|
171
|
+
for (let i = 0; i < resData.length; ++i) {
|
|
172
|
+
const item = resData[i];
|
|
173
|
+
assert(item.modelKey);
|
|
174
|
+
assert(item.key);
|
|
175
|
+
assert(item.name);
|
|
176
|
+
assert(item.desc !== undefined);
|
|
177
|
+
assert(item.homePage !== undefined);
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('GET /api/project/model_list', async () => {
|
|
182
|
+
let temRequest = request.get('/api/project/model_list');
|
|
183
|
+
temRequest = temRequest.set('s_t', st);
|
|
184
|
+
temRequest = temRequest.set('s_sign', md5(`${signKey}_${st}`));
|
|
185
|
+
const res = await temRequest;
|
|
186
|
+
assert(res.body.success === true);
|
|
187
|
+
|
|
188
|
+
const resData = res.body.data;
|
|
189
|
+
assert(resData.length > 0);
|
|
190
|
+
|
|
191
|
+
for(let i = 0; i < resData.length; ++i) {
|
|
192
|
+
const item = resData[i];
|
|
193
|
+
assert(item.model);
|
|
194
|
+
assert(item.model.key);
|
|
195
|
+
assert(item.model.name);
|
|
196
|
+
assert(item.project);
|
|
197
|
+
for(const projKey in item.project) {
|
|
198
|
+
assert(item.project[projKey].key);
|
|
199
|
+
assert(item.project[projKey].name)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
})
|