@tushi11/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 (76) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc +55 -0
  3. package/README.md +239 -0
  4. package/app/controller/base.js +42 -0
  5. package/app/controller/project.js +78 -0
  6. package/app/controller/view.js +19 -0
  7. package/app/extend/logger.js +36 -0
  8. package/app/middleware/api-params-verify.js +75 -0
  9. package/app/middleware/api-sign-verify.js +37 -0
  10. package/app/middleware/error-handler.js +33 -0
  11. package/app/middleware/project-handler.js +27 -0
  12. package/app/middleware.js +41 -0
  13. package/app/pages/asserts/custom.css +13 -0
  14. package/app/pages/boot.js +49 -0
  15. package/app/pages/common/curl.js +86 -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 +20 -0
  18. package/app/pages/dashboard/complex-view/header-view/header-view.vue +134 -0
  19. package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +48 -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 +127 -0
  22. package/app/pages/dashboard/complex-view/schema-view/components/components-config.js +23 -0
  23. package/app/pages/dashboard/complex-view/schema-view/components/create-form/create-form.vue +101 -0
  24. package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +98 -0
  25. package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +125 -0
  26. package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +130 -0
  27. package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +101 -0
  28. package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +23 -0
  29. package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +130 -0
  30. package/app/pages/dashboard/dashboard.vue +99 -0
  31. package/app/pages/dashboard/entry.dashboard.js +46 -0
  32. package/app/pages/store/index.js +4 -0
  33. package/app/pages/store/menu.js +73 -0
  34. package/app/pages/store/project.js +17 -0
  35. package/app/pages/widgets/header-container/asserts/avatar.png +0 -0
  36. package/app/pages/widgets/header-container/asserts/logo.png +0 -0
  37. package/app/pages/widgets/header-container/header-container.vue +98 -0
  38. package/app/pages/widgets/schema-form/complex-view/input/input.vue +135 -0
  39. package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +135 -0
  40. package/app/pages/widgets/schema-form/complex-view/select/select.vue +116 -0
  41. package/app/pages/widgets/schema-form/form-item-config.js +23 -0
  42. package/app/pages/widgets/schema-form/schema-form.vue +144 -0
  43. package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +45 -0
  44. package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +60 -0
  45. package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +31 -0
  46. package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +40 -0
  47. package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +120 -0
  48. package/app/pages/widgets/schema-search-bar/search-item-config.js +27 -0
  49. package/app/pages/widgets/schema-table/schema-table.vue +255 -0
  50. package/app/pages/widgets/sider-container/sider-container.vue +28 -0
  51. package/app/router/project.js +12 -0
  52. package/app/router/view.js +8 -0
  53. package/app/router-schema/project.js +31 -0
  54. package/app/service/base.js +13 -0
  55. package/app/service/project.js +46 -0
  56. package/app/view/entry.tpl +25 -0
  57. package/app/webpack/config/webpack.base.js +267 -0
  58. package/app/webpack/config/webpack.dev.js +62 -0
  59. package/app/webpack/config/webpack.prod.js +118 -0
  60. package/app/webpack/dev.js +56 -0
  61. package/app/webpack/libs/blank.js +1 -0
  62. package/app/webpack/prod.js +22 -0
  63. package/config/config.default.js +12 -0
  64. package/elpis-core/env.js +20 -0
  65. package/elpis-core/index.js +98 -0
  66. package/elpis-core/loader/config.js +57 -0
  67. package/elpis-core/loader/controller.js +82 -0
  68. package/elpis-core/loader/extend.js +62 -0
  69. package/elpis-core/loader/middleware.js +74 -0
  70. package/elpis-core/loader/router-schema.js +56 -0
  71. package/elpis-core/loader/router.js +51 -0
  72. package/elpis-core/loader/service.js +74 -0
  73. package/index.js +38 -0
  74. package/model/index.js +111 -0
  75. package/package.json +92 -0
  76. package/test/controller/project.test.js +215 -0
