@yukihong/schema-admin-x 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 +208 -0
- package/app/controller/base.js +39 -0
- package/app/controller/project.js +80 -0
- package/app/controller/view.js +20 -0
- package/app/extend/logger.js +39 -0
- package/app/middleware/api-params-verify.js +79 -0
- package/app/middleware/api-sign-verify.js +34 -0
- package/app/middleware/error-handler.js +34 -0
- package/app/middleware/project-handler.js +27 -0
- package/app/middleware.js +40 -0
- package/app/pages/asserts/custom.css +13 -0
- package/app/pages/boot.js +53 -0
- package/app/pages/common/curl.js +91 -0
- package/app/pages/common/utils.js +3 -0
- package/app/pages/dashboard/complex-view/header-view/complex-view/sub-menu/sub-menu.vue +21 -0
- package/app/pages/dashboard/complex-view/header-view/header-view.vue +127 -0
- package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +44 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/search-panel/search-panel.vue +41 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/table-panel/table-panel.vue +131 -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 +100 -0
- package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +106 -0
- package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +131 -0
- package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +126 -0
- package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +102 -0
- package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +26 -0
- package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +128 -0
- package/app/pages/dashboard/dashboard.vue +99 -0
- package/app/pages/dashboard/entry.dashboard.js +44 -0
- package/app/pages/store/index.js +4 -0
- package/app/pages/store/menu.js +61 -0
- package/app/pages/store/project.js +17 -0
- package/app/pages/widgets/header-container/asserts/avatar.png +0 -0
- package/app/pages/widgets/header-container/asserts/logo.png +0 -0
- package/app/pages/widgets/header-container/header-container.vue +109 -0
- package/app/pages/widgets/schema-form/complex-view/input/input.vue +146 -0
- package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +140 -0
- package/app/pages/widgets/schema-form/complex-view/select/select.vue +121 -0
- package/app/pages/widgets/schema-form/form-item-config.js +23 -0
- package/app/pages/widgets/schema-form/schema-form.vue +131 -0
- package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +50 -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 +44 -0
- package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +51 -0
- package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +127 -0
- package/app/pages/widgets/schema-search-bar/search-item-config.js +27 -0
- package/app/pages/widgets/schema-table/schema-table.vue +248 -0
- package/app/pages/widgets/sider-container/sider-container.vue +28 -0
- package/app/public/static/logo.png +0 -0
- package/app/public/static/normalize.css +239 -0
- package/app/router/project.js +7 -0
- package/app/router/view.js +10 -0
- package/app/router-schema/project.js +30 -0
- package/app/service/base.js +13 -0
- package/app/service/project.js +50 -0
- package/app/view/entry.tpl +25 -0
- package/app/webpack/config/webpack.base.js +213 -0
- package/app/webpack/config/webpack.dev.js +61 -0
- package/app/webpack/config/webpack.prod.js +124 -0
- package/app/webpack/dev.js +63 -0
- package/app/webpack/libs/blank.js +1 -0
- package/app/webpack/prod.js +19 -0
- package/config/config.beta.js +3 -0
- package/config/config.default.js +3 -0
- package/config/config.prod.js +3 -0
- package/elpis-core/env.js +20 -0
- package/elpis-core/index.js +99 -0
- package/elpis-core/loader/config.js +52 -0
- package/elpis-core/loader/controller.js +75 -0
- package/elpis-core/loader/extend.js +55 -0
- package/elpis-core/loader/middleware.js +64 -0
- package/elpis-core/loader/router-schema.js +47 -0
- package/elpis-core/loader/router.js +59 -0
- package/elpis-core/loader/service.js +75 -0
- package/index.js +40 -0
- package/model/index.js +102 -0
- package/package.json +93 -0
- package/test/controller/project.test.js +217 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const glob = require('glob');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { sep } = path;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* controller loader
|
|
7
|
+
* @param {object} app Koa 实例
|
|
8
|
+
* 加载所有 controller,可通过 'app.controllers.${目录}.${文件}' 访问
|
|
9
|
+
*
|
|
10
|
+
例子:
|
|
11
|
+
app/controller
|
|
12
|
+
|
|
|
13
|
+
| -- custom-module
|
|
14
|
+
|
|
|
15
|
+
| -- custom-controller.js
|
|
16
|
+
=> app.controller.customModule.customController
|
|
17
|
+
*/
|
|
18
|
+
module.exports = (app) => {
|
|
19
|
+
// 1. 初始化一个空对象,用来存放所有控制器的嵌套结构
|
|
20
|
+
const controller = {};
|
|
21
|
+
|
|
22
|
+
// 读取 elpis/app/controller/**/**.js 下所有的文件
|
|
23
|
+
const elpisControllerPath = path.resolve(__dirname, `..${sep}..${sep}app${sep}controller`);
|
|
24
|
+
const elpisFileList = glob.sync(path.resolve(elpisControllerPath, `.${sep}**${sep}**.js`));
|
|
25
|
+
elpisFileList.forEach(file => {
|
|
26
|
+
handleFile(file);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// 读取 业务根目录/app/controller/**/**.js 下所有的文件
|
|
30
|
+
const businessControllerPath = path.resolve(app.businessPath, `.${sep}controller`);
|
|
31
|
+
const businessFileList = glob.sync(path.resolve(businessControllerPath, `.${sep}**${sep}**.js`));
|
|
32
|
+
businessFileList.forEach(file => {
|
|
33
|
+
handleFile(file);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 遍历所有文件目录,把内容加载到 app.controller 下
|
|
37
|
+
function handleFile(file) {
|
|
38
|
+
// 2. 提取当前文件的完整路径(比如:/project/app/controller/custom-module/custom-controller.js)
|
|
39
|
+
let name = path.resolve(file);
|
|
40
|
+
|
|
41
|
+
// 3. 截取路径 app/controller/custom-module.custom-controller.js => custom-module.custom-controller
|
|
42
|
+
name = name.substring( name.lastIndexOf(`controller${sep}`) + `controller${sep}`.length, name.lastIndexOf('.'));
|
|
43
|
+
|
|
44
|
+
// 4. 把 '-' 统一改为驼峰式,custom-module/custom-controller.js => customModule.customController
|
|
45
|
+
name = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());
|
|
46
|
+
|
|
47
|
+
// 5. 每次处理新文件时,重置 tempController 指向根对象 controller
|
|
48
|
+
// (tempController 是一个“移动指针”,用来一层层深入嵌套结构)
|
|
49
|
+
// 挂载 controller 到内存 app 对象中
|
|
50
|
+
let tempController = controller;
|
|
51
|
+
|
|
52
|
+
// 6. 把处理后的路径按分隔符拆分成数组(比如: [customModule(目录), customController(文件)])
|
|
53
|
+
const names = name.split(sep);
|
|
54
|
+
|
|
55
|
+
// 7. 循环处理每一层目录/文件名
|
|
56
|
+
for( let i = 0, len = names.length; i < len; ++i) {
|
|
57
|
+
if( i === len - 1 ) { // 8. 文件
|
|
58
|
+
// 导入中间件文件并传入app初始化,挂载到当前层级
|
|
59
|
+
const ControllerMoule = require(path.resolve(file))(app);
|
|
60
|
+
tempController[names[i]] = new ControllerMoule();
|
|
61
|
+
} else {
|
|
62
|
+
// 9. 如果是目录
|
|
63
|
+
// 如果当前层级不存在这个目录对象,就创建一个空对象
|
|
64
|
+
if(!tempController[names[i]]) {
|
|
65
|
+
tempController[names[i]] = {};
|
|
66
|
+
}
|
|
67
|
+
// 10. 移动指针(动的探测手),让 tempController 指向刚创建的子目录对象
|
|
68
|
+
// (因为是引用类型,后续修改会同步到根对象 controller)
|
|
69
|
+
tempController = tempController[names[i]];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
app.controller = controller;
|
|
75
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const glob = require('glob');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { sep } = path;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* extend loader
|
|
7
|
+
* @param {object} app Koa 实例
|
|
8
|
+
* 加载所有 extend,可通过 'app.extend.${文件}' 访问
|
|
9
|
+
*
|
|
10
|
+
例子:
|
|
11
|
+
app/extend
|
|
12
|
+
|
|
|
13
|
+
| -- custom-extend.js
|
|
14
|
+
=> app.extend.customExtend
|
|
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 => {
|
|
21
|
+
handleFile(file);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// 读取 业务/app/extend/**.js 下所有的文件
|
|
25
|
+
const businessExtendPath = path.resolve(app.businessPath, `.${sep}extend`);
|
|
26
|
+
const businessFileList = glob.sync(path.resolve(businessExtendPath, `.${sep}**${sep}**.js`));
|
|
27
|
+
businessFileList.forEach(file => {
|
|
28
|
+
handleFile(file);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// 遍历所有文件目录,把内容加载到 app.extend 下
|
|
32
|
+
function handleFile(file) {
|
|
33
|
+
// 2. 提取当前文件的完整路径(比如:/project/app/extend/custom-extend.js)
|
|
34
|
+
let name = path.resolve(file);
|
|
35
|
+
|
|
36
|
+
// 3. 截取路径 app/extend/custom-extend.js => custom-extend
|
|
37
|
+
name = name.substring( name.lastIndexOf(`extend${sep}`) + `extend${sep}`.length, name.lastIndexOf('.'));
|
|
38
|
+
|
|
39
|
+
// 4. 把 '-' 统一改为驼峰式,custom-extend.js => customExtend
|
|
40
|
+
name = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());
|
|
41
|
+
|
|
42
|
+
// 过滤 app 已存在的 key
|
|
43
|
+
for( const key in app ) {
|
|
44
|
+
if( key === name ) {
|
|
45
|
+
console.log(`[exten load error] name:${name} is already in app`)
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 挂载 extend 到 app 上
|
|
51
|
+
app[name] = require(path.resolve(file))(app);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const glob = require('glob');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { sep } = path;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* middleware loader
|
|
7
|
+
* @param {object} app Koa 实例
|
|
8
|
+
* 加载所有 middleware,可通过 'app.middlewares.${目录}.${文件}' 访问
|
|
9
|
+
*
|
|
10
|
+
例子:
|
|
11
|
+
app/middleware
|
|
12
|
+
|
|
|
13
|
+
| -- custom-module
|
|
14
|
+
|
|
|
15
|
+
| -- custom-middleware.js
|
|
16
|
+
=> app.middlewares.customModule.customMiddleware
|
|
17
|
+
*/
|
|
18
|
+
module.exports = (app) => {
|
|
19
|
+
const middlewares = {};
|
|
20
|
+
// 读取 /elpis/app/middleware/**/**.js 下所有的文件
|
|
21
|
+
const elpisMiddlewarePath = path.resolve(__dirname, `..${sep}..${sep}app${sep}middleware`);
|
|
22
|
+
const elpisFileList = glob.sync(path.resolve(elpisMiddlewarePath, `.${sep}**${sep}**.js`));
|
|
23
|
+
elpisFileList.forEach(file => {
|
|
24
|
+
handleFile(file);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// 读取 业务根目录/app/middleware/**/**.js 下所有的文件
|
|
28
|
+
const businessMiddlewarePath = path.resolve(app.businessPath, `.${sep}middleware`);
|
|
29
|
+
const businessFileList = glob.sync(path.resolve(businessMiddlewarePath, `.${sep}**${sep}**.js`));
|
|
30
|
+
businessFileList.forEach(file => {
|
|
31
|
+
handleFile(file);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 把内容加载到 app.middlewares 下
|
|
35
|
+
function handleFile(file){
|
|
36
|
+
// 提取文件名称
|
|
37
|
+
let name = path.resolve(file);
|
|
38
|
+
|
|
39
|
+
// 截取路径 app/middlewares/custom-module.custom-middleware.js => custom-module.custom-middleware
|
|
40
|
+
name = name.substring( name.lastIndexOf(`middleware${sep}`) + `middleware${sep}`.length, name.lastIndexOf('.'));
|
|
41
|
+
|
|
42
|
+
// 把 '-' 统一改为驼峰式,custom-module/custom-middleware.js => customModule.customMiddleware
|
|
43
|
+
name = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());
|
|
44
|
+
|
|
45
|
+
// 挂载 middleware 到内存 app 对象中
|
|
46
|
+
let tempMiddleware = middlewares;
|
|
47
|
+
const names = name.split(sep);
|
|
48
|
+
|
|
49
|
+
for( let i = 0, len = names.length; i < len; ++i) {
|
|
50
|
+
if( i === len - 1 ) {
|
|
51
|
+
// 导入中间件文件并传入app初始化,挂载到当前层级
|
|
52
|
+
tempMiddleware[names[i]] = require(path.resolve(file))(app);
|
|
53
|
+
} else {
|
|
54
|
+
if(!tempMiddleware[names[i]]) {
|
|
55
|
+
tempMiddleware[names[i]] = {};
|
|
56
|
+
}
|
|
57
|
+
// 移动指针指向当前层级的子对象(引用类型,修改会同步到原对象)
|
|
58
|
+
tempMiddleware = tempMiddleware[names[i]];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
app.middlewares = middlewares;
|
|
64
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
'${api4}': ${jsonSchema},
|
|
18
|
+
}
|
|
19
|
+
*/
|
|
20
|
+
module.exports = (app) => {
|
|
21
|
+
// 注册所有 routerSchema, 使得可以 'app.routerSchema' 这样访问
|
|
22
|
+
let routerSchema = {};
|
|
23
|
+
|
|
24
|
+
// 读取 elpis/app/router-schema/**/**.js 下所有的文件
|
|
25
|
+
const elpisRouterSchemaPath = path.resolve(__dirname, `..${sep}..${sep}app${sep}router-schema`);
|
|
26
|
+
const elpisFileList = glob.sync(path.resolve(elpisRouterSchemaPath, `.${sep}**${sep}**.js`));
|
|
27
|
+
elpisFileList.forEach(file => {
|
|
28
|
+
handleFile(file);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// 读取 业务/app/router-schema/**/**.js 下所有的文件
|
|
32
|
+
const businessRouterSchemaPath = path.resolve(app.businessPath, `.${sep}router-schema`);
|
|
33
|
+
const businessFileList = glob.sync(path.resolve(businessRouterSchemaPath, `.${sep}**${sep}**.js`));
|
|
34
|
+
businessFileList.forEach(file => {
|
|
35
|
+
handleFile(file);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function handleFile(file){
|
|
39
|
+
routerSchema = {
|
|
40
|
+
...routerSchema,
|
|
41
|
+
...require(path.resolve(file))
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
app.routerSchema = routerSchema
|
|
47
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
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 businessRouterPath = path.resolve(app.businessPath, `.${sep}router`);
|
|
26
|
+
// 注册所有业务路由
|
|
27
|
+
const businessFileList = glob.sync(path.resolve(businessRouterPath, `.${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
|
+
|
|
38
|
+
// 路由注册到 app 上
|
|
39
|
+
app.use(router.routes());
|
|
40
|
+
app.use(router.allowedMethods());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Koa路由匹配完整流程(核心注释版)
|
|
45
|
+
* 执行顺序:中间件 → 路由匹配 → 兜底处理
|
|
46
|
+
*/
|
|
47
|
+
// 1. 请求进入Koa应用,先执行前置中间件(静态文件/日志/跨域等)[使用了koa-static才会去找静态文件]
|
|
48
|
+
// - 若匹配静态文件(如/static/css/page1.css),直接返回文件,流程终止
|
|
49
|
+
// - 若未匹配,进入下一步路由匹配
|
|
50
|
+
|
|
51
|
+
// 2. 执行router.routes()中间件(路由匹配核心)
|
|
52
|
+
// - 遍历所有已注册的具体路由(如/page1、/api/user)
|
|
53
|
+
// - 按注册顺序匹配请求路径+请求方法(GET/POST)
|
|
54
|
+
// - 匹配成功:执行对应路由处理函数,返回响应,流程终止
|
|
55
|
+
// - 匹配失败:进入兜底路由
|
|
56
|
+
|
|
57
|
+
// 3. 执行兜底路由(router.get('*', ...))
|
|
58
|
+
// - 匹配所有未被具体路由拦截的请求
|
|
59
|
+
// - 执行兜底逻辑(如重定向首页/返回404),流程终止
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const glob = require('glob');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { sep } = path;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* service loader
|
|
7
|
+
* @param {object} app Koa 实例
|
|
8
|
+
* 加载所有 service,可通过 'app.service.${目录}.${文件}' 访问
|
|
9
|
+
*
|
|
10
|
+
例子:
|
|
11
|
+
app/service
|
|
12
|
+
|
|
|
13
|
+
| -- custom-module
|
|
14
|
+
|
|
|
15
|
+
| -- custom-service.js
|
|
16
|
+
=> app.service.customModule.customService
|
|
17
|
+
*/
|
|
18
|
+
module.exports = (app) => {
|
|
19
|
+
// 1. 初始化一个空对象,用来存放所有控制器的嵌套结构
|
|
20
|
+
const service = {};
|
|
21
|
+
|
|
22
|
+
// 读取 elpis/app/service/**/**.js 下所有的文件
|
|
23
|
+
const elpisServicePath = path.resolve(__dirname, `..${sep}..${sep}app${sep}service`);
|
|
24
|
+
const elpisFileList = glob.sync(path.resolve(elpisServicePath, `.${sep}**${sep}**.js`));
|
|
25
|
+
elpisFileList.forEach(file => {
|
|
26
|
+
handleFile(file);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// 读取 业务/app/service/**/**.js 下所有的文件
|
|
30
|
+
const businessServicePath = path.resolve(app.businessPath, `.${sep}service`);
|
|
31
|
+
const businessFileList = glob.sync(path.resolve(businessServicePath, `.${sep}**${sep}**.js`));
|
|
32
|
+
businessFileList.forEach(file => {
|
|
33
|
+
handleFile(file);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 遍历所有文件目录,把内容加载到 app.service 下
|
|
37
|
+
function handleFile(file) {
|
|
38
|
+
// 2. 提取当前文件的完整路径(比如:/project/app/service/custom-module/custom-service.js)
|
|
39
|
+
let name = path.resolve(file);
|
|
40
|
+
|
|
41
|
+
// 3. 截取路径 app/service/custom-module.custom-service.js => custom-module.custom-service
|
|
42
|
+
name = name.substring( name.lastIndexOf(`service${sep}`) + `service${sep}`.length, name.lastIndexOf('.'));
|
|
43
|
+
|
|
44
|
+
// 4. 把 '-' 统一改为驼峰式,custom-module/custom-service.js => customModule.customService
|
|
45
|
+
name = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());
|
|
46
|
+
|
|
47
|
+
// 5. 每次处理新文件时,重置 tempService 指向根对象 service
|
|
48
|
+
// (tempService 是一个“移动指针”,用来一层层深入嵌套结构)
|
|
49
|
+
// 挂载 service 到内存 app 对象中
|
|
50
|
+
let tempService = service;
|
|
51
|
+
|
|
52
|
+
// 6. 把处理后的路径按分隔符拆分成数组(比如: [customModule(目录), tempService(文件)])
|
|
53
|
+
const names = name.split(sep);
|
|
54
|
+
|
|
55
|
+
// 7. 循环处理每一层目录/文件名
|
|
56
|
+
for( let i = 0, len = names.length; i < len; ++i) {
|
|
57
|
+
if( i === len - 1 ) { // 8. 文件
|
|
58
|
+
// 导入中间件文件并传入app初始化,挂载到当前层级
|
|
59
|
+
const ServiceMoule = require(path.resolve(file))(app);
|
|
60
|
+
tempService[names[i]] = new ServiceMoule();
|
|
61
|
+
} else {
|
|
62
|
+
// 9. 如果是目录
|
|
63
|
+
// 如果当前层级不存在这个目录对象,就创建一个空对象
|
|
64
|
+
if(!tempService[names[i]]) {
|
|
65
|
+
tempService[names[i]] = {};
|
|
66
|
+
}
|
|
67
|
+
// 10. 移动指针(动的探测手),让 tempService 指向刚创建的子目录对象
|
|
68
|
+
// (因为是引用类型,后续修改会同步到根对象 service)
|
|
69
|
+
tempService = tempService[names[i]];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
app.service = service;
|
|
75
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const ElpisCore = require('./elpis-core');
|
|
2
|
+
|
|
3
|
+
// 引入 前端工程化构建方法
|
|
4
|
+
const FEBuildDev = require('./app/webpack/dev.js');
|
|
5
|
+
const FEBuildProd = require('./app/webpack/prod.js');
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 服务端基础
|
|
11
|
+
*/
|
|
12
|
+
Controller: {
|
|
13
|
+
Base: require('./app/controller/base.js')
|
|
14
|
+
},
|
|
15
|
+
Service: {
|
|
16
|
+
Base: require('./app/service/base.js')
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 编译构建前端工程
|
|
22
|
+
* @params env 环境变量 local/production
|
|
23
|
+
*/
|
|
24
|
+
frontendBuild(env) {
|
|
25
|
+
if(env === 'local') {
|
|
26
|
+
FEBuildDev();
|
|
27
|
+
} else if (env === 'production') {
|
|
28
|
+
FEBuildProd();
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 启动 elpis
|
|
34
|
+
* @params options 项目配置,透传到 elpis-core
|
|
35
|
+
*/
|
|
36
|
+
serverStart(options = {}) {
|
|
37
|
+
const app = ElpisCore.start(options);
|
|
38
|
+
return app;
|
|
39
|
+
}
|
|
40
|
+
}
|
package/model/index.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const glob = require('glob');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { sep } = path;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* project 继承 model
|
|
8
|
+
*/
|
|
9
|
+
const projectExtendModel = (model, project) => {
|
|
10
|
+
return _.mergeWith({}, model, project, (modelValue, projValue) => {
|
|
11
|
+
// 处理数组合并的特殊情况
|
|
12
|
+
if (Array.isArray(modelValue) && Array.isArray(projValue)) {
|
|
13
|
+
let result = [];
|
|
14
|
+
|
|
15
|
+
// 因为 project 继承 model,所以需要处理修改和新增内容的情况
|
|
16
|
+
// project 有的键值,model 也有 => 修改(重载)
|
|
17
|
+
// project 有的键值,model 没有 => 新增
|
|
18
|
+
// model 有的键值,project 没有 => 保留(继承)
|
|
19
|
+
|
|
20
|
+
// 处理修改和保留
|
|
21
|
+
for(let i = 0; i < modelValue.length; ++i) {
|
|
22
|
+
let modelItem = modelValue[i];
|
|
23
|
+
const projItem = projValue.find(projItem => projItem.key === modelItem.key);
|
|
24
|
+
// project 有的键值,model 也有,则递归调用 projectExtendModel 方法修改
|
|
25
|
+
result.push(projItem ? projectExtendModel(modelItem, projItem) : modelItem);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 处理新增
|
|
29
|
+
for(let i = 0; i < projValue.length; ++i) {
|
|
30
|
+
const projItem = projValue[i];
|
|
31
|
+
const modelItem = modelValue.find(modelItem => modelItem.key === projItem.key);
|
|
32
|
+
|
|
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
|
+
proj1Key: ${proj1},
|
|
49
|
+
proj2Key: ${proj2},
|
|
50
|
+
}
|
|
51
|
+
}, ...]
|
|
52
|
+
*/
|
|
53
|
+
module.exports = (app) => {
|
|
54
|
+
const modelList = [];
|
|
55
|
+
|
|
56
|
+
// 遍历当前文件夹,构造模型数据结构,挂载到 modelList 上
|
|
57
|
+
const modelPath = path.resolve(process.cwd(), `.${sep}model`);
|
|
58
|
+
const fileList = glob.sync(path.resolve(modelPath, `.${sep}**${sep}**.js`));
|
|
59
|
+
fileList.forEach(file => {
|
|
60
|
+
if(file.indexOf('index.js') > -1) { return;}
|
|
61
|
+
|
|
62
|
+
// 区分配置类型(model / project) 备注:之前使用`${sep}project${sep}`在windows系统下返回是\,而glob.sync分隔符不管什么平台都是/
|
|
63
|
+
const type = file.indexOf(`/project/`) > -1 ? 'project' : 'model';
|
|
64
|
+
if(type === 'project' ) {
|
|
65
|
+
const modelKey = file.match(/\/model\/(.*?)\/project/)?.[1];
|
|
66
|
+
const projKey = file.match(/\/project\/(.*?)\.js/)?.[1];
|
|
67
|
+
let modelItem = modelList.find(item => item.model?.key === modelKey);
|
|
68
|
+
if(!modelItem) { // 初始化 model 数据结构
|
|
69
|
+
modelItem = {};
|
|
70
|
+
modelList.push(modelItem);
|
|
71
|
+
}
|
|
72
|
+
if(!modelItem.project) { // 初始化 project 数据结构
|
|
73
|
+
modelItem.project = {};
|
|
74
|
+
}
|
|
75
|
+
modelItem.project[projKey] = require(path.resolve(file));
|
|
76
|
+
modelItem.project[projKey].key = projKey; // 注入 projectKey
|
|
77
|
+
modelItem.project[projKey].modelKey = modelKey; // 注入 modelKey
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if( type === 'model') {
|
|
81
|
+
const modelKey = file.match(/\/model\/(.*?)\/model\.js/)?.[1];
|
|
82
|
+
let modelItem = modelList.find(item => item.model?.key === modelKey);
|
|
83
|
+
if(!modelItem) {
|
|
84
|
+
modelItem = {};
|
|
85
|
+
modelList.push(modelItem);
|
|
86
|
+
}
|
|
87
|
+
modelItem.model = require(path.resolve(file));
|
|
88
|
+
modelItem.model.key = modelKey; // 注入 modelKey
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// 数据进一步整理: project => 继承 model
|
|
94
|
+
modelList.forEach(item => {
|
|
95
|
+
const { model, project } = item;
|
|
96
|
+
for(const key in project) {
|
|
97
|
+
project[key] = projectExtendModel(model, project[key]);
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
return modelList;
|
|
102
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yukihong/schema-admin-x",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"lint": "eslint --quiet --ext js,vue .",
|
|
8
|
+
"test": "_ENV='local mocha 'test/**/*.js'",
|
|
9
|
+
"test:win": "set _ENV=local&&mocha 'test/**/*.js'"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://git.code.tencent.com/yukiHong/Elpis.git"
|
|
14
|
+
},
|
|
15
|
+
"author": "Yuki",
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@babel/core": "^7.24.0",
|
|
19
|
+
"@babel/runtime": "^7.28.6",
|
|
20
|
+
"@element-plus/icons-vue": "^2.3.2",
|
|
21
|
+
"ajv": "^6.10.2",
|
|
22
|
+
"axios": "^0.19.2",
|
|
23
|
+
"echarts": "^5.5.0",
|
|
24
|
+
"element-plus": "^2.3.7",
|
|
25
|
+
"generate-password": "^1.7.1",
|
|
26
|
+
"glob": "^7.1.4",
|
|
27
|
+
"jsonwebtoken": "^9.0.2",
|
|
28
|
+
"knex": "^0.19.0",
|
|
29
|
+
"koa": "2.7.0",
|
|
30
|
+
"koa-bodyparser": "^4.2.1",
|
|
31
|
+
"koa-nunjucks-2": "^3.0.2",
|
|
32
|
+
"koa-router": "^7.4.0",
|
|
33
|
+
"koa-static": "^5.0.0",
|
|
34
|
+
"koa-useragent": "2.0.0",
|
|
35
|
+
"koa2-cors": "^2.0.6",
|
|
36
|
+
"less": "^3.8.1",
|
|
37
|
+
"lodash": "^4.17.21",
|
|
38
|
+
"log4js": "^6.9.1",
|
|
39
|
+
"md5": "^2.2.1",
|
|
40
|
+
"moment": "^2.29.4",
|
|
41
|
+
"mysql": "^2.18.1",
|
|
42
|
+
"node-schedule": "^2.1.1",
|
|
43
|
+
"nodemon": "^1.19.2",
|
|
44
|
+
"path": "^0.12.7",
|
|
45
|
+
"pinia": "^2.1.6",
|
|
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": "latest",
|
|
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
|
+
}
|