@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.
Files changed (78) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc +55 -0
  3. package/README.md +201 -0
  4. package/app/controller/base.js +37 -0
  5. package/app/controller/project.js +65 -0
  6. package/app/controller/view.js +20 -0
  7. package/app/extend/logger.js +35 -0
  8. package/app/middleware/api-params-verify.js +97 -0
  9. package/app/middleware/api-sign-verify.js +30 -0
  10. package/app/middleware/error-handler.js +34 -0
  11. package/app/middleware/project-handler.js +22 -0
  12. package/app/middleware.js +37 -0
  13. package/app/pages/assets/custom.css +16 -0
  14. package/app/pages/boot.js +53 -0
  15. package/app/pages/common/curl.js +74 -0
  16. package/app/pages/common/utils.js +2 -0
  17. package/app/pages/dashboard/complex-view/header-view/complex-view/sub-menu/sub-menu.vue +35 -0
  18. package/app/pages/dashboard/complex-view/header-view/header-view.vue +132 -0
  19. package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +45 -0
  20. package/app/pages/dashboard/complex-view/schema-view/complex-view/search-panel/search-panel.vue +37 -0
  21. package/app/pages/dashboard/complex-view/schema-view/complex-view/table-panel/table-panel.vue +130 -0
  22. package/app/pages/dashboard/complex-view/schema-view/components/component-config.js +23 -0
  23. package/app/pages/dashboard/complex-view/schema-view/components/create-form/create-form.vue +90 -0
  24. package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +110 -0
  25. package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +133 -0
  26. package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +125 -0
  27. package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +97 -0
  28. package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +35 -0
  29. package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +127 -0
  30. package/app/pages/dashboard/dashboard.vue +94 -0
  31. package/app/pages/dashboard/entry.dashboard.js +43 -0
  32. package/app/pages/store/index.js +3 -0
  33. package/app/pages/store/menu.js +59 -0
  34. package/app/pages/store/project.js +17 -0
  35. package/app/pages/widgets/header-container/assets/avatar.png +0 -0
  36. package/app/pages/widgets/header-container/assets/logo.png +0 -0
  37. package/app/pages/widgets/header-container/header-container.vue +124 -0
  38. package/app/pages/widgets/schema-form/complex-view/input/input.vue +143 -0
  39. package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +141 -0
  40. package/app/pages/widgets/schema-form/complex-view/select/select.vue +127 -0
  41. package/app/pages/widgets/schema-form/form-item-config.js +22 -0
  42. package/app/pages/widgets/schema-form/schema-form.vue +129 -0
  43. package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +49 -0
  44. package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +65 -0
  45. package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +43 -0
  46. package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +50 -0
  47. package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +133 -0
  48. package/app/pages/widgets/schema-search-bar/search-item-config.js +26 -0
  49. package/app/pages/widgets/schema-table/schema-table.vue +234 -0
  50. package/app/pages/widgets/sider-container/sider-container.vue +32 -0
  51. package/app/public/static/logo.png +0 -0
  52. package/app/public/static/normalize.css +239 -0
  53. package/app/router/project.js +6 -0
  54. package/app/router/view.js +9 -0
  55. package/app/router-schema/project.js +30 -0
  56. package/app/service/base.js +12 -0
  57. package/app/service/project.js +44 -0
  58. package/app/view/entry.tpl +26 -0
  59. package/app/webpack/config/webpack.base.js +194 -0
  60. package/app/webpack/config/webpack.dev.js +58 -0
  61. package/app/webpack/config/webpack.prod.js +114 -0
  62. package/app/webpack/dev.js +48 -0
  63. package/app/webpack/libs/blank.js +1 -0
  64. package/app/webpack/prod.js +18 -0
  65. package/config/config.default.js +3 -0
  66. package/elpis-core/env.js +20 -0
  67. package/elpis-core/index.js +88 -0
  68. package/elpis-core/loader/config.js +55 -0
  69. package/elpis-core/loader/controller.js +56 -0
  70. package/elpis-core/loader/extend.js +45 -0
  71. package/elpis-core/loader/middleware.js +58 -0
  72. package/elpis-core/loader/router-schema.js +38 -0
  73. package/elpis-core/loader/router.js +40 -0
  74. package/elpis-core/loader/service.js +56 -0
  75. package/index.js +39 -0
  76. package/model/index.js +104 -0
  77. package/package.json +93 -0
  78. 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
+ })