@james-zhang/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.
- package/.eslintignore +3 -0
- package/.eslintrc +55 -0
- package/.tmp-check.js +47 -0
- package/README.md +287 -0
- package/app/controller/base.js +37 -0
- package/app/controller/project.js +76 -0
- package/app/controller/view.js +17 -0
- package/app/extend/logger.js +36 -0
- package/app/middleware/api-params-verify.js +78 -0
- package/app/middleware/api-sign-verify.js +31 -0
- package/app/middleware/error-handler.js +36 -0
- package/app/middleware/project-handler.js +26 -0
- package/app/middleware.js +69 -0
- package/app/pages/asserts/custom.css +10 -0
- package/app/pages/boot.js +44 -0
- package/app/pages/common/curl.js +76 -0
- package/app/pages/common/utils.js +2 -0
- package/app/pages/dashboard/complex-view/header-view/complex-view/sub-menu/sub-menu.vue +19 -0
- package/app/pages/dashboard/complex-view/header-view/header-view.vue +131 -0
- package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +29 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/search-pannel/search-pannel.vue +36 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/table-pannel/table-pannel.vue +125 -0
- package/app/pages/dashboard/complex-view/schema-view/components/component-config.js +19 -0
- package/app/pages/dashboard/complex-view/schema-view/components/create-form/create-form.vue +119 -0
- package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +131 -0
- package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +149 -0
- package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +126 -0
- package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +111 -0
- package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +28 -0
- package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +138 -0
- package/app/pages/dashboard/dashboard.vue +121 -0
- package/app/pages/dashboard/entry.dashboard.js +52 -0
- package/app/pages/dashboard/router.js +3 -0
- package/app/pages/dashboard/todo/todo.vue +10 -0
- package/app/pages/store/index.js +3 -0
- package/app/pages/store/menu.js +71 -0
- package/app/pages/store/project.js +15 -0
- package/app/pages/widgets/header-container/asserts/avatar.png +0 -0
- package/app/pages/widgets/header-container/asserts/logo.png +0 -0
- package/app/pages/widgets/header-container/header-container.vue +118 -0
- package/app/pages/widgets/schema-form/complex-view/input/input.vue +165 -0
- package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +164 -0
- package/app/pages/widgets/schema-form/complex-view/select/select.vue +138 -0
- package/app/pages/widgets/schema-form/form-item-config.js +20 -0
- package/app/pages/widgets/schema-form/schema-form.vue +154 -0
- package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +49 -0
- package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +65 -0
- package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +44 -0
- package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +50 -0
- package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +134 -0
- package/app/pages/widgets/schema-search-bar/search-item-config.js +27 -0
- package/app/pages/widgets/schema-table/schema-table.vue +277 -0
- package/app/pages/widgets/sider-container/sider-container.vue +42 -0
- package/app/public/static/logo.png +0 -0
- package/app/public/static/normalize.css +239 -0
- package/app/router/project.js +7 -0
- package/app/router/view.js +7 -0
- package/app/router-schema/project.js +34 -0
- package/app/service/base.js +10 -0
- package/app/service/project.js +43 -0
- package/app/view/entry.tpl +31 -0
- package/app/webpack/config/webpack.base.js +224 -0
- package/app/webpack/config/webpack.dev.js +53 -0
- package/app/webpack/config/webpack.prod.js +104 -0
- package/app/webpack/dev.js +54 -0
- package/app/webpack/libs/blank.js +1 -0
- package/app/webpack/prod.js +17 -0
- package/config/config.default.js +3 -0
- package/docs/dashboard-model.js +150 -0
- package/elpis-core/env.js +21 -0
- package/elpis-core/index.js +82 -0
- package/elpis-core/loader/config.js +58 -0
- package/elpis-core/loader/controller.js +55 -0
- package/elpis-core/loader/extend.js +42 -0
- package/elpis-core/loader/middleware.js +50 -0
- package/elpis-core/loader/router-schema.js +28 -0
- package/elpis-core/loader/router.js +42 -0
- package/elpis-core/loader/service.js +51 -0
- package/index.js +37 -0
- package/logs/application.log +50 -0
- package/logs/application.log.2026-06-13 +159 -0
- package/model/index.js +119 -0
- package/package.json +88 -0
- package/scripts/generate-cover.py +93 -0
- package/scripts/generate-diagrams.js +80 -0
- package/test/controller/project.test.js +218 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-drawer
|
|
3
|
+
v-model="isShow"
|
|
4
|
+
direction="rtl"
|
|
5
|
+
:destroy-on-close="true"
|
|
6
|
+
:size="550"
|
|
7
|
+
>
|
|
8
|
+
<template #header>
|
|
9
|
+
<h3>{{ title }}</h3>
|
|
10
|
+
</template>
|
|
11
|
+
<template #default>
|
|
12
|
+
<SchemaForm
|
|
13
|
+
ref="schemaFormRef"
|
|
14
|
+
v-loading="loading"
|
|
15
|
+
:schema="schema"
|
|
16
|
+
:model="formModel"
|
|
17
|
+
/>
|
|
18
|
+
</template>
|
|
19
|
+
<template #footer>
|
|
20
|
+
<el-button
|
|
21
|
+
type="primary"
|
|
22
|
+
@click="save"
|
|
23
|
+
>
|
|
24
|
+
{{ saveBtnText }}
|
|
25
|
+
</el-button>
|
|
26
|
+
</template>
|
|
27
|
+
</el-drawer>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup>
|
|
31
|
+
import { computed, inject, ref } from 'vue';
|
|
32
|
+
import { ElNotification } from 'element-plus';
|
|
33
|
+
import $curl from '$elpisCommon/curl';
|
|
34
|
+
import SchemaForm from '$elpisWidgets/schema-form/schema-form.vue';
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
api,
|
|
38
|
+
components
|
|
39
|
+
} = inject('schemaViewData')
|
|
40
|
+
|
|
41
|
+
const emit = defineEmits(['command'])
|
|
42
|
+
|
|
43
|
+
const name = ref('createForm');
|
|
44
|
+
const componentConfig = computed(() => components.value[name.value] ?? {});
|
|
45
|
+
const schema = computed(() => componentConfig.value.schema ?? {});
|
|
46
|
+
const config = computed(() => componentConfig.value.config ?? {});
|
|
47
|
+
const title = computed(() => config.value.title ?? '');
|
|
48
|
+
const saveBtnText = computed(() => config.value.saveBtnText ?? '');
|
|
49
|
+
|
|
50
|
+
const schemaFormRef = ref(null);
|
|
51
|
+
const loading = ref(false);
|
|
52
|
+
// 控制抽屉显示隐藏
|
|
53
|
+
const isShow = ref(false);
|
|
54
|
+
// 新增表单数据对象
|
|
55
|
+
const formModel = ref({});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 父组件调用打开抽屉方法
|
|
59
|
+
* @param {Object} rowData 表格行数据:新增时为空,编辑时传入当前行详情
|
|
60
|
+
*/
|
|
61
|
+
const show = async () => {
|
|
62
|
+
let component = components.value[name.value];
|
|
63
|
+
let retryCount = 0;
|
|
64
|
+
while (!component && retryCount < 10) {
|
|
65
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
66
|
+
component = components.value[name.value];
|
|
67
|
+
retryCount++;
|
|
68
|
+
}
|
|
69
|
+
if (!component) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
isShow.value = true;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const close = () => {
|
|
76
|
+
isShow.value = false;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const save = async () => {
|
|
80
|
+
if(loading.value){
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if(!(await schemaFormRef.value.validate())) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
loading.value = true;
|
|
88
|
+
const res=await $curl({
|
|
89
|
+
url: api.value,
|
|
90
|
+
method: 'POST',
|
|
91
|
+
data: {
|
|
92
|
+
...schemaFormRef.value.getValue()
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
loading.value = false;
|
|
96
|
+
if(!res || !res.success){
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
ElNotification.success({
|
|
100
|
+
title: '保存成功',
|
|
101
|
+
message: '保存成功',
|
|
102
|
+
type: 'success'
|
|
103
|
+
});
|
|
104
|
+
close();
|
|
105
|
+
emit('command', {
|
|
106
|
+
event:'loadTableData'
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// 向外暴露方法、变量,供父组件通过ref调用
|
|
111
|
+
defineExpose({
|
|
112
|
+
name,
|
|
113
|
+
show
|
|
114
|
+
});
|
|
115
|
+
</script>
|
|
116
|
+
|
|
117
|
+
<style lang="less" scoped>
|
|
118
|
+
|
|
119
|
+
</style>
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-drawer
|
|
3
|
+
v-model="isShow"
|
|
4
|
+
direction="rtl"
|
|
5
|
+
:destroy-on-close="true"
|
|
6
|
+
:size="550"
|
|
7
|
+
>
|
|
8
|
+
<!-- 抽屉头部标题 -->
|
|
9
|
+
<template #header>
|
|
10
|
+
<h3>{{ title }}</h3>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<!-- 抽屉主体内容区 -->
|
|
14
|
+
<template #default>
|
|
15
|
+
<el-card
|
|
16
|
+
v-loading="loading"
|
|
17
|
+
shadow="always"
|
|
18
|
+
class="detail-panel"
|
|
19
|
+
>
|
|
20
|
+
<!-- 遍历schema配置里所有字段,自动渲染详情项 -->
|
|
21
|
+
<el-row
|
|
22
|
+
v-for="(item, key) in schema.properties"
|
|
23
|
+
:key="key"
|
|
24
|
+
class="row-item"
|
|
25
|
+
>
|
|
26
|
+
<!-- 字段标签名称 -->
|
|
27
|
+
<el-col
|
|
28
|
+
:span="6"
|
|
29
|
+
class="item-label"
|
|
30
|
+
>
|
|
31
|
+
{{ item.label }}:
|
|
32
|
+
</el-col>
|
|
33
|
+
<!-- 后端返回详情值 -->
|
|
34
|
+
<el-col
|
|
35
|
+
:span="18"
|
|
36
|
+
class="item-value"
|
|
37
|
+
>
|
|
38
|
+
{{ dtoModel[key] }}
|
|
39
|
+
</el-col>
|
|
40
|
+
</el-row>
|
|
41
|
+
</el-card>
|
|
42
|
+
</template>
|
|
43
|
+
</el-drawer>
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
<script setup>
|
|
47
|
+
import { computed, inject, ref } from 'vue';
|
|
48
|
+
import $curl from '$elpisCommon/curl';
|
|
49
|
+
|
|
50
|
+
const {
|
|
51
|
+
api,
|
|
52
|
+
components
|
|
53
|
+
}=inject('schemaViewData')
|
|
54
|
+
|
|
55
|
+
const name=ref('detailPanel')
|
|
56
|
+
const componentConfig = computed(() => components.value[name.value] ?? {});
|
|
57
|
+
const schema = computed(() => componentConfig.value.schema ?? {});
|
|
58
|
+
const config = computed(() => componentConfig.value.config ?? {});
|
|
59
|
+
const title = computed(() => config.value.title ?? '');
|
|
60
|
+
|
|
61
|
+
const isShow = ref(false);
|
|
62
|
+
const loading=ref(false)
|
|
63
|
+
const mainKey=ref('')
|
|
64
|
+
const mainValue=ref()
|
|
65
|
+
const dtoModel=ref({})
|
|
66
|
+
|
|
67
|
+
// 打开抽屉方法,接收表格行数据
|
|
68
|
+
const show = (rowData) => {
|
|
69
|
+
mainKey.value = config.value.mainKey ?? '';
|
|
70
|
+
if (mainKey.value && rowData) {
|
|
71
|
+
mainValue.value = rowData[mainKey.value];
|
|
72
|
+
}
|
|
73
|
+
dtoModel.value={}
|
|
74
|
+
isShow.value = true;
|
|
75
|
+
|
|
76
|
+
fetchFormData()
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const close = () => {
|
|
80
|
+
isShow.value = false;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const fetchFormData = async () => {
|
|
84
|
+
if (loading.value) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
loading.value = true;
|
|
88
|
+
const res = await $curl({
|
|
89
|
+
method: 'get',
|
|
90
|
+
url: api.value,
|
|
91
|
+
query: {
|
|
92
|
+
[mainKey.value]: mainValue.value
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
loading.value = false;
|
|
96
|
+
|
|
97
|
+
if (!res || !res.success || !res.data) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
dtoModel.value = res.data;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// 向父组件暴露方法,父组件可通过ref调用show打开弹窗
|
|
104
|
+
defineExpose({
|
|
105
|
+
show,
|
|
106
|
+
close
|
|
107
|
+
})
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<style lang="less" scoped>
|
|
111
|
+
.detail-panel {
|
|
112
|
+
border: 1px solid #a6a6a6;
|
|
113
|
+
padding: 30px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.row-item {
|
|
117
|
+
margin-bottom: 15px;
|
|
118
|
+
font-size: 16px;
|
|
119
|
+
|
|
120
|
+
.item-label {
|
|
121
|
+
color: #333333;
|
|
122
|
+
text-align: right;
|
|
123
|
+
padding-right: 10px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.item-value {
|
|
127
|
+
color: #666666;
|
|
128
|
+
word-break: break-all;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
</style>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-drawer
|
|
3
|
+
v-model="isShow"
|
|
4
|
+
direction="rtl"
|
|
5
|
+
:destroy-on-close="true"
|
|
6
|
+
:size="550"
|
|
7
|
+
>
|
|
8
|
+
<template #header>
|
|
9
|
+
<h3>{{ title }}</h3>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<template #default>
|
|
13
|
+
<SchemaForm
|
|
14
|
+
ref="schemaFormRef"
|
|
15
|
+
v-loading="loading"
|
|
16
|
+
:schema="schema"
|
|
17
|
+
:model="dtoModel"
|
|
18
|
+
/>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<template #footer>
|
|
22
|
+
<el-button
|
|
23
|
+
type="primary"
|
|
24
|
+
@click="save"
|
|
25
|
+
>
|
|
26
|
+
{{ saveBtnText }}
|
|
27
|
+
</el-button>
|
|
28
|
+
</template>
|
|
29
|
+
</el-drawer>
|
|
30
|
+
</template>
|
|
31
|
+
<script setup>
|
|
32
|
+
import { computed, ref, inject } from 'vue';
|
|
33
|
+
import {ElNotification} from 'element-plus'
|
|
34
|
+
import $curl from '$elpisCommon/curl'
|
|
35
|
+
import SchemaForm from '$elpisWidgets/schema-form/schema-form.vue'
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
api,
|
|
39
|
+
components
|
|
40
|
+
}=inject('schemaViewData')
|
|
41
|
+
|
|
42
|
+
const emit=defineEmits(['command'])
|
|
43
|
+
|
|
44
|
+
const schemaFormRef = ref(null);
|
|
45
|
+
const name = ref('editForm');
|
|
46
|
+
const componentConfig = computed(() => components.value[name.value] ?? {});
|
|
47
|
+
const schema = computed(() => componentConfig.value.schema ?? {});
|
|
48
|
+
const config = computed(() => componentConfig.value.config ?? {});
|
|
49
|
+
const title = computed(() => config.value.title ?? '');
|
|
50
|
+
const saveBtnText = computed(() => config.value.saveBtnText ?? '');
|
|
51
|
+
const mainKey = ref('')
|
|
52
|
+
const mainValue = ref('')
|
|
53
|
+
const isShow = ref(false);
|
|
54
|
+
const loading=ref(false)
|
|
55
|
+
const dtoModel=ref({})
|
|
56
|
+
|
|
57
|
+
const show = async (rowData) => {
|
|
58
|
+
let component = components.value[name.value];
|
|
59
|
+
let retryCount = 0;
|
|
60
|
+
while (!component && retryCount < 10) {
|
|
61
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
62
|
+
component = components.value[name.value];
|
|
63
|
+
retryCount++;
|
|
64
|
+
}
|
|
65
|
+
if (!component) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
mainKey.value = config.value.mainKey ?? ''; // 表单主键
|
|
69
|
+
if (mainKey.value && rowData) {
|
|
70
|
+
mainValue.value = rowData[mainKey.value]; // 表单主键值
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
dtoModel.value = {};
|
|
74
|
+
isShow.value = true;
|
|
75
|
+
fetchFormData();
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const close = () => {
|
|
79
|
+
isShow.value = false;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const fetchFormData = async () => {
|
|
83
|
+
if (loading.value) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
loading.value = true;
|
|
87
|
+
const res = await $curl({
|
|
88
|
+
method: 'get',
|
|
89
|
+
url: api.value,
|
|
90
|
+
query: {
|
|
91
|
+
[mainKey.value]: mainValue.value
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
loading.value = false;
|
|
95
|
+
|
|
96
|
+
if (!res || !res.success || !res.data) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
dtoModel.value = res.data;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const save = async () => {
|
|
103
|
+
if (loading.value) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// 表单校验,校验失败直接终止提交
|
|
107
|
+
if (!(await schemaFormRef.value.validate())) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
loading.value = true;
|
|
112
|
+
// 发起编辑PUT请求
|
|
113
|
+
const res = await $curl({
|
|
114
|
+
method: 'put',
|
|
115
|
+
url: api.value,
|
|
116
|
+
data: {
|
|
117
|
+
[mainKey.value]: mainValue.value,
|
|
118
|
+
...schemaFormRef.value.getValue()
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
loading.value = false;
|
|
122
|
+
|
|
123
|
+
// 接口请求失败拦截
|
|
124
|
+
if (!res || !res.success) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 弹出成功通知
|
|
129
|
+
ElNotification({
|
|
130
|
+
title: '修改成功',
|
|
131
|
+
message: '修改成功',
|
|
132
|
+
type: 'success'
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// 关闭抽屉弹窗
|
|
136
|
+
close();
|
|
137
|
+
// 向上派发事件,通知父组件刷新表格数据
|
|
138
|
+
emit('command', {
|
|
139
|
+
event: 'loadTableData'
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
defineExpose({
|
|
144
|
+
name,
|
|
145
|
+
show
|
|
146
|
+
});
|
|
147
|
+
</script>
|
|
148
|
+
|
|
149
|
+
<style lang="less" scoped></style>
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ref, watch, onMounted } from 'vue'
|
|
2
|
+
import { useRoute } from 'vue-router'
|
|
3
|
+
import { storeToRefs } from 'pinia'
|
|
4
|
+
import { useMenuStore } from '$elpisStore/menu.js'
|
|
5
|
+
|
|
6
|
+
export function useSchema() {
|
|
7
|
+
const route = useRoute()
|
|
8
|
+
const menuStore = useMenuStore()
|
|
9
|
+
const { menuList } = storeToRefs(menuStore)
|
|
10
|
+
|
|
11
|
+
const tableSchema = ref({})
|
|
12
|
+
const tableConfig = ref()
|
|
13
|
+
const searchSchema = ref({})
|
|
14
|
+
const searchConfig = ref()
|
|
15
|
+
const components = ref({})
|
|
16
|
+
|
|
17
|
+
const api = ref('')
|
|
18
|
+
|
|
19
|
+
// 构造schemaConfig相关配置,输送给schemaView解释
|
|
20
|
+
const buildData = function () {
|
|
21
|
+
const { key, sider_key: siderKey } = route.query
|
|
22
|
+
|
|
23
|
+
const mItem = menuStore.findMenuItem({
|
|
24
|
+
key: 'key',
|
|
25
|
+
value: key ?? siderKey
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
if (mItem && mItem.schemaConfig) {
|
|
29
|
+
const { schemaConfig: sConfig } = mItem
|
|
30
|
+
|
|
31
|
+
const configSchema = JSON.parse(JSON.stringify(sConfig.schema))
|
|
32
|
+
|
|
33
|
+
api.value = sConfig.api ?? ''
|
|
34
|
+
|
|
35
|
+
// 构造tableSchema和tableConfig
|
|
36
|
+
tableSchema.value = buildDtoSchema(configSchema, 'table')
|
|
37
|
+
tableConfig.value = sConfig.tableConfig
|
|
38
|
+
// 构造searchSchema和searchConfig
|
|
39
|
+
const dtoSearchSchema = buildDtoSchema(configSchema, 'search')
|
|
40
|
+
for (const key in dtoSearchSchema.properties) {
|
|
41
|
+
if (route.query[key] !== undefined) {
|
|
42
|
+
dtoSearchSchema.properties[key].option.default = route.query[key]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
searchSchema.value = dtoSearchSchema
|
|
46
|
+
searchConfig.value = sConfig.searchConfig
|
|
47
|
+
|
|
48
|
+
// 构造component = {comName: {schema:{},config={}}}
|
|
49
|
+
const { componentConfig } = sConfig
|
|
50
|
+
if (componentConfig && Object.keys(componentConfig).length > 0) {
|
|
51
|
+
const dtoComponents = {}
|
|
52
|
+
for (const comName in componentConfig) {
|
|
53
|
+
dtoComponents[comName] = {
|
|
54
|
+
schema: buildDtoSchema(configSchema, comName),
|
|
55
|
+
config: componentConfig[comName]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
components.value = dtoComponents
|
|
59
|
+
} else {
|
|
60
|
+
components.value = {}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 通用构建 schema 方法(清除噪音)
|
|
66
|
+
const buildDtoSchema = function (_schema, comName) {
|
|
67
|
+
if (!_schema?.properties) {
|
|
68
|
+
return {}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const dtoSchema = {
|
|
72
|
+
type: 'object',
|
|
73
|
+
properties: {}
|
|
74
|
+
}
|
|
75
|
+
// 提取有效schema字段信息(清除噪音)
|
|
76
|
+
for (const key in _schema.properties) {
|
|
77
|
+
const props = _schema.properties[key]
|
|
78
|
+
// 优先取当前组件自己的 option,没有则复用 createFormOption
|
|
79
|
+
let optionKey = `${comName}Option`
|
|
80
|
+
if (!props[optionKey] && props.createFormOption) {
|
|
81
|
+
optionKey = 'createFormOption'
|
|
82
|
+
}
|
|
83
|
+
if (props[optionKey]) {
|
|
84
|
+
let dtoProps = {}
|
|
85
|
+
// 提取props中非option的部分,存放到dtoprops
|
|
86
|
+
for (const pKey in props) {
|
|
87
|
+
if (pKey.indexOf('Option') < 0) {
|
|
88
|
+
dtoProps[pKey] = props[pKey]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// 处理 comName Option
|
|
92
|
+
dtoProps = Object.assign({}, dtoProps, { option: props[optionKey] })
|
|
93
|
+
|
|
94
|
+
// 处理required字段
|
|
95
|
+
const { required } = _schema
|
|
96
|
+
if (required && required.find(pk => pk === key)) {
|
|
97
|
+
dtoProps.option.required = true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
dtoSchema.properties[key] = dtoProps
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return dtoSchema
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
watch([
|
|
107
|
+
() => route.query.key,
|
|
108
|
+
() => route.query.sider_key,
|
|
109
|
+
() => menuList.value
|
|
110
|
+
], () => {
|
|
111
|
+
buildData()
|
|
112
|
+
}, { deep: true })
|
|
113
|
+
|
|
114
|
+
onMounted(() => {
|
|
115
|
+
buildData()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
api,
|
|
120
|
+
tableSchema,
|
|
121
|
+
tableConfig,
|
|
122
|
+
searchSchema,
|
|
123
|
+
searchConfig,
|
|
124
|
+
components
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="schema-view">
|
|
3
|
+
<SearchPanel v-if="searchSchema?.properties && Object.keys(searchSchema.properties).length>0" class="search-panel-wrapper" @search="onSearch" @reset="onReset" @load="onLoad" />
|
|
4
|
+
<TablePanel ref="tablePanelRef" @operate="onTableOperate" class="table-panel-wrapper" />
|
|
5
|
+
<template v-for="(item,key) in components">
|
|
6
|
+
<component
|
|
7
|
+
:key="key"
|
|
8
|
+
v-if="ComponentConfig[key] && ComponentConfig[key].component"
|
|
9
|
+
:is="ComponentConfig[key].component"
|
|
10
|
+
:ref="(el) => setComRef(el, key)"
|
|
11
|
+
@command="onComponentCommand"
|
|
12
|
+
></component>
|
|
13
|
+
</template>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
<script setup>
|
|
17
|
+
import {provide,ref} from 'vue'
|
|
18
|
+
import SearchPanel from './complex-view/search-pannel/search-pannel.vue'
|
|
19
|
+
import TablePanel from './complex-view/table-pannel/table-pannel.vue'
|
|
20
|
+
import ComponentConfig from './components/component-config.js'
|
|
21
|
+
import {useSchema} from './hook/schema'
|
|
22
|
+
|
|
23
|
+
const {api,tableSchema,tableConfig,searchSchema,searchConfig,components}=useSchema()
|
|
24
|
+
|
|
25
|
+
const apiParams=ref({})
|
|
26
|
+
provide('schemaViewData',{
|
|
27
|
+
api,
|
|
28
|
+
apiParams,
|
|
29
|
+
tableSchema,
|
|
30
|
+
tableConfig,
|
|
31
|
+
SearchSchema:searchSchema,
|
|
32
|
+
SearchConfig:searchConfig,
|
|
33
|
+
components
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const tablePanelRef=ref(null)
|
|
37
|
+
|
|
38
|
+
const comRefMap=ref({})
|
|
39
|
+
const setComRef=(el, key)=>{
|
|
40
|
+
if(el){
|
|
41
|
+
comRefMap.value[key]=el
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
const onSearch=(searchValObj)=>{
|
|
47
|
+
apiParams.value=searchValObj
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//table事件映射
|
|
51
|
+
const EventHandlerMap={
|
|
52
|
+
showComponent:showComponent,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const onTableOperate=({btnConfig,rowData})=>{
|
|
56
|
+
const {eventKey}=btnConfig
|
|
57
|
+
if(EventHandlerMap[eventKey]){
|
|
58
|
+
EventHandlerMap[eventKey]({btnConfig,rowData})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//table事件处理
|
|
62
|
+
async function showComponent({btnConfig,rowData}){
|
|
63
|
+
const {comName}=btnConfig.eventOption
|
|
64
|
+
if(!comName){
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
// 等待组件被渲染
|
|
68
|
+
let comRef=comRefMap.value[comName]
|
|
69
|
+
let retryCount=0
|
|
70
|
+
while(!comRef && retryCount<10){
|
|
71
|
+
await new Promise(resolve=>setTimeout(resolve,100))
|
|
72
|
+
comRef=comRefMap.value[comName]
|
|
73
|
+
retryCount++
|
|
74
|
+
}
|
|
75
|
+
if(!comRef || typeof comRef.show!=='function'){
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
comRef.show(rowData)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
//响应组件事件
|
|
82
|
+
const onComponentCommand = (data)=>{
|
|
83
|
+
const {event}=data
|
|
84
|
+
if(event==='loadTableData'){
|
|
85
|
+
tablePanelRef.value.loadTableData()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<style lang="less" scoped>
|
|
94
|
+
.schema-view{
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-direction: column;
|
|
97
|
+
height: 100%;
|
|
98
|
+
width: 100%;
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
|
|
101
|
+
.search-panel-wrapper {
|
|
102
|
+
flex-shrink: 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.table-panel-wrapper {
|
|
106
|
+
flex: 1;
|
|
107
|
+
min-height: 0;
|
|
108
|
+
overflow: hidden;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- 父级折叠子菜单 -->
|
|
3
|
+
<el-sub-menu :index="menuItem.key">
|
|
4
|
+
<template #title>
|
|
5
|
+
{{ menuItem.name }}
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<!-- 遍历当前菜单的子项 -->
|
|
9
|
+
<template v-for="item in menuItem.subMenu" :key="item.key">
|
|
10
|
+
<!-- 递归调用自身:存在下级子菜单则继续嵌套 -->
|
|
11
|
+
<sub-menu
|
|
12
|
+
v-if="item.subMenu && item.subMenu.length > 0"
|
|
13
|
+
:menu-item="item"
|
|
14
|
+
/>
|
|
15
|
+
<!-- 无子菜单则渲染普通菜单项 -->
|
|
16
|
+
<el-menu-item v-else :index="item.key">
|
|
17
|
+
{{ item.name }}
|
|
18
|
+
</el-menu-item>
|
|
19
|
+
</template>
|
|
20
|
+
</el-sub-menu>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup>
|
|
24
|
+
// 接收父组件传入的单条菜单对象
|
|
25
|
+
const { menuItem } = defineProps(['menuItem']);
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<style lang="less" scoped></style>
|