@@ -0,0 +1,56 @@
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
+ */
21
+ module.exports = (app) => {
22
+ let routerSchema = {};
23
+
24
+ // 读取 elpis/app/router-schema/**/**.js 下所有的文件
25
+ const elpisRouterSchemaPath = path.resolve(
26
+ __dirname,
27
+ `..${sep}..${sep}app${sep}router-schema`
28
+ );
29
+ const elpisFileList = glob.sync(
30
+ path.resolve(elpisRouterSchemaPath, `.${sep}**${sep}**.js`)
31
+ );
32
+ elpisFileList.forEach((file) => {
33
+ handleFile(file);
34
+ });
35
+
36
+ // 读取 业务/app/router-schema/**/**.js 下所有的文件
37
+ const businessRouterSchemaPath = path.resolve(
38
+ app.businessPath,
39
+ `.${sep}router-schema`
40
+ );
41
+ const businessFileList = glob.sync(
42
+ path.resolve(businessRouterSchemaPath, `.${sep}**${sep}**.js`)
43
+ );
44
+ businessFileList.forEach((file) => {
45
+ handleFile(file);
46
+ });
47
+
48
+ // 注册所有 routerSchema,使得可以 'app.routerSchema' 这样访问
49
+ function handleFile(file) {
50
+ routerSchema = {
51
+ ...routerSchema,
52
+ ...require(path.resolve(file)),
53
+ };
54
+ }
55
+ app.routerSchema = routerSchema;
56
+ };
@@ -0,0 +1,51 @@
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(
18
+ __dirname,
19
+ `..${sep}..${sep}app${sep}router`
20
+ );
21
+ // 注册所有业务路由
22
+ const elpisFileList = glob.sync(
23
+ path.resolve(elpisRouterPath, `.${sep}**${sep}**.js`)
24
+ );
25
+ elpisFileList.forEach((file) => {
26
+ // module.exports = (app, router) => {
27
+ // router.get("xxx/x/x/x", xxxContr);
28
+ // };
29
+ require(path.resolve(file))(app, router);
30
+ });
31
+
32
+ // 找到业务路由文件路径
33
+ const businessRouterPath = path.resolve(app.businessPath, `.${sep}router`);
34
+ // 注册所有业务路由
35
+ const businessFileList = glob.sync(
36
+ path.resolve(businessRouterPath, `.${sep}**${sep}**.js`)
37
+ );
38
+ businessFileList.forEach((file) => {
39
+ require(path.resolve(file))(app, router);
40
+ });
41
+
42
+ // 路由兜底(健壮性)
43
+ router.get('*', async (ctx, next) => {
44
+ ctx.status = 302; // 临时重定向
45
+ ctx.redirect(`${app?.options?.homePage ?? '/'}`);
46
+ });
47
+
48
+ // 路由注册到 app 上
49
+ app.use(router.routes());
50
+ app.use(router.allowedMethods());
51
+ };
@@ -0,0 +1,74 @@
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.services.${目录}.${文件}' 访问
9
+ * 例子:
10
+ * app/service
11
+ * |
12
+ * | -- custom-module (文件夹)
13
+ * |
14
+ * | -- custom-service.js
15
+ * 通过这样的方式去访问 ==> app.service.customModule.customService
16
+ */
17
+ module.exports = (app) => {
18
+ const service = {};
19
+
20
+ // 读取 elpis/app/service/**/**.js 下所有的文件
21
+ const elpisServicePath = path.resolve(
22
+ __dirname,
23
+ `..${sep}..${sep}app${sep}service`
24
+ );
25
+ const elpisFileList = glob.sync(
26
+ path.resolve(elpisServicePath, `.${sep}**${sep}**.js`)
27
+ );
28
+ elpisFileList.forEach((file) => {
29
+ handleFile(file);
30
+ });
31
+
32
+ // 读取 业务/app/service/**/**.js 下所有的文件
33
+ const businessServicePath = path.resolve(app.businessPath, `.${sep}service`);
34
+ const businessFileList = glob.sync(
35
+ path.resolve(businessServicePath, `.${sep}**${sep}**.js`)
36
+ );
37
+ businessFileList.forEach((file) => {
38
+ handleFile(file);
39
+ });
40
+
41
+ // 把内容加载到 app.service 下
42
+ function handleFile(file) {
43
+ // 提取文件名称
44
+ let name = path.resolve(file);
45
+
46
+ // 截取路径 app/service/custom-module/custom-service => custom-module/custom-service
47
+ name = name.substring(
48
+ name.lastIndexOf(`service${sep}`) + `service${sep}`.length,
49
+ name.lastIndexOf('.')
50
+ );
51
+
52
+ // 把 '-' 统一改为驼峰式,custom-module/custom-service.js => customModule.customService
53
+ name = name.replace(/[_-][a-z]/gi, (s) => s.substring(1).toUpperCase());
54
+
55
+ // 挂载 service 到 内存 app 对象中
56
+ let tempService = service;
57
+ const names = name.split(sep); // [ customModule(目录), customService(文件) ]
58
+ for (let i = 0, len = names.length; i < len; ++i) {
59
+ if (i === len - 1) {
60
+ // 文件
61
+ const ServiceModule = require(path.resolve(file))(app);
62
+ tempService[names[i]] = new ServiceModule();
63
+ } else {
64
+ // 文件夹
65
+ if (!tempService[names[i]]) {
66
+ tempService[names[i]] = {};
67
+ }
68
+ tempService = tempService[names[i]];
69
+ }
70
+ }
71
+ }
72
+
73
+ app.service = service;
74
+ };
package/index.js ADDED
@@ -0,0 +1,38 @@
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
+ module.exports = {
8
+ /**
9
+ * 服务端基础
10
+ */
11
+ Controller: {
12
+ Base: require('./app/controller/base.js'),
13
+ },
14
+ Service: {
15
+ ...require('./app/service/base.js'),
16
+ },
17
+
18
+ /**
19
+ * 编译构建前端工程
20
+ * @param env 环境变量 local/prod
21
+ */
22
+ frontendBuild(env) {
23
+ if (env === 'local') {
24
+ FEBuildDev();
25
+ } else if (env === 'production') {
26
+ FEbuildProd();
27
+ }
28
+ },
29
+
30
+ /**
31
+ * 启动 elpis
32
+ * @param options 项目配置,透传到 elpis-core
33
+ */
34
+ serverStart(options = {}) {
35
+ const app = ElpisCore.start(options);
36
+ return app;
37
+ },
38
+ };
package/model/index.js ADDED
@@ -0,0 +1,111 @@
1
+ const _ = require('lodash');
2
+ const glob = require('glob');
3
+ const path = require('path');
4
+ const { sep } = path;
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
+
13
+ // 因为 project 继承 model,所以需要处理修改和新增内容的情况
14
+ // project有的键值,model也有 => 修改(重载)
15
+ // project有的键值,model没有 => 新增(拓展)
16
+ // model有的键值,project没有 => 保留(继承)
17
+
18
+ // 处理修改和保留
19
+ for (let i = 0; i < modelValue.length; ++i) {
20
+ let modelItem = modelValue[i];
21
+ const projItem = projValue.find(
22
+ (projItem) => projItem.key === modelItem.key
23
+ );
24
+ // project有的键值,model也有,则递归调用 projectExtendModel 方法覆盖修改
25
+ result.push(
26
+ projItem ? projectExtendModel(modelItem, projItem) : modelItem
27
+ );
28
+ }
29
+
30
+ // 处理新增
31
+ for (let i = 0; i < projValue.length; ++i) {
32
+ const projItem = projValue[i];
33
+ const modelItem = modelValue.find(
34
+ (modelItem) => modelItem.key === projItem.key
35
+ );
36
+ if (!modelItem) {
37
+ result.push(projItem);
38
+ }
39
+ }
40
+
41
+ return result;
42
+ }
43
+ });
44
+ };
45
+
46
+ /**
47
+ * 解析 model 配置,并返回组织且继承后的数据结构
48
+ * [{
49
+ * model: ${model}
50
+ * project: {
51
+ * proj1Key: ${proj1},
52
+ * proj2Key: ${proj2}
53
+ * }
54
+ * },...]
55
+ */
56
+ module.exports = (app) => {
57
+ const modelList = [];
58
+
59
+ // 遍历当前文件夹,构造模型数据结构,挂载到 modelList 上
60
+ const modelPath = path.resolve(process.cwd(), `.${sep}model`);
61
+ const fileList = glob.sync(path.resolve(modelPath, `.${sep}**${sep}**.js`));
62
+ fileList.forEach((file) => {
63
+ if (file.indexOf('index.js') > -1) return;
64
+
65
+ // 区分配置类型 (model / project)
66
+ const type =
67
+ path.resolve(file).indexOf(`${sep}project${sep}`) > -1
68
+ ? 'project'
69
+ : 'model'; // 在 Windows 系统中,file.indexOf(`${sep}project${sep}`) 获取不对的原因是 glob 模块返回的路径使用了 /,而 ${sep} 在 Windows 上是 \。
70
+
71
+ if (type === 'project') {
72
+ const modelKey = file.match(/\/model\/(.*?)\/project/)?.[1];
73
+ const projKey = file.match(/\/project\/(.*?)\.js/)?.[1];
74
+ let modelItem = modelList.find((item) => item.model?.key === modelKey);
75
+ if (!modelItem) {
76
+ // 初始化 model 数据结构
77
+ modelItem = {};
78
+ modelList.push(modelItem);
79
+ }
80
+ if (!modelItem.project) {
81
+ // 初始化 project 数据结构
82
+ modelItem.project = {};
83
+ }
84
+ modelItem.project[projKey] = require(path.resolve(file));
85
+ modelItem.project[projKey].key = projKey; // 注入 projectKey
86
+ modelItem.project[projKey].modelKey = modelKey; // 注入 modelKey
87
+ }
88
+
89
+ if (type === 'model') {
90
+ const modelKey = file.match(/\/model\/(.*?)\/model\.js/)?.[1];
91
+ let modelItem = modelList.find((item) => item.model?.key === modelKey);
92
+ if (!modelItem) {
93
+ // 初始化 model 数据结构
94
+ modelItem = {};
95
+ modelList.push(modelItem);
96
+ }
97
+ modelItem.model = require(path.resolve(file));
98
+ modelItem.model.key = modelKey; // 注入 modelKey
99
+ }
100
+ });
101
+
102
+ // 数据进一步整理: project => 继承 model
103
+ modelList.forEach((item) => {
104
+ const { model, project } = item;
105
+ for (const key in project) {
106
+ project[key] = projectExtendModel(model, project[key]);
107
+ }
108
+ });
109
+
110
+ return modelList;
111
+ };
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@tushi11/elpis",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "lint": "eslint --quiet --ext js,vue .",
8
+ "test": "cross-env _ENV='local' mocha 'test/**/*.js'"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "\u0016https://e.coding.net/g-hawq5881/elpis/elpis.git"
13
+ },
14
+ "author": "Mr.邹",
15
+ "license": "ISC",
16
+ "dependencies": {
17
+ "@babel/core": "^7.24.0",
18
+ "@element-plus/icons-vue": "^2.3.1",
19
+ "ajv": "^6.10.2",
20
+ "axios": "^0.19.2",
21
+ "echarts": "^5.5.0",
22
+ "element-plus": "^2.3.7",
23
+ "generate-password": "^1.7.1",
24
+ "glob": "^7.1.4",
25
+ "jsonwebtoken": "^9.0.2",
26
+ "knex": "^0.19.0",
27
+ "koa": "2.7.0",
28
+ "koa-bodyparser": "^4.2.1",
29
+ "koa-nunjucks-2": "^3.0.2",
30
+ "koa-router": "^7.4.0",
31
+ "koa-static": "^5.0.0",
32
+ "koa-useragent": "2.0.0",
33
+ "koa2-cors": "^2.0.6",
34
+ "less": "^3.8.1",
35
+ "lodash": "^4.17.21",
36
+ "log4js": "^6.9.1",
37
+ "md5": "^2.2.1",
38
+ "moment": "^2.29.4",
39
+ "mysql": "^2.18.1",
40
+ "node-schedule": "^2.1.1",
41
+ "nodemon": "^1.19.2",
42
+ "path": "^0.12.7",
43
+ "pinia": "^2.1.6",
44
+ "superagent": "^8.1.2",
45
+ "vue": "^3.3.4",
46
+ "vue-json-viewer": "^3.0.4",
47
+ "vue-router": "^4.2.4",
48
+ "vuex": "^4.1.0",
49
+ "@babel/plugin-transform-runtime": "^7.1.0",
50
+ "@babel/preset-env": "^7.4.5",
51
+ "babel-loader": "^8.0.4",
52
+ "clean-webpack-plugin": "^0.1.19",
53
+ "consoler": "^0.2.0",
54
+ "cross-env": "^7.0.3",
55
+ "css-loader": "^0.23.1",
56
+ "css-minimizer-webpack-plugin": "^5.0.1",
57
+ "directory-named-webpack-plugin": "^4.0.1",
58
+ "express": "^4.18.2",
59
+ "file-loader": "^6.2.0",
60
+ "happypack": "^5.0.1",
61
+ "html-webpack-inject-attributes-plugin": "^1.0.1",
62
+ "html-webpack-plugin": "^5.5.3",
63
+ "less-loader": "^11.1.3",
64
+ "mini-css-extract-plugin": "^2.7.6",
65
+ "style-loader": "^0.14.1",
66
+ "terser-webpack-plugin": "^2.3.5",
67
+ "url-loader": "^4.1.1",
68
+ "vue-loader": "^17.2.2",
69
+ "vue-style-loader": "^4.1.2",
70
+ "webpack": "^5.88.1",
71
+ "webpack-dev-middleware": "^6.1.1",
72
+ "webpack-hot-middleware": "^2.25.4",
73
+ "webpack-merge": "^4.2.1"
74
+ },
75
+ "devDependencies": {
76
+ "assert": "^2.0.0",
77
+ "babel-eslint": "^10.0.2",
78
+ "eslint": "^7.32.0",
79
+ "eslint-plugin-import": "^2.28.1",
80
+ "eslint-plugin-vue": "^9.17.0",
81
+ "ghooks": "~1.0.3",
82
+ "mocha": "^6.1.4",
83
+ "supertest": "^4.0.2",
84
+ "validate-commit-msg": "~2.14.0"
85
+ },
86
+ "config": {
87
+ "ghooks": {
88
+ "commit-msg": "validate-commit-msg",
89
+ "pre-commit": "npm run lint"
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,215 @@
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 = 'apisignverifykey001';
7
+ const st = Date.now();
8
+
9
+ describe('测试 project 相关接口', function () {
10
+ this.timeout(60000);
11
+
12
+ let modelList;
13
+ const projectList = [];
14
+ let request;
15
+
16
+ it('启动服务', async () => {
17
+ const app = await elpisCore.start({});
18
+ modelList = require('../../model/index.js')(app);
19
+ modelList.forEach((modelItem) => {
20
+ const { project } = modelItem;
21
+ for (const pKey in project) {
22
+ projectList.push(project[pKey]);
23
+ }
24
+ });
25
+ request = supertest(app.listen());
26
+ });
27
+
28
+ it('GET /api/project without proj_key', async () => {
29
+ let tmpRequest = request.get('/api/project');
30
+ tmpRequest = tmpRequest.set('s_t', st);
31
+ tmpRequest = tmpRequest.set('s_sign', md5(`${signKey}_${st}`));
32
+ const res = await tmpRequest;
33
+ assert(res.body.success === false);
34
+
35
+ const resBody = res.body;
36
+ assert(resBody.code === 442);
37
+ assert(
38
+ resBody.message.indexOf(
39
+ `request validate fail: data should have required property 'proj_key'`
40
+ ) > -1
41
+ );
42
+ });
43
+
44
+ it('GET /api/project fail', async () => {
45
+ let tmpRequest = request.get('/api/project');
46
+ tmpRequest = tmpRequest.set('s_t', st);
47
+ tmpRequest = tmpRequest.set('s_sign', md5(`${signKey}_${st}`));
48
+ tmpRequest = tmpRequest.query({ proj_key: 'xxxxxx' });
49
+
50
+ const res = await tmpRequest;
51
+ assert(res.body.success === false);
52
+
53
+ const resBody = res.body;
54
+ assert(resBody.code === 50000);
55
+ assert(resBody.message === '获取项目异常');
56
+ });
57
+
58
+ it('GET /api/project with proj_key', async () => {
59
+ // const { key: projKey } =
60
+ // projectList[Math.floor(Math.random() * projectList.length)];
61
+ for (let i = 0; i < projectList.length; ++i) {
62
+ const projItem = projectList[i];
63
+ const { key: projKey } = projItem;
64
+ console.log(' --------- GET /api/project with proj_key:', projKey);
65
+
66
+ let tmpRequest = request.get('/api/project');
67
+ tmpRequest = tmpRequest.set('s_t', st);
68
+ tmpRequest = tmpRequest.set('s_sign', md5(`${signKey}_${st}`));
69
+ tmpRequest = tmpRequest.query({ proj_key: projKey });
70
+
71
+ const res = await tmpRequest;
72
+ assert(res.body.success === true);
73
+
74
+ const resData = res.body.data;
75
+ assert(resData.key === projKey);
76
+ assert(resData.modelKey);
77
+ assert(resData.name);
78
+ assert(resData.desc !== undefined);
79
+ assert(resData.homePage !== undefined);
80
+
81
+ const { menu } = resData;
82
+ menu.forEach((menuItem) => {
83
+ checkMenuItem(menuItem);
84
+ });
85
+ }
86
+
87
+ // 校验 menu 菜单
88
+ function checkMenuItem(menuItem) {
89
+ assert(menuItem.key);
90
+ assert(menuItem.name);
91
+ assert(menuItem.menuType);
92
+
93
+ if (menuItem.menuType === 'group') {
94
+ assert(menuItem.subMenu !== undefined);
95
+ menuItem.subMenu.forEach((submenuItem) => {
96
+ checkMenuItem(submenuItem);
97
+ });
98
+ }
99
+ if (menuItem.menuType === 'module') {
100
+ checkModule(menuItem);
101
+ }
102
+ }
103
+
104
+ // 检查 module 菜单配置
105
+ function checkModule(menuItem) {
106
+ console.log(
107
+ ' --------- GET /api/project with proj_key - menuKey:',
108
+ menuItem.key
109
+ );
110
+ const { moduleType } = menuItem;
111
+ assert(moduleType);
112
+
113
+ if (moduleType === 'sider') {
114
+ const { siderConfig } = menuItem;
115
+ assert(siderConfig);
116
+ assert(siderConfig.menu);
117
+ siderConfig.menu.forEach((siderMenuItem) => {
118
+ checkMenuItem(siderMenuItem);
119
+ });
120
+ }
121
+
122
+ if (moduleType === 'iframe') {
123
+ const { iframeConfig } = menuItem;
124
+ assert(iframeConfig);
125
+ assert(iframeConfig.path !== undefined);
126
+ }
127
+
128
+ if (moduleType === 'custom') {
129
+ const { customConfig } = menuItem;
130
+ assert(customConfig);
131
+ assert(customConfig.path !== undefined);
132
+ }
133
+
134
+ if (moduleType === 'schema') {
135
+ const { schemaConfig } = menuItem;
136
+ assert(schemaConfig);
137
+ assert(schemaConfig.api !== undefined);
138
+ assert(schemaConfig.schema);
139
+ }
140
+ }
141
+ });
142
+
143
+ it('GET /api/project/list without proj_key', async () => {
144
+ let tmpRequest = request.get('/api/project/list');
145
+ tmpRequest = tmpRequest.set('s_t', st);
146
+ tmpRequest = tmpRequest.set('s_sign', md5(`${signKey}_${st}`));
147
+ const res = await tmpRequest;
148
+ assert(res.body.success === true);
149
+
150
+ const resData = res.body.data;
151
+
152
+ assert(resData.length === projectList.length);
153
+ for (let i = 0; i < resData.length; i++) {
154
+ const item = resData[i];
155
+ assert(item.modelKey);
156
+ assert(item.key);
157
+ assert(item.name);
158
+ assert(item.desc !== undefined);
159
+ assert(item.homePage !== undefined);
160
+ }
161
+ });
162
+
163
+ it('GET /api/project/list with proj_key', async () => {
164
+ const { key: projKey } =
165
+ projectList[Math.floor(Math.random() * projectList.length)];
166
+ console.log(` --------- GET /api/project/list with proj_key: ${projKey}`);
167
+
168
+ const { modelKey } = projectList.find((item) => item.key === projKey);
169
+
170
+ let tmpRequest = request.get('/api/project/list');
171
+ tmpRequest = tmpRequest.set('s_t', st);
172
+ tmpRequest = tmpRequest.set('s_sign', md5(`${signKey}_${st}`));
173
+ tmpRequest = tmpRequest.query({ proj_key: projKey });
174
+
175
+ const res = await tmpRequest;
176
+ assert(res.body.success === true);
177
+
178
+ const resData = res.body.data;
179
+ assert(
180
+ projectList.filter((item) => item.modelKey === modelKey).length ===
181
+ resData.length
182
+ );
183
+ for (let i = 0; i < resData.length; i++) {
184
+ const item = resData[i];
185
+ assert(item.modelKey);
186
+ assert(item.key);
187
+ assert(item.name);
188
+ assert(item.desc !== undefined);
189
+ assert(item.homePage !== undefined);
190
+ }
191
+ });
192
+
193
+ it('GET /api/project/model_list', async () => {
194
+ let tmpRequest = request.get('/api/project/model_list');
195
+ tmpRequest = tmpRequest.set('s_t', st);
196
+ tmpRequest = tmpRequest.set('s_sign', md5(`${signKey}_${st}`));
197
+ const res = await tmpRequest;
198
+ assert(res.body.success === true);
199
+
200
+ const resData = res.body.data;
201
+ assert(resData.length > 0);
202
+
203
+ for (let i = 0; i < resData.length; i++) {
204
+ const item = resData[i];
205
+ assert(item.model);
206
+ assert(item.model.key);
207
+ assert(item.model.name);
208
+ assert(item.project);
209
+ for (const projKey in item.project) {
210
+ assert(item.project[projKey].key);
211
+ assert(item.project[projKey].name);
212
+ }
213
+ }
214
+ });
215
+ });