@dmqweb/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 (81) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc +59 -0
  3. package/.vscode/settings.json +15 -0
  4. package/README.md +198 -0
  5. package/app/controller/base.js +41 -0
  6. package/app/controller/project.js +98 -0
  7. package/app/controller/view.js +24 -0
  8. package/app/extend/logger.js +43 -0
  9. package/app/middleware/api-params-verify.js +89 -0
  10. package/app/middleware/api-sign-verify.js +47 -0
  11. package/app/middleware/error-handler.js +41 -0
  12. package/app/middleware/project-handler.js +27 -0
  13. package/app/middleware.js +40 -0
  14. package/app/pages/asserts/custom.css +12 -0
  15. package/app/pages/boot.js +59 -0
  16. package/app/pages/common/curl.js +88 -0
  17. package/app/pages/common/util.js +3 -0
  18. package/app/pages/dashboard/complex-view/header-view/complex-view/sub-menu/sub-menu.vue +40 -0
  19. package/app/pages/dashboard/complex-view/header-view/header-view.vue +141 -0
  20. package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +43 -0
  21. package/app/pages/dashboard/complex-view/schema-view/complex-view/search-panel/search-panel.vue +39 -0
  22. package/app/pages/dashboard/complex-view/schema-view/complex-view/table-panel/table-panel.vue +146 -0
  23. package/app/pages/dashboard/complex-view/schema-view/components/component-config.js +24 -0
  24. package/app/pages/dashboard/complex-view/schema-view/components/create-form/create-form.vue +118 -0
  25. package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +177 -0
  26. package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +157 -0
  27. package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +150 -0
  28. package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +113 -0
  29. package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +35 -0
  30. package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +134 -0
  31. package/app/pages/dashboard/dashboard.vue +127 -0
  32. package/app/pages/dashboard/entry.dashboard.js +46 -0
  33. package/app/pages/store/index.js +5 -0
  34. package/app/pages/store/menu.js +61 -0
  35. package/app/pages/store/project.js +13 -0
  36. package/app/pages/widgets/header-container/asserts/avatar.png +0 -0
  37. package/app/pages/widgets/header-container/asserts/logo.png +0 -0
  38. package/app/pages/widgets/header-container/header-container.vue +144 -0
  39. package/app/pages/widgets/schema-form/complex-view/input/input.vue +165 -0
  40. package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +166 -0
  41. package/app/pages/widgets/schema-form/complex-view/select/select.vue +144 -0
  42. package/app/pages/widgets/schema-form/form-item.config.js +24 -0
  43. package/app/pages/widgets/schema-form/schema-form.vue +144 -0
  44. package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +57 -0
  45. package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +77 -0
  46. package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +51 -0
  47. package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +58 -0
  48. package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +138 -0
  49. package/app/pages/widgets/schema-search-bar/search-item-config.js +27 -0
  50. package/app/pages/widgets/schema-table/schema-table.vue +254 -0
  51. package/app/pages/widgets/sider-container/sider-container.vue +32 -0
  52. package/app/router/business.js +15 -0
  53. package/app/router/project.js +10 -0
  54. package/app/router/view.js +11 -0
  55. package/app/router-schema/business.js +82 -0
  56. package/app/router-schema/project.js +40 -0
  57. package/app/service/base.js +13 -0
  58. package/app/service/project.js +55 -0
  59. package/app/view/entry.tpl +27 -0
  60. package/app/webpack/config/blank.js +3 -0
  61. package/app/webpack/config/webpack.base.js +269 -0
  62. package/app/webpack/config/webpack.dev.js +61 -0
  63. package/app/webpack/config/webpack.prod.js +149 -0
  64. package/app/webpack/dev.js +58 -0
  65. package/app/webpack/prod.js +21 -0
  66. package/config/config.default.js +3 -0
  67. package/elpis-core/env.js +22 -0
  68. package/elpis-core/index.js +99 -0
  69. package/elpis-core/loader/config.js +51 -0
  70. package/elpis-core/loader/controller.js +75 -0
  71. package/elpis-core/loader/extend.js +54 -0
  72. package/elpis-core/loader/middleware.js +69 -0
  73. package/elpis-core/loader/router-schema.js +50 -0
  74. package/elpis-core/loader/router.js +52 -0
  75. package/elpis-core/loader/service.js +74 -0
  76. package/index.js +29 -0
  77. package/jsconfig.json +16 -0
  78. package/logs/applocation.log +3 -0
  79. package/model/index.js +119 -0
  80. package/package.json +93 -0
  81. package/test/controller/project.test.js +216 -0
