@codertqy/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 (83) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc +55 -0
  3. package/README.md +218 -0
  4. package/app/controller/base.js +40 -0
  5. package/app/controller/project.js +81 -0
  6. package/app/controller/view.js +22 -0
  7. package/app/extend/logger.js +43 -0
  8. package/app/middleware/api-params-verify.js +73 -0
  9. package/app/middleware/api-sign-verify.js +49 -0
  10. package/app/middleware/error-handler.js +31 -0
  11. package/app/middleware/project-handler.js +26 -0
  12. package/app/middleware.js +44 -0
  13. package/app/pages/assets/custom.css +14 -0
  14. package/app/pages/boot.js +56 -0
  15. package/app/pages/common/curl.js +84 -0
  16. package/app/pages/common/index.css +3 -0
  17. package/app/pages/common/utils.js +1 -0
  18. package/app/pages/dashboard/complex-view/header-view/complex-view/sub-menu/sub-menu.vue +19 -0
  19. package/app/pages/dashboard/complex-view/header-view/header-view.vue +126 -0
  20. package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +45 -0
  21. package/app/pages/dashboard/complex-view/schema-view/complex-view/search-panel/search-panel.vue +35 -0
  22. package/app/pages/dashboard/complex-view/schema-view/complex-view/table-panel/table-panel.vue +120 -0
  23. package/app/pages/dashboard/complex-view/schema-view/components/component-config.js +23 -0
  24. package/app/pages/dashboard/complex-view/schema-view/components/create-form/create-form.vue +87 -0
  25. package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +100 -0
  26. package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +122 -0
  27. package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +161 -0
  28. package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +95 -0
  29. package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +19 -0
  30. package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +135 -0
  31. package/app/pages/dashboard/dashboard.vue +86 -0
  32. package/app/pages/dashboard/entry.dashboard.js +48 -0
  33. package/app/pages/store/index.js +3 -0
  34. package/app/pages/store/menu.js +68 -0
  35. package/app/pages/store/project.js +12 -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 +107 -0
  39. package/app/pages/widgets/schema-form/complex-view/input/input.vue +138 -0
  40. package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +140 -0
  41. package/app/pages/widgets/schema-form/complex-view/select/select.vue +122 -0
  42. package/app/pages/widgets/schema-form/form-item-config.js +20 -0
  43. package/app/pages/widgets/schema-form/schema-form.vue +135 -0
  44. package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +51 -0
  45. package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +63 -0
  46. package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +41 -0
  47. package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +49 -0
  48. package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +126 -0
  49. package/app/pages/widgets/schema-search-bar/search-item-config.js +22 -0
  50. package/app/pages/widgets/schema-table/schema-table.vue +259 -0
  51. package/app/pages/widgets/sider-container/sider-container.vue +27 -0
  52. package/app/public/output/entry.page1.tpl +40 -0
  53. package/app/public/output/entry.page2.tpl +11 -0
  54. package/app/public/static/logo.png +0 -0
  55. package/app/public/static/normalize.css +239 -0
  56. package/app/router/project.js +22 -0
  57. package/app/router/view.js +17 -0
  58. package/app/router-schema/project.js +34 -0
  59. package/app/service/base.js +15 -0
  60. package/app/service/project.js +59 -0
  61. package/app/view/entry.tpl +25 -0
  62. package/app/webpack/config/webpack.base.js +280 -0
  63. package/app/webpack/config/webpack.dev.js +57 -0
  64. package/app/webpack/config/webpack.prod.js +127 -0
  65. package/app/webpack/dev.js +63 -0
  66. package/app/webpack/libs/blank.js +1 -0
  67. package/app/webpack/prod.js +22 -0
  68. package/config/config.beta.js +1 -0
  69. package/config/config.default.js +3 -0
  70. package/config/config.prod.js +1 -0
  71. package/elpis-core/env.js +27 -0
  72. package/elpis-core/index.js +98 -0
  73. package/elpis-core/loader/config.js +58 -0
  74. package/elpis-core/loader/controller.js +93 -0
  75. package/elpis-core/loader/extend.js +63 -0
  76. package/elpis-core/loader/middleware.js +84 -0
  77. package/elpis-core/loader/router-schema.js +56 -0
  78. package/elpis-core/loader/router.js +50 -0
  79. package/elpis-core/loader/service.js +85 -0
  80. package/index.js +38 -0
  81. package/model/index.js +129 -0
  82. package/package.json +92 -0
  83. package/test/controller/project.test.js +214 -0
