@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,94 @@
1
+ <template>
2
+ <el-config-provider :locale="zhCn">
3
+ <header-view
4
+ :proj-name="projName"
5
+ @menu-select="onMenuSelect"
6
+ >
7
+ <template #main-content>
8
+ <router-view />
9
+ </template>
10
+ </header-view>
11
+ </el-config-provider>
12
+ </template>
13
+ <script setup>
14
+ import{ ref, onMounted } from 'vue';
15
+ import { useRouter, useRoute } from 'vue-router';
16
+ import zhCn from 'element-plus/es/locale/lang/zh-cn';
17
+ import HeaderView from './complex-view/header-view/header-view.vue';
18
+ import $curl from '$elpisCommon/curl.js'
19
+ import { useMenuStore } from '$elpisStore/menu.js'
20
+ import { useProjectStore } from '$elpisStore/project.js'
21
+
22
+ const router = useRouter();
23
+ const route = useRoute();
24
+ const projectStore = useProjectStore();
25
+ const menuStore = useMenuStore();
26
+
27
+ onMounted(() => {
28
+ getProjectList();
29
+ getProjectConfig();
30
+ })
31
+
32
+ const projName = ref('');
33
+
34
+ // 请求 /api/project/list 接口,并缓存到 project-store 中
35
+ async function getProjectList() {
36
+ const res = await $curl({
37
+ method: 'get',
38
+ url: '/api/project/list',
39
+ query: {
40
+ // TODO: 这里需要动态获取当前项目的key
41
+ proj_key: route.query.proj_key
42
+ }
43
+ })
44
+
45
+ if (!res || !res.success || !res.data) return;
46
+
47
+ projectStore.setProjectList(res.data);
48
+ }
49
+
50
+ // 请求 /api/project 接口,并缓存到 menu-store 中
51
+ async function getProjectConfig() {
52
+ const res = await $curl({
53
+ method: 'get',
54
+ url: '/api/project',
55
+ query: {
56
+ // TODO: 这里需要动态获取当前项目的key
57
+ proj_key: route.query.proj_key
58
+ }
59
+ })
60
+ if (!res || !res.success || !res.data) return;
61
+
62
+ const { name, menu } = res.data;
63
+ projName.value = name;
64
+ menuStore.setMenuList(menu);
65
+ }
66
+
67
+ // 点击菜单回调
68
+ const onMenuSelect = (menuItem) => {
69
+ const { moduleType, key, customConfig } = menuItem;
70
+
71
+ // 如果是当前页面,不处理
72
+ if ( key === route.query.key) return;
73
+
74
+ const pathMap = {
75
+ iframe: '/iframe',
76
+ sider: '/sider',
77
+ schema: '/schema',
78
+ custom: customConfig?.path
79
+ }
80
+
81
+ router.push({
82
+ path: `/view/dashboard${pathMap[moduleType]}`,
83
+ query: {
84
+ key,
85
+ proj_key: route.query.proj_key
86
+ }
87
+ })
88
+ }
89
+ </script>
90
+ <style lang="less" scoped>
91
+ :deep(.el-main) {
92
+ padding: 0;
93
+ }
94
+ </style>
@@ -0,0 +1,43 @@
1
+ import boot from '$elpisPages/boot.js';
2
+ import Dashboard from './dashboard.vue';
3
+ import businessDashboardRouterConfig from '$businessDashboardRouterConfig';
4
+
5
+ const routes = [];
6
+
7
+ // 头部菜单路由
8
+ routes.push({
9
+ path: '/view/dashboard/iframe',
10
+ component: () => import('./complex-view/iframe-view/iframe-view.vue')
11
+ });
12
+ routes.push({
13
+ path: '/view/dashboard/schema',
14
+ component: () => import('./complex-view/schema-view/schema-view.vue')
15
+ });
16
+
17
+
18
+ const siderRoutes = [{
19
+ path: 'iframe',
20
+ component: () => import('./complex-view/iframe-view/iframe-view.vue')
21
+ }, {
22
+ path: 'schema',
23
+ component: () => import('./complex-view/schema-view/schema-view.vue')
24
+ }]
25
+ // 侧边栏菜单路由
26
+ routes.push({
27
+ path: '/view/dashboard/sider',
28
+ component: () => import('./complex-view/sider-view/sider-view.vue'),
29
+ children: siderRoutes
30
+ });
31
+
32
+ // 业务拓展路由
33
+ if (typeof businessDashboardRouterConfig === 'function') {
34
+ businessDashboardRouterConfig({ routes, siderRoutes });
35
+ }
36
+
37
+ // 侧边栏兜底策略
38
+ routes.push({
39
+ path: '/view/dashboard/sider/:chapters+',
40
+ component: () => import('./complex-view/sider-view/sider-view.vue')
41
+ });
42
+
43
+ boot(Dashboard, { routes });
@@ -0,0 +1,3 @@
1
+ import { createPinia } from "pinia";
2
+ const pinia = createPinia();
3
+ export default pinia;
@@ -0,0 +1,59 @@
1
+ import { defineStore } from 'pinia';
2
+ import { ref } from 'vue';
3
+
4
+ export const useMenuStore = defineStore('menu', () => {
5
+ // 菜单列表
6
+ const menuList = ref([]);
7
+
8
+ // 设置菜单列表
9
+ const setMenuList = (list) => {
10
+ menuList.value = list;
11
+ }
12
+
13
+ /**
14
+ * 找出菜单目录
15
+ * @param key 搜索字段
16
+ * @param value 搜索值
17
+ * @param mlist 要搜索的菜单列表
18
+ */
19
+ const findMenuItem = function( { key, value }, mList = menuList.value) {
20
+ for (let i = 0; i < mList.length; ++i) {
21
+ const menuItem = mList[i];
22
+ if (!menuItem) continue;
23
+
24
+ const { menuType, moduleType } = menuItem;
25
+
26
+ if (menuItem[key] === value) return menuItem;
27
+
28
+ if (menuType === 'group' && menuItem.subMenu) {
29
+ const mItem = findMenuItem({ key, value }, menuItem.subMenu);
30
+ if (mItem) return mItem;
31
+ }
32
+
33
+ if (moduleType === 'sider' && menuItem.siderConfig?.menu) {
34
+ const mItem = findMenuItem({ key, value }, menuItem.siderConfig.menu);
35
+ if (mItem) return mItem;
36
+ }
37
+ }
38
+ }
39
+
40
+ /**
41
+ * 找出第一个菜单目录
42
+ * @param mList 菜单列表
43
+ */
44
+ const findFirstMenuItem = function(mList = menuList.value) {
45
+ if (!mList || !mList[0]) return;
46
+ let firstMenuItem = mList[0];
47
+ if (firstMenuItem.subMenu) {
48
+ firstMenuItem = findFirstMenuItem(firstMenuItem.subMenu);
49
+ };
50
+ return firstMenuItem;
51
+ }
52
+
53
+ return {
54
+ menuList,
55
+ setMenuList,
56
+ findMenuItem,
57
+ findFirstMenuItem
58
+ }
59
+ })
@@ -0,0 +1,17 @@
1
+ import { defineStore } from 'pinia';
2
+ import { ref } from 'vue';
3
+
4
+ export const useProjectStore = defineStore('project', () => {
5
+ // 项目列表
6
+ const projectList = ref([]);
7
+
8
+ // 设置项目列表
9
+ const setProjectList = (list) => {
10
+ projectList.value = list;
11
+ }
12
+
13
+ return {
14
+ projectList,
15
+ setProjectList
16
+ }
17
+ })
@@ -0,0 +1,124 @@
1
+ <template>
2
+ <el-container class="header-container">
3
+ <el-header class="header">
4
+ <el-row
5
+ type="flex"
6
+ align="middle"
7
+ class="header-row"
8
+ >
9
+ <!-- 左上方标题栏 -->
10
+ <el-row
11
+ type="flex"
12
+ align="middle"
13
+ class="title-panel"
14
+ >
15
+ <img
16
+ src="./assets/logo.png"
17
+ alt=""
18
+ class="logo"
19
+ >
20
+ <el-row class="text">
21
+ {{ title }}
22
+ </el-row>
23
+ </el-row>
24
+ <!-- 插槽:菜单区域 -->
25
+ <slot name="menu-panel" />
26
+ <!-- 右上方操作区域 -->
27
+ <el-row
28
+ type="flex"
29
+ align="middle"
30
+ justify="end"
31
+ class="setting-panel"
32
+ >
33
+ <slot name="setting-content" />
34
+ <img
35
+ src="./assets/avatar.png"
36
+ alt=""
37
+ class="avatar"
38
+ >
39
+ <el-dropdown @command="handleUserCommand">
40
+ <span class="user-name">
41
+ {{ userName }}<i class="el-icon-arrow-down el-icon--right" />
42
+ </span>
43
+ <template #dropdown>
44
+ <el-dropdown-item command="logout">
45
+ 退出
46
+ </el-dropdown-item>
47
+ </template>
48
+ </el-dropdown>
49
+ </el-row>
50
+ </el-row>
51
+ </el-header>
52
+ <el-main class="main-caontainer">
53
+ <!-- 插槽:核心内容填充区域 -->
54
+ <slot name="main-content" />
55
+ </el-main>
56
+ </el-container>
57
+ </template>
58
+ <script setup>
59
+ import { ref } from 'vue';
60
+
61
+ defineProps({
62
+ title: {
63
+ type: String,
64
+ default: ''
65
+ }
66
+ });
67
+
68
+ const userName = ref('admin');
69
+ const handleUserCommand = (command) => {
70
+ console.log(command);
71
+ }
72
+ </script>
73
+ <style lang="less" scoped>
74
+ .header-container {
75
+ height: 100%;
76
+ min-width: 1000px;
77
+ overflow: hidden;
78
+ .header{
79
+ box-sizing: content-box;
80
+ max-height: 120px;
81
+ border-bottom: 1px solid #e8e8e8;
82
+ .header-row {
83
+ height: 60px;
84
+ padding: 0px 20px;
85
+ .title-panel {
86
+ width: 180px;
87
+ min-width: 180px;
88
+ .logo {
89
+ margin-right: 10px;
90
+ width: 25px;
91
+ height: 25px;
92
+ border-radius: 50%;
93
+ }
94
+ .text {
95
+ font-size: 15px;
96
+ font-weight: 500;
97
+ }
98
+ }
99
+ .setting-panel {
100
+ margin-left: auto;
101
+ min-width: 180px;
102
+ .avatar {
103
+ margin-right: 12px;
104
+ width: 30px;
105
+ height: 30px;
106
+ border-radius: 50%;
107
+ }
108
+ .user-name {
109
+ font-size: 16px;
110
+ font-weight: 500;
111
+ cursor: pointer;
112
+ height: 60px;
113
+ line-height: 60px;
114
+ outline: none;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ .main-container {}
120
+ }
121
+ :deep(.el-header) {
122
+ padding: 0px;
123
+ }
124
+ </style>
@@ -0,0 +1,143 @@
1
+ <template>
2
+ <el-row
3
+ type="flex"
4
+ align="middle"
5
+ class="form-item"
6
+ >
7
+ <!-- label -->
8
+ <el-row
9
+ justify="end"
10
+ class="item-label"
11
+ >
12
+ <el-row
13
+ v-if="schema.option?.required"
14
+ type="flex"
15
+ class="required"
16
+ >
17
+ *
18
+ </el-row>
19
+ {{ schema.label }}
20
+ </el-row>
21
+ <!-- value -->
22
+ <el-row class="item-value">
23
+ <el-input
24
+ v-model="dtoValue"
25
+ v-bind="schema.option"
26
+ class="component"
27
+ :class="{'valid-border': validTips}"
28
+ :placeholder="placeholder"
29
+ @input="validate"
30
+ @focus="onFocus"
31
+ @blur="onBlur"
32
+ />
33
+ </el-row>
34
+ <el-row
35
+ v-if="validTips"
36
+ class="valid-tips"
37
+ >
38
+ {{ validTips }}
39
+ </el-row>
40
+ </el-row>
41
+ </template>
42
+ <script setup>
43
+ import { ref, onMounted, watch, inject, toRefs } from 'vue';
44
+
45
+ const ajv = inject('ajv');
46
+
47
+ const props = defineProps({
48
+ schemaKey: String,
49
+ schema: Object,
50
+ model: String
51
+ });
52
+ const { schemaKey, schema } = props;
53
+ const { model } = toRefs(props);
54
+
55
+ const name = ref('input');
56
+
57
+ const dtoValue = ref();
58
+ const placeholder = ref('');
59
+ const validTips = ref(null);
60
+ const initData = () => {
61
+ validTips.value = null;
62
+ dtoValue.value = model.value ?? schema.option?.default;
63
+
64
+ const { minLength, maxLength, pattern } = schema;
65
+
66
+ const ruleList = [];
67
+ if (schema.option?.placeholder) {
68
+ ruleList.push(schema.option.placeholder);
69
+ }
70
+ if (minLength) {
71
+ ruleList.push(`最小长度为 ${minLength}`);
72
+ }
73
+ if (maxLength) {
74
+ ruleList.push(`最大长度为 ${maxLength}`);
75
+ }
76
+ if (pattern) {
77
+ ruleList.push(`格式为 ${pattern}`);
78
+ }
79
+ placeholder.value = ruleList.join('|');
80
+ }
81
+
82
+ onMounted(() => {
83
+ initData();
84
+ });
85
+
86
+ watch([model, schema], () => {
87
+ initData();
88
+ }, { deep: true });
89
+
90
+ const validate = () => {
91
+ validTips.value = null;
92
+ const { type } = schema;
93
+
94
+ // 检验是否必填
95
+ if (schema.option?.required && !dtoValue.value) {
96
+ validTips.value = '不能为空';
97
+ return false;
98
+ }
99
+ // 校验 schema
100
+ if (dtoValue.value) {
101
+ const validate = ajv.compile(schema);
102
+ const valid = validate(dtoValue.value);
103
+ if (!valid && validate.errors && validate.errors[0]) {
104
+ const { keyword, params } = validate.errors[0];
105
+ if (keyword === 'type') {
106
+ validTips.value = `类型必须为 ${type}`;
107
+ } else if (keyword === 'maxLength') {
108
+ validTips.value = `最大长度应为 ${params.limit}`;
109
+ } else if (keyword === 'minLength') {
110
+ validTips.value = `最小长度应为 ${params.limit}`;
111
+ } else if (keyword === 'pattern') {
112
+ validTips.value = `格式不正确`;
113
+ } else {
114
+ console.log(valid.errors[0]);
115
+ validTips.value = `不符合要求`;
116
+ }
117
+ return false;
118
+ }
119
+ }
120
+ return true;
121
+ }
122
+ const getValue = () => {
123
+ return dtoValue.value !== undefined ? {
124
+ [schemaKey]: dtoValue.value
125
+ } : {}
126
+ }
127
+
128
+ const onFocus = () => {
129
+ validTips.value = null;
130
+ }
131
+ const onBlur = () => {
132
+ validate();
133
+ }
134
+
135
+ defineExpose({
136
+ name,
137
+ validate,
138
+ getValue
139
+ })
140
+ </script>
141
+ <style lang="less" scoped>
142
+
143
+ </style>
@@ -0,0 +1,141 @@
1
+ <template>
2
+ <el-row
3
+ type="flex"
4
+ align="middle"
5
+ class="form-item"
6
+ >
7
+ <!-- label -->
8
+ <el-row
9
+ justify="end"
10
+ class="item-label"
11
+ >
12
+ <el-row
13
+ v-if="schema.option?.required"
14
+ type="flex"
15
+ class="required"
16
+ >
17
+ *
18
+ </el-row>
19
+ {{ schema.label }}
20
+ </el-row>
21
+ <!-- value -->
22
+ <el-row class="item-value">
23
+ <el-input-number
24
+ v-model="dtoValue"
25
+ v-bind="schema.option"
26
+ class="component"
27
+ :class="{'valid-border': validTips}"
28
+ :controls="false"
29
+ :placeholder="placeholder"
30
+ @input="validate"
31
+ @focus="onFocus"
32
+ @blur="onBlur"
33
+ />
34
+ </el-row>
35
+ <el-row
36
+ v-if="validTips"
37
+ class="valid-tips"
38
+ >
39
+ {{ validTips }}
40
+ </el-row>
41
+ </el-row>
42
+ </template>
43
+ <script setup>
44
+ import { ref, onMounted, watch, inject, toRefs } from 'vue';
45
+
46
+ const ajv = inject('ajv');
47
+
48
+ const props = defineProps({
49
+ schemaKey: String,
50
+ schema: Object,
51
+ model: Number
52
+ });
53
+ const { schemaKey, schema } = props;
54
+ const { model } = toRefs(props);
55
+
56
+ const name = ref('inputNumber');
57
+
58
+ const dtoValue = ref();
59
+ const placeholder = ref('');
60
+ const validTips = ref(null);
61
+ const initData = () => {
62
+ validTips.value = null;
63
+ dtoValue.value = model.value ?? schema.option?.default;
64
+
65
+ const { minimum, maximum } = schema;
66
+
67
+ const ruleList = [];
68
+ if (schema.option?.placeholder) {
69
+ ruleList.push(schema.option.placeholder);
70
+ }
71
+ if (minimum !== undefined) {
72
+ ruleList.push(`最小值为 ${minimum}`);
73
+ }
74
+ if (maximum !== undefined) {
75
+ ruleList.push(`最大值为 ${maximum}`);
76
+ }
77
+ placeholder.value = ruleList.join('|');
78
+ }
79
+
80
+ onMounted(() => {
81
+ initData();
82
+ });
83
+
84
+ watch([model, schema], () => {
85
+ initData();
86
+ }, { deep: true });
87
+
88
+ const validate = () => {
89
+ validTips.value = null;
90
+ const { type } = schema;
91
+
92
+ // 检验是否必填
93
+ if (schema.option?.required && !dtoValue.value) {
94
+ validTips.value = '不能为空';
95
+ return false;
96
+ }
97
+ // 校验 schema
98
+ if (dtoValue.value) {
99
+ const validate = ajv.compile(schema);
100
+ const valid = validate(dtoValue.value);
101
+ if (!valid && validate.errors && validate.errors[0]) {
102
+ const { keyword, params } = validate.errors[0];
103
+ if (keyword === 'type') {
104
+ validTips.value = `类型必须为 ${type}`;
105
+ } else if (keyword === 'minimum') {
106
+ validTips.value = `最小值为 ${params.limit}`;
107
+ } else if (keyword === 'maximum') {
108
+ validTips.value = `最大值为 ${params.limit}`;
109
+ } else {
110
+ console.log(valid.errors[0]);
111
+ validTips.value = `不符合要求`;
112
+ }
113
+ return false;
114
+ }
115
+ }
116
+ return true;
117
+ }
118
+ const getValue = () => {
119
+ return dtoValue.value !== undefined ? {
120
+ [schemaKey]: dtoValue.value
121
+ } : {}
122
+ }
123
+
124
+ const onFocus = () => {
125
+ validTips.value = null;
126
+ }
127
+ const onBlur = () => {
128
+ validate();
129
+ }
130
+
131
+ defineExpose({
132
+ name,
133
+ validate,
134
+ getValue
135
+ })
136
+ </script>
137
+ <style lang="less" scoped>
138
+ :deep(.el-input-number .el-input__inner) {
139
+ text-align: left;
140
+ }
141
+ </style>