@@ -0,0 +1,52 @@
1
+ const KoaRouter = require("koa-router")
2
+ const path = require("path")
3
+ const glob = require("glob")
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
+ // 创建路由实例
14
+ const router = new KoaRouter()
15
+
16
+ //获取所有 elpis 路由文件
17
+ const elpisRouterPath = path.resolve(__dirname, `..${sep}..${sep}app${sep}router`)
18
+ // 注册所有 elpis 路由
19
+ const elpisRouterFileList = glob.sync(path.resolve(elpisRouterPath, `.${sep}**${sep}*.js`))
20
+ elpisRouterFileList.forEach(file => {
21
+ // 加载路由模块,并传入app实例和router实例
22
+ require(path.resolve(file))(app,router)
23
+ })
24
+
25
+ //获取所有业务路由文件
26
+ const businessRouterPath = path.resolve(app.businessPath, `.${sep}router`)
27
+ // 注册所有业务路由
28
+ const businessRouterFileList = glob.sync(path.resolve(businessRouterPath, `.${sep}**${sep}*.js`))
29
+ businessRouterFileList.forEach(file => {
30
+ // 加载路由模块,并传入app实例和router实例
31
+ require(path.resolve(file))(app,router)
32
+ })
33
+
34
+ // 根路径重定向到首页
35
+ router.get('/', async (ctx,next) => {
36
+ ctx.status = 302;
37
+ ctx.redirect(app?.options?.homePage ?? '/view/project-list');
38
+ });
39
+
40
+ // 路由兜底 - 放在最后确保其他路由优先匹配(健壮性)
41
+ router.get('/view/*', async (ctx,next) => {
42
+ ctx.status = 302;
43
+ ctx.redirect(`${app?.options?.homePage ?? './'}`);
44
+ });
45
+
46
+ // 将路由实例保存到app对象上,方便调试
47
+ app.router = router
48
+
49
+ // 注册路由实例
50
+ app.use(router.routes())
51
+ app.use(router.allowedMethods())
52
+ }
@@ -0,0 +1,74 @@
1
+ const path = require('path')
2
+ const glob = require('glob')
3
+ const { sep } = path
4
+
5
+ /**
6
+ * service loader
7
+ * @param {object} app Koa 实例
8
+ *
9
+ * 加载所有 service 可通过 'app.service.${目录}.${文件名}' 访问'
10
+ *
11
+ * 例如
12
+ * app/service
13
+ * |
14
+ * | -- custom-moudle
15
+ * |
16
+ * | -- costom-service.js
17
+ * => app.service.customMoudle.costomService
18
+ */
19
+
20
+ module.exports = (app) => {
21
+ const service = {};
22
+
23
+ // 拼接eplis内部的service配置
24
+ const elpisServicePath = path.resolve(__dirname, `..${sep}..${sep}app${sep}service`);
25
+ // 使用glob模式匹配查找所有嵌套目录下的.js文件 (**表示任意层级子目录)
26
+ const elpisServiceFileList = glob.sync(path.resolve(elpisServicePath, `.${sep}**${sep}**.js`));
27
+ elpisServiceFileList.forEach(handleFile);
28
+
29
+ // 业务层app下配置的services
30
+ const businessServicePath = path.resolve(app.businessPath, `.${sep}service`);
31
+ // 使用glob模式匹配查找所有嵌套目录下的.js文件 (**表示任意层级子目录)
32
+ const businessServiceFileList = glob.sync(path.resolve(businessServicePath, `.${sep}**${sep}**.js`));
33
+
34
+ // 遍历所有文件目录,把内容加载到app.service 下
35
+ businessServiceFileList.forEach(handleFile);
36
+ function handleFile(file) {
37
+ // 提取文件名
38
+ let name = path.resolve(file);
39
+
40
+ // 截取路径 => app/service/custom-moudle/costom-service.js => custom-moudle/costom-service
41
+ name = name.substring(name.lastIndexOf(`service${sep}`) + `service${sep}`.length, name.lastIndexOf('.'))
42
+
43
+ // 把'-'统一成驼峰式,custom-moudle/costom-service => customMoudle/costomService
44
+ name = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());
45
+
46
+ // 挂载 service 到内容 app 对象中
47
+
48
+ // 创建临时引用指向服务层容器,用于构建嵌套结构
49
+ let tempService = service
50
+ // 将服务层路径按分隔符分割成数组,例如 'user/auth' => ['user'(目录), 'auth'(文件)]
51
+ const names = name.split(sep);
52
+ // 遍历路径数组,构建层次化的对象结构
53
+ for (let i = 0; i < names.length; i++){
54
+ // 判断是否为最后一层
55
+ if (i === names.length - 1){
56
+ // 创建服务层实例对象
57
+ // 1. 加载服务层模块并传入app实例,获取返回的服务层类
58
+ const serviceMoule = require(path.resolve(file))(app);
59
+ // 2. 实例化服务层类,创建具体的服务层对象实例
60
+ tempService[names[i]] = new serviceMoule();
61
+ }else{
62
+ // 处理服务层目录结构
63
+ // 如果当前层级的对象不存在,则创建空对象
64
+ if (!tempService[names[i]]){
65
+ tempService[names[i]] = {}
66
+ }
67
+ // 将临时引用指向下一层级,继续构建更深层的结构
68
+ tempService = tempService[names[i]]
69
+ }
70
+ }
71
+ };
72
+ // 挂载 middlewares 到内容 app 对象中
73
+ app.service = service;
74
+ }
package/index.js ADDED
@@ -0,0 +1,29 @@
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
+ Controller:{
10
+ Base:require("./app/controller/base.js")
11
+ },
12
+ Service:{
13
+ Base:require("./app/service/base.js")
14
+ },
15
+ // 启动elpis,传入配置文件
16
+ serverStart(options={}){
17
+ // 启动项目
18
+ const app = ElpisCore.start(options);
19
+ return app;
20
+ },
21
+ // 编译构建前端工程
22
+ frontendBuild(env){
23
+ if(env === "local"){
24
+ FEBuildDev();
25
+ }else if(env === "production"){
26
+ FEBuildProd();
27
+ }
28
+ }
29
+ }
package/jsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "ESNext",
4
+ "moduleResolution": "Bundler",
5
+ "target": "ES2022",
6
+ "jsx": "react-jsx",
7
+ "allowImportingTsExtensions": true,
8
+ "strictNullChecks": true,
9
+ "strictFunctionTypes": true
10
+ },
11
+ "exclude": [
12
+ "node_modules",
13
+ "**/node_modules/*",
14
+ "docs/dashboard-model.js"
15
+ ]
16
+ }
@@ -0,0 +1,3 @@
1
+ [2025-09-28T15:38:14.660] [INFO] default - [GET /api/project/list] signature: 10471f4ee0aabb01d1a3a69247742653
2
+ [2025-09-28T15:38:14.664] [INFO] default - [GET /api/project] signature: 10471f4ee0aabb01d1a3a69247742653
3
+ [2025-09-28T15:38:14.666] [INFO] default - [GET /api/project/model_list] signature: 10471f4ee0aabb01d1a3a69247742653
package/model/index.js ADDED
@@ -0,0 +1,119 @@
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, projectValue) => {
9
+ // 如果modelValue和projectValue都是数组
10
+ // 处理数组合并的特殊情况
11
+ if (Array.isArray(modelValue) && Array.isArray(projectValue)){
12
+ let result = []
13
+
14
+ // 因为 project 继承 model,所以需要处理修改和新增内容的情况
15
+ // project有的键值, model也有,则修改 (重载)
16
+ // project有的键值, model中没有,则添加 (扩展)
17
+ // model有的键值, project中没有,则保留 (继承)
18
+
19
+ // 处理修改和保留
20
+ for(let i = 0; i < modelValue.length; i++) {
21
+ // 获取menu数据
22
+ let modelItem = modelValue[i];
23
+ // 获取和modelitem里的key值相同的projectitem
24
+ const projectItem = projectValue.find(projectItem => projectItem.key === modelItem.key )
25
+
26
+ // 如果匹配到相同的projectItem,则进行递归继承,没有则保留
27
+ result.push(projectItem ? projectExtendModel(modelItem, projectItem) : modelItem)
28
+ }
29
+
30
+ // 处理新增
31
+ for(let i = 0; i < projectValue.length; i++) {
32
+ const projectItem = projectValue[i];
33
+ const modelItem = modelValue.find(modelItem => modelItem.key === projectItem.key)
34
+ if (!modelItem) {
35
+ result.push(projectItem)
36
+ }
37
+ }
38
+ return result
39
+ }
40
+ return undefined;
41
+ })
42
+ }
43
+
44
+ /**
45
+ * 解析 model 配置, 并返回组织且继承后的数据结构
46
+ * [{
47
+ * model: ${model};
48
+ * project:{
49
+ * proj1Key: ${proj1},
50
+ * proj2Key: ${proj2}
51
+ * }
52
+ * }, ...]
53
+ *
54
+ */
55
+
56
+ // 导出一个函数,接收app参数,用于构建model数据结构
57
+ module.exports = (app) => {
58
+ const modelList = [];
59
+
60
+ // 遍历当前文件夹,构造模型数据结构,挂载到 modelList 上
61
+ const modelPath = path.resolve(process.cwd(), `.${sep}model`);
62
+ const fileList = glob.sync(path.resolve(modelPath, `.${sep}**${sep}**.js`));
63
+ fileList.forEach(file => {
64
+ // 跳过当前文件(index.js)自身
65
+ if (file.indexOf('index.js') > -1) { return; }
66
+
67
+ // 区分 model 文件和 project 文件
68
+ const type = file.includes(`${sep}project${sep}`) || file.includes('/project/') || file.includes('\\project\\') ? 'project' : 'model';
69
+
70
+ // 处理project类型的文件
71
+ if (type === 'project') {
72
+ // 从文件路径中提取model名称,例如从 model/business/project/pdd.js 提取 business
73
+ const modelKey = file.match(/model[/\\]([^/\\]+)[/\\]project/)?.[1];
74
+ // 从文件路径中提取project名称,例如从 model/business/project/pdd.js 提取 pdd
75
+ const projectKey = file.match(/project[/\\]([^/\\]+)\.js/)?.[1];
76
+
77
+ // 在modelList中查找是否已存在该modelKey对应的modelItem
78
+ let modelItem = modelList.find(item => item.model?.key === modelKey)
79
+ // 如果没有找到对应的modelItem,则创建一个新的对象并添加到modelList中
80
+ if ( !modelItem ) {
81
+ modelItem = {};
82
+ modelList.push(modelItem);
83
+ }
84
+ // 如果modelItem还没有project属性,则初始化为空对象
85
+ if ( !modelItem.project ) {
86
+ modelItem.project = {};
87
+ }
88
+ // 加载project文件内容,并将其添加到modelItem.project对象中,以projectKey为键
89
+ modelItem.project[projectKey] = require(path.resolve(file));
90
+ // 为加载的project对象添加key属性,标识其名称
91
+ modelItem.project[projectKey].key = projectKey
92
+ modelItem.project[projectKey].modelKey = modelKey
93
+ }
94
+
95
+ // 处理model类型的文件
96
+ if (type === 'model') {
97
+ const modelKey = file.match(/[/\\]model[/\\](.*?)[/\\]model\.js/)?.[1]
98
+ let modelItem = modelList.find(item => item.model?.key === modelKey)
99
+ if ( !modelItem ) {
100
+ modelItem = {};
101
+ modelList.push(modelItem);
102
+ }
103
+ // 加载model文件内容,并将其赋值给modelItem.model
104
+ modelItem.model = require(path.resolve(file));
105
+ // 为加载的model对象添加key属性,标识其名称
106
+ modelItem.model.key = modelKey
107
+ }
108
+ })
109
+
110
+ // 数据进一步整理 project => 继承 model
111
+ modelList.forEach(item => {
112
+ const {model, project} = item;
113
+ for (const projectKey in project){
114
+ project[projectKey] = projectExtendModel(model, project[projectKey])
115
+ }
116
+ })
117
+
118
+ return modelList;
119
+ }
package/package.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "name": "@dmqweb/elpis",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://git.code.tencent.com/dumingqing/Elpis.git"
8
+ },
9
+ "license": "ISC",
10
+ "author": "dumingqing",
11
+ "main": "index.js",
12
+ "scripts": {
13
+ "test": "set _ENV=local&& mocha test/**/*.js",
14
+ "lint": "eslint --quiet --ext js,vue ."
15
+ },
16
+ "dependencies": {
17
+ "@babel/core": "^7.24.0",
18
+ "@babel/runtime": "^7.28.4",
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
+ "superagent": "^8.1.2",
46
+ "vue": "^3.3.4",
47
+ "vue-json-viewer": "^3.0.4",
48
+ "vue-router": "^4.2.4",
49
+ "vuex": "^4.1.0",
50
+ "@babel/plugin-transform-runtime": "^7.1.0",
51
+ "@babel/preset-env": "^7.4.5",
52
+ "babel-loader": "^8.0.4",
53
+ "clean-webpack-plugin": "^0.1.19",
54
+ "consoler": "^0.2.0",
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-eslint-parser": "^10.2.0",
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-parser": "^7.28.0",
79
+ "eslint": "^8.57.0",
80
+ "eslint-plugin-import": "^2.28.1",
81
+ "eslint-plugin-vue": "^9.33.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,216 @@
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 = 'elpis-sign-key';
7
+ const st = Date.now();
8
+
9
+ // 测试套件:测试 project 相关接口
10
+ describe('测试 project 相关接口', function () {
11
+ this.timeout(5000);
12
+
13
+ let request;
14
+ let modelList;
15
+ let projectList = [];
16
+
17
+ // 启动应用
18
+ it('启动' , async () => {
19
+ const app = await elpisCore.start();
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
+ request = supertest(app.listen());
28
+ })
29
+
30
+ // 测试获取项目配置接口不带projectKey
31
+ it('GET /api/project/list without projectKey', async () => {
32
+ let tmpRequest = request.get('/api/project');
33
+ tmpRequest = tmpRequest.set('s_t', st) // 设置时间戳
34
+ tmpRequest = tmpRequest.set('s_sign',md5(`${signKey}_${st}`)) // 设置签名
35
+ const res = await tmpRequest;
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') > -1 )
41
+ })
42
+ // 测试获取项目配置接口带错误projectKey
43
+ it('GET /api/project/list fail', async () => {
44
+ let tmpRequest = request.get('/api/project');
45
+ tmpRequest = tmpRequest.set('s_t', st) // 设置时间戳
46
+ tmpRequest = tmpRequest.set('s_sign',md5(`${signKey}_${st}`)) // 设置签名
47
+ tmpRequest = tmpRequest.query({
48
+ proj_key: 'xxxx'
49
+ })
50
+ const res = await tmpRequest;
51
+ assert(res.body.success === false)
52
+
53
+ const resBody = res.body;
54
+ assert(resBody.code === 5000);
55
+ assert(resBody.message === '项目获取异常' )
56
+ })
57
+ // 测试获取项目配置接口带正确projectKey
58
+ it('GET /api/project/list with projectKey', async () => {
59
+ for (let i = 0; i < projectList.length; i++) {
60
+ const projItem = projectList[i];
61
+ const { key:projKey } = projItem;
62
+ console.log(`GET /api/project/list with proj_key: ${projKey}` )
63
+
64
+ let tmpRequest = request.get('/api/project');
65
+ tmpRequest = tmpRequest.set('s_t', st) // 设置时间戳
66
+ tmpRequest = tmpRequest.set('s_sign',md5(`${signKey}_${st}`)) // 设置签名
67
+ tmpRequest = tmpRequest.query({
68
+ proj_key: projKey
69
+ })
70
+ const res = await tmpRequest;
71
+ assert(res.body.success === true)
72
+
73
+ const resData = res.body.data
74
+ assert(resData.proj_key === projKey)
75
+ assert(resData.modelKey)
76
+ assert(resData.name)
77
+ assert(resData.desc !== undefined)
78
+ assert(resData.homePage !== undefined)
79
+
80
+ const { menu } = resData
81
+ menu.forEach(menuItem => {
82
+ checkMenuItem(menuItem)
83
+ })
84
+ }
85
+ // 校验menu 菜单
86
+ function checkMenuItem(menuItem) {
87
+ console.log('----- GET /api/project/list with proj_key -menuKey: ', menuItem.key)
88
+ assert(menuItem.key)
89
+ assert(menuItem.name)
90
+ assert(menuItem.menuType)
91
+ if (menuItem.moduleType === '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
+ // 校验 module 菜单配置
103
+ function checkModule(menuItem) {
104
+ const { moduleType } = menuItem
105
+ assert(moduleType)
106
+
107
+ if (moduleType === 'sider'){
108
+ const { siderConfig } = menuItem
109
+ assert(siderConfig)
110
+ assert(siderConfig.menu)
111
+ siderConfig.menu.forEach(siderMenuItem => {
112
+ checkMenuItem(siderMenuItem)
113
+ })
114
+ }
115
+
116
+ if (moduleType === 'custom'){
117
+ const { customConfig } = menuItem
118
+ assert(customConfig)
119
+ assert(customConfig.path !== undefined)
120
+ }
121
+
122
+ if (moduleType === 'schema'){
123
+ const { schemaConfig } = menuItem
124
+ assert(schemaConfig)
125
+ assert(schemaConfig.api !== undefined)
126
+ assert(schemaConfig.schema)
127
+ }
128
+
129
+ if (moduleType === 'iframe'){
130
+ const { iframeConfig } = menuItem
131
+ assert(iframeConfig)
132
+ assert(iframeConfig.path !== undefined)
133
+ }
134
+
135
+ }
136
+ })
137
+
138
+
139
+ // 测试获取项目列表接口不带proejctKey
140
+ it('GET /api/project/list without proj_key', async () => {
141
+ let tmpRequest = request.get('/api/project/list');
142
+ tmpRequest = tmpRequest.set('s_t', st) // 设置时间戳
143
+ tmpRequest = tmpRequest.set('s_sign',md5(`${signKey}_${st}`)) // 设置签名
144
+ const res = await tmpRequest;
145
+ assert(res.body.success === true)
146
+
147
+ const resData = res.body.data;
148
+ assert(resData.length === projectList.length)
149
+ for (let i = 0; i < resData.length; i++) {
150
+ // name, desc, homePage, key, modelkey
151
+ const item = resData[i];
152
+ assert(item.name)
153
+ assert(item.key)
154
+ assert(item.desc !== undefined)
155
+ assert(item.homePage !== undefined)
156
+ assert(item.modelKey)
157
+ }
158
+ })
159
+ it('GET /api/project/list with proj_key', async () => {
160
+ // 随机获取一个项目key
161
+ const { key:projKey } = projectList[Math.floor(Math.random() * projectList.length)]
162
+ console.log(`GET /api/project/list with proj_key: ${projKey}` )
163
+ const { modelKey } = projectList.find(item => item.key === projKey)
164
+
165
+ let tmpRequest = request.get('/api/project/list');
166
+ tmpRequest = tmpRequest.set('s_t', st) // 设置时间戳
167
+ tmpRequest = tmpRequest.set('s_sign',md5(`${signKey}_${st}`)) // 设置签名
168
+ tmpRequest = tmpRequest.query({
169
+ proj_key: projKey
170
+ })
171
+ const res = await tmpRequest;
172
+ assert(res.body.success === true)
173
+
174
+ const resData = res.body.data
175
+ assert(projectList.filter(item => item.modelKey === modelKey).length === resData.length)
176
+ for (let i = 0; i < resData.length; i++) {
177
+ // name, desc, homePage, key, modelkey
178
+ const item = resData[i];
179
+ assert(item.name)
180
+ assert(item.key)
181
+ assert(item.desc !== undefined)
182
+ assert(item.homePage !== undefined)
183
+ assert(item.modelKey)
184
+ }
185
+ })
186
+
187
+ // 测试获取项目模型列表接口
188
+ it('GET /api/project/model_list', async () => {
189
+ // 构造带签名验证的请求
190
+ let tmpRequest = request.get('/api/project/model_list');
191
+ tmpRequest = tmpRequest.set('s_t', st) // 设置时间戳
192
+ tmpRequest = tmpRequest.set('s_sign',md5(`${signKey}_${st}`)) // 设置签名
193
+ const res = await tmpRequest;
194
+
195
+ // console.log(JSON.stringify(res.body))
196
+ // 验证响应结果
197
+ assert(res.body.success === true)
198
+ const resData = res.body.data;
199
+ assert(resData.length > 0)
200
+
201
+ // 验证数据结构完整性
202
+ for (let i = 0; i < resData.length; i++) {
203
+ const item = resData[i];
204
+ assert(item.model)
205
+ assert(item.model.name)
206
+ assert(item.model.key)
207
+ assert(item.project)
208
+ for (const projectKey in item.project) {
209
+ assert(item.project[projectKey].name)
210
+ assert(item.project[projectKey].key)
211
+ }
212
+ }
213
+ })
214
+
215
+
216
+ });