package/model/index.js ADDED
@@ -0,0 +1,129 @@
1
+ const _ = require("lodash");
2
+ const glob = require("glob");
3
+ const path = require("path");
4
+ const { sep } = path;
5
+
6
+ /**
7
+ * 项目 project 继承 model 方法
8
+ * @param {} model 基类的数据
9
+ * @param {*} project 每一个项目类的数据
10
+ * @returns
11
+ */
12
+ const porjectExtendModel = (model, project) => {
13
+ // 回调函数的参数是与前者对应
14
+ return _.mergeWith({}, model, project, (modelValue, projValue) => {
15
+ // 处理数组数据合并的特殊情况,不是数组,使用lodash默认合并
16
+ // 一般对应着 结构中的 menu 属性
17
+ if (_.isArray(modelValue) && _.isArray(projValue)) {
18
+ let result = [];
19
+ // 因为 project 继承 model 所以需要处理修改和新增内容的情况
20
+ // project有的,model也有 ---》修改(重载)
21
+ // project有的,model没有 ---》新增(扩展)
22
+ // model有的,project也有 ---》保留(继承)
23
+
24
+ // 处理 修改/保留 判断项目类中的是否有基类的。有就进行覆盖修改/保留
25
+ for (let i = 0; i < modelValue.length; i++) {
26
+ let modelItem = modelValue[i];
27
+ const projItem = projValue.find(
28
+ (projItem) => projItem.key === modelItem.key,
29
+ );
30
+ // 都有,则递归调用 porjectExtendModel 方法 覆盖修改
31
+ result.push(
32
+ projItem ? porjectExtendModel(modelItem, projItem) : modelItem,
33
+ );
34
+ }
35
+ // 处理新增,判断基类中是否有项目类中的,没有则直接新增、有则不做任何操作
36
+ for (let i = 0; i < projValue.length; i++) {
37
+ let projItem = projValue[i];
38
+ const modelItem = modelValue.find(
39
+ (modelItem) => projItem.key === modelItem.key,
40
+ );
41
+ // 找不到直接新增
42
+ if (!modelItem) {
43
+ result.push(projItem);
44
+ }
45
+ }
46
+ // 拿到合并后的效果
47
+ return result;
48
+ }
49
+ });
50
+ };
51
+
52
+
53
+ /**
54
+ * 解析 model 配置,并返回组织且继承后的数据结构
55
+ * [{
56
+ * modelKey: ${model},
57
+ * project:{
58
+ * proj1Key:${proj1},
59
+ * proj2Key:${proj2},
60
+ * }
61
+ * },...]
62
+ */
63
+
64
+ module.exports = (app) => {
65
+ const modelList = [];
66
+ // 遍历当前文件夹,构造模型数据结构,挂载到 modelList 上
67
+ const modelPath = path.resolve(process.cwd(), `.${sep}model`);
68
+ const fileList = glob.sync(path.resolve(modelPath, `.${sep}**${sep}**.js`));
69
+
70
+ // 遍历每一个文件
71
+ fileList.forEach((file) => {
72
+ if (file.indexOf("index.js") > -1) {
73
+ return;
74
+ }
75
+ // 区分配置类型是 (model / project)
76
+
77
+ // 具体的模型文件
78
+ const type = file.indexOf(`/project/`) > -1 ? "project" : "model";
79
+ if (type === "project") {
80
+ // 拿到基类key
81
+ const modelKey = file.match(/\/model\/(.*?)\/project/)?.[1];
82
+ // 具体的 项目类key
83
+ const projKey = file.match(/\/project\/(.*?)\.js/)?.[1];
84
+
85
+ const modelItem = modelList.find((item) => item.model?.key === modelKey);
86
+ if (!modelItem) {
87
+ // 初始化 model 数据结构
88
+ modelItem = {};
89
+ modelList.push(modelItem);
90
+ }
91
+ if (!modelItem.project) {
92
+ // 初始化 project 数据结构
93
+ modelItem.project = {};
94
+ }
95
+ // 将项目类的属性与值全部挂载到project上(以projKey作为键),并且注入projKey
96
+ modelItem.project[projKey] = require(path.resolve(file));
97
+ modelItem.project[projKey].key = projKey;
98
+ modelItem.project[projKey].modelKey = modelKey; //注入 modelKey 告诉所属哪一个
99
+ }
100
+ // 基类模型,重新构造数据格式
101
+ if (type === "model") {
102
+ // 拿到当前基类的类型:buiness / course
103
+ const modelKey = file.match(/\/model\/(.*?)\/model\.js/)?.[1];
104
+ let modelItem = modelList.find((item) => item.model?.key === modelKey);
105
+ if (!modelItem) {
106
+ // 初始化 model 数据结构
107
+ modelItem = {};
108
+ modelList.push(modelItem);
109
+ }
110
+ // 将基类中的属性与值全部挂载到model上,并且注入modelKey
111
+ modelItem.model = require(path.resolve(file));
112
+ modelItem.model.key = modelKey;
113
+ }
114
+ });
115
+
116
+ // 数据进一步整理 project ==> 继承 model
117
+ modelList.forEach((item) => {
118
+ // 解析基类的数据,以及项目类的数据
119
+ const { model, project } = item;
120
+
121
+ // 遍历项目类数据,继承基类数据
122
+ for (const key in project) {
123
+ // 拿到的key属性就是 taobao、pdd之类的
124
+ // 值是数组,则进行遍历操作,如果是基本类型,则直接进行lodash中的覆盖合并
125
+ project[key] = porjectExtendModel(model, project[key]);
126
+ }
127
+ });
128
+ return modelList;
129
+ };
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@codertqy/elpis",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "lint": "eslint --quiet --ext js,vue .",
8
+ "test": "set _ENV=local && mocha ./test/**/*.js"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://git.code.tencent.com/codertqy/elpis.git"
13
+ },
14
+ "author": "codertqy",
15
+ "license": "ISC",
16
+ "dependencies": {
17
+ "@babel/core": "^7.24.0",
18
+ "@element-plus/icons-vue": "^2.3.2",
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": "^10.1.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
+ "html-webpack-inject-attributes-plugin": "^1.0.1",
61
+ "html-webpack-plugin": "^5.5.3",
62
+ "less-loader": "^11.1.3",
63
+ "mini-css-extract-plugin": "^2.7.6",
64
+ "style-loader": "^0.14.1",
65
+ "terser-webpack-plugin": "^5.3.16",
66
+ "thread-loader": "^4.0.4",
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,214 @@
1
+ // 断言库
2
+ const assert = require("assert");
3
+ // 单元测试 模拟请求测试的服务
4
+ const supertest = require("supertest");
5
+ const md5 = require("md5");
6
+ const elpisCore = require("../../elpis-core/index");
7
+
8
+ const signKey = "bgkjibal2kbhkc4vbakjl3sdvhja";
9
+ const st = Date.now();
10
+
11
+ describe("测试 project 相关接口", function () {
12
+ this.timeout(60000);
13
+
14
+ let modelList;
15
+ const projectList = [];
16
+ let request;
17
+ it("启动服务", async () => {
18
+ const app = await elpisCore.start();
19
+
20
+ // 进行对比的数据
21
+ modelList = require("../../model/index")(app);
22
+ modelList.forEach((modelItem) => {
23
+ const { project } = modelItem;
24
+ for (const projKey in project) {
25
+ // 将pdd,taobao这种添加到projectList
26
+ projectList.push(project[projKey]);
27
+ }
28
+ });
29
+
30
+ request = supertest(app.listen());
31
+ });
32
+
33
+ // 测试 /api/project 接口
34
+ it("GET /api/project without proj_key", async () => {
35
+ let tmpRequest = request.get("/api/project");
36
+ tmpRequest = tmpRequest.set("s_sign", md5(`${signKey}_${st}`));
37
+ tmpRequest = tmpRequest.set("s_t", st);
38
+ const res = await tmpRequest;
39
+ assert(res.body.success === false);
40
+
41
+ const resBody = res.body;
42
+ assert(resBody.code === 442);
43
+ assert(resBody.message.indexOf("request validate fail") > -1);
44
+ });
45
+
46
+ it("GET /api/project fail", async () => {
47
+ let tmpRequest = request.get("/api/project");
48
+ tmpRequest = tmpRequest.set("s_sign", md5(`${signKey}_${st}`));
49
+ tmpRequest = tmpRequest.set("s_t", st);
50
+ tmpRequest = tmpRequest.query({
51
+ proj_key: "xxxxxxxxxxxxxxx",
52
+ });
53
+ const res = await tmpRequest;
54
+ console.log(res.body);
55
+ assert(res.body.success === false);
56
+ const resBody = res.body;
57
+ assert(resBody.code === 50000);
58
+ assert(resBody.message.indexOf("项目异常") > -1);
59
+ });
60
+ it("GET /api/project with proj_key", async () => {
61
+ // 校验 menu 菜单
62
+ const checkMenuItem = (menuItem) => {
63
+ console.log(`--------- GET /api/project with proj_key - menuKey: ${projKey}`);
64
+ assert(menuItem.key);
65
+ assert(menuItem.name);
66
+ assert(menuItem.menuType);
67
+
68
+ if (menuItem.menuType === "group") {
69
+ assert(menuItem.subMenu !== undefined);
70
+ menuItem.subMenu.forEach((subMenuItem) => {
71
+ // 递归调用 subMenu 里面的每一项 都是使用 checkMenuItem 方法
72
+ checkMenuItem(subMenuItem);
73
+ });
74
+ } else if (menuItem.menuType === "module") {
75
+ checkModule(menuItem);
76
+ }
77
+ };
78
+
79
+ // 校验 module 菜单配置
80
+ const checkModule = (menuItem) => {
81
+ const { moduleType } = menuItem;
82
+ assert(moduleType);
83
+ if (moduleType === "sider") {
84
+ const { siderConfig } = menuItem;
85
+ assert(siderConfig);
86
+ assert(siderConfig.menu);
87
+ siderConfig.menu.forEach((siderMenuItem) => {
88
+ // 递归调用 menu 里面的每一项 都是使用 checkMenuItem 方法
89
+ checkMenuItem(siderMenuItem);
90
+ });
91
+ }
92
+ if (moduleType === "iframe") {
93
+ const { iframeConfig } = menuItem;
94
+ assert(iframeConfig);
95
+ assert(iframeConfig.path !== undefined);
96
+ }
97
+ if (moduleType === "custom") {
98
+ const { customConfig } = menuItem;
99
+ assert(customConfig);
100
+ assert(customConfig.path !== undefined);
101
+ }
102
+ if (moduleType === "schema") {
103
+ const { schemaConfig } = menuItem;
104
+ assert(schemaConfig);
105
+ assert(schemaConfig.api !== undefined);
106
+ assert(schemaConfig.schema);
107
+ }
108
+ };
109
+ // 循环获取 projKey 验证每一个
110
+ for (let i = 0; i < projectList.length; i++) {
111
+ const { key: projKey } = projectList[i];
112
+ console.log(`GET /api/project with proj_key: ${projKey}`);
113
+ let tmpRequest = request.get("/api/project");
114
+ tmpRequest = tmpRequest.set("s_sign", md5(`${signKey}_${st}`));
115
+ tmpRequest = tmpRequest.set("s_t", st);
116
+ tmpRequest = tmpRequest.query({
117
+ proj_key: projKey,
118
+ });
119
+ const res = await tmpRequest;
120
+ assert(res.body.success === true);
121
+
122
+ const resData = res.body.data;
123
+ assert(resData.key === projKey);
124
+ assert(resData.name);
125
+ assert(resData.modelKey);
126
+ assert(resData.desc !== undefined);
127
+ assert(resData.homePage !== undefined);
128
+
129
+ const { menu } = resData;
130
+ menu.forEach((menuItem) => {
131
+ checkMenuItem(menuItem);
132
+ });
133
+ }
134
+ });
135
+
136
+ // 测试 /api/project/list 接口
137
+ it("GET /api/project/list without proj_key", async () => {
138
+ let tmpRequest = request.get("/api/project/list");
139
+ tmpRequest = tmpRequest.set("s_sign", md5(`${signKey}_${st}`));
140
+ tmpRequest = tmpRequest.set("s_t", st);
141
+ const res = await tmpRequest;
142
+ // 针对数据进行断言
143
+ assert(res.body.success === true);
144
+
145
+ const resData = res.body.data;
146
+ assert(resData.length === projectList.length);
147
+ for (let i = 0; i < resData.length; i++) {
148
+ const item = resData[i];
149
+ assert(item.key);
150
+ assert(item.name);
151
+ assert(item.desc !== undefined);
152
+ assert(item.modelKey);
153
+ assert(item.homePage !== undefined);
154
+ }
155
+ });
156
+
157
+ it("GET /api/project/list with proj_key", async () => {
158
+ // 取出project对象中的每一个对象中的唯一的key属性
159
+ const { key: projKey } =
160
+ projectList[Math.floor(Math.random() * projectList.length)];
161
+ // 查找当前的 projKey 所属已存在的那一个里面
162
+ const { modelKey } = projectList.find((item) => item.key === projKey);
163
+ let tmpRequest = request.get("/api/project/list");
164
+ tmpRequest = tmpRequest.set("s_sign", md5(`${signKey}_${st}`));
165
+ tmpRequest = tmpRequest.set("s_t", st);
166
+ // 携带请求参数
167
+ tmpRequest = tmpRequest.query({
168
+ proj_key: projKey,
169
+ });
170
+ const res = await tmpRequest;
171
+ assert(res.body.success === true);
172
+
173
+ const resData = res.body.data;
174
+ assert(
175
+ projectList.filter(
176
+ (item) => (item.modelKey === modelKey.length) === resData.length,
177
+ ),
178
+ );
179
+ for (let i = 0; i < resData.length; i++) {
180
+ const item = resData[i];
181
+ assert(item.key);
182
+ assert(item.name);
183
+ assert(item.desc !== undefined);
184
+ assert(item.modelKey);
185
+ assert(item.homePage !== undefined);
186
+ }
187
+ });
188
+
189
+ // 测试 /api/project/model_list 接口
190
+ it("GET /api/project/model_list", async () => {
191
+ let tmpRequest = request.get("/api/project/model_list");
192
+ tmpRequest = tmpRequest.set("s_sign", md5(`${signKey}_${st}`));
193
+ tmpRequest = tmpRequest.set("s_t", st);
194
+ const res = await tmpRequest;
195
+
196
+ // 针对数据进行断言
197
+ assert(res.body.success === true);
198
+
199
+ const resData = res.body.data;
200
+ assert(resData.length > 0);
201
+
202
+ for (let i = 0; i < resData.length; i++) {
203
+ const item = resData[i];
204
+ assert(item.model);
205
+ assert(item.model.key);
206
+ assert(item.model.name);
207
+ assert(item.project);
208
+ for (const projKey in item.project) {
209
+ assert(item.project[projKey].key);
210
+ assert(item.project[projKey].name);
211
+ }
212
+ }
213
+ });
214
+ });