@jiangliffey/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/CLAUDE.md +81 -0
- package/README.md +200 -0
- package/app/controller/base.js +38 -0
- package/app/controller/project.js +74 -0
- package/app/controller/view.js +22 -0
- package/app/extend/logger.js +35 -0
- package/app/middleware/api-params-verify.js +81 -0
- package/app/middleware/api-sign-verify.js +35 -0
- package/app/middleware/error-handler.js +33 -0
- package/app/middleware/project-handler.js +27 -0
- package/app/middleware.js +37 -0
- package/app/pages/asserts/custom.css +12 -0
- package/app/pages/boot.js +50 -0
- package/app/pages/common/curl.js +89 -0
- package/app/pages/common/utils.js +2 -0
- package/app/pages/dashboard/complex-view/header-view/complex-view/sub-menu/sub-menu.vue +21 -0
- package/app/pages/dashboard/complex-view/header-view/header-view.vue +123 -0
- package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +43 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/search-panel/search-panel.vue +40 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/table-panel/table-panel.vue +124 -0
- package/app/pages/dashboard/complex-view/schema-view/components/component-config.js +23 -0
- package/app/pages/dashboard/complex-view/schema-view/components/create-form/create-form.vue +87 -0
- package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +100 -0
- package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +118 -0
- package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +124 -0
- package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +80 -0
- package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +21 -0
- package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +135 -0
- package/app/pages/dashboard/dashboard.vue +96 -0
- package/app/pages/dashboard/entry.dashboard.js +45 -0
- package/app/pages/dashboard/todo/todo.vue +11 -0
- package/app/pages/store/index.js +4 -0
- package/app/pages/store/menu.js +58 -0
- package/app/pages/store/project.js +14 -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 +106 -0
- package/app/pages/widgets/schema-form/complex-view/input/input.vue +134 -0
- package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +136 -0
- package/app/pages/widgets/schema-form/complex-view/select/select.vue +116 -0
- package/app/pages/widgets/schema-form/form-item-config.js +23 -0
- package/app/pages/widgets/schema-form/schema-form.vue +135 -0
- package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +50 -0
- package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +67 -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 +51 -0
- package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +129 -0
- package/app/pages/widgets/schema-search-bar/search-item-config.js +27 -0
- package/app/pages/widgets/schema-table/schema-table.vue +235 -0
- package/app/pages/widgets/sider-container/sider-container.vue +31 -0
- package/app/public/static/logo.png +0 -0
- package/app/public/static/normalize.css +239 -0
- package/app/router/project.js +6 -0
- package/app/router/view.js +8 -0
- package/app/router-schema/project.js +30 -0
- package/app/service/base.js +11 -0
- package/app/service/project.js +56 -0
- package/app/view/entry.tpl +26 -0
- package/app/webpack/config/webpack.base.js +203 -0
- package/app/webpack/config/webpack.dev.js +59 -0
- package/app/webpack/config/webpack.prod.js +107 -0
- package/app/webpack/dev.js +53 -0
- package/app/webpack/libs/blank.js +3 -0
- package/app/webpack/prod.js +17 -0
- package/config/config.default.js +3 -0
- package/docs/dashboard-model.js +153 -0
- package/elpis-core/env.js +23 -0
- package/elpis-core/index.js +96 -0
- package/elpis-core/loader/config.js +50 -0
- package/elpis-core/loader/controller.js +54 -0
- package/elpis-core/loader/extend.js +49 -0
- package/elpis-core/loader/middleware.js +53 -0
- package/elpis-core/loader/router-schema.js +41 -0
- package/elpis-core/loader/router.js +45 -0
- package/elpis-core/loader/service.js +54 -0
- package/index.js +40 -0
- package/model/index.js +99 -0
- package/package.json +92 -0
- package/test/controller/project.test.js +225 -0
|
@@ -0,0 +1,100 @@
|
|
|
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
|
+
<el-card v-loading="loading" shadow="always" class="detail-panel">
|
|
13
|
+
<el-row
|
|
14
|
+
v-for="(item, key) in components[name]?.schema?.properties"
|
|
15
|
+
:key="key"
|
|
16
|
+
type="flex"
|
|
17
|
+
align="middle"
|
|
18
|
+
class="row-item"
|
|
19
|
+
>
|
|
20
|
+
<el-row class="item-label">{{ item.label }}:</el-row>
|
|
21
|
+
<el-row class="item-value">{{ dtoModel[key] }}</el-row>
|
|
22
|
+
</el-row>
|
|
23
|
+
</el-card>
|
|
24
|
+
</template>
|
|
25
|
+
</el-drawer>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup>
|
|
29
|
+
import { inject, ref } from 'vue';
|
|
30
|
+
import $curl from '$elpisCommon/curl.js';
|
|
31
|
+
|
|
32
|
+
const { api, components } = inject('schemaViewData');
|
|
33
|
+
|
|
34
|
+
const name = ref('detailPanel');
|
|
35
|
+
|
|
36
|
+
const isShow = ref(false);
|
|
37
|
+
const loading = ref(false);
|
|
38
|
+
const title = ref('');
|
|
39
|
+
const mainKey = ref('');
|
|
40
|
+
const mainValue = ref();
|
|
41
|
+
const dtoModel = ref({});
|
|
42
|
+
|
|
43
|
+
const show = (rowData) => {
|
|
44
|
+
const { config } = components.value[name.value];
|
|
45
|
+
|
|
46
|
+
title.value = config.title;
|
|
47
|
+
mainKey.value = config.mainKey; // 表单主键
|
|
48
|
+
mainValue.value = rowData[config.mainKey]; // 表单主键值
|
|
49
|
+
dtoModel.value = {};
|
|
50
|
+
|
|
51
|
+
isShow.value = true;
|
|
52
|
+
|
|
53
|
+
fetchFormData();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const fetchFormData = async() => {
|
|
57
|
+
if(loading.value) { return; }
|
|
58
|
+
|
|
59
|
+
loading.value = true;
|
|
60
|
+
const res = await $curl({
|
|
61
|
+
method: 'get',
|
|
62
|
+
url: api.value,
|
|
63
|
+
query: {
|
|
64
|
+
[mainKey.value]: mainValue.value
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
loading.value = false;
|
|
68
|
+
|
|
69
|
+
if(!res || !res.success || !res.data) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
dtoModel.value = res.data;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
defineExpose({ name, show })
|
|
77
|
+
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<style lang="less" scoped>
|
|
81
|
+
.detail-panel {
|
|
82
|
+
border: 1px solid #a6a6a6;
|
|
83
|
+
padding: 30px;
|
|
84
|
+
|
|
85
|
+
.row-item {
|
|
86
|
+
height: 40px;
|
|
87
|
+
line-height: 40px;
|
|
88
|
+
font-size: 20px;
|
|
89
|
+
|
|
90
|
+
.item-label {
|
|
91
|
+
margin-right: 20px;
|
|
92
|
+
width: 120px;
|
|
93
|
+
color: white;
|
|
94
|
+
}
|
|
95
|
+
.item-value {
|
|
96
|
+
color: #d2dae4;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
</style>
|
|
@@ -0,0 +1,118 @@
|
|
|
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
|
+
<schema-form ref="schemaFormRef" v-loading="loading" :schema="components[name]?.schema" :model="dtoModel"></schema-form>
|
|
13
|
+
</template>
|
|
14
|
+
<template #footer>
|
|
15
|
+
<el-button type="primary" @click="save">{{ saveBtnText }}</el-button>
|
|
16
|
+
</template>
|
|
17
|
+
</el-drawer>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script setup>
|
|
21
|
+
import { ref, inject } from 'vue';
|
|
22
|
+
import $curl from '$elpisCommon/curl.js';
|
|
23
|
+
import { ElNotification } from 'element-plus';
|
|
24
|
+
import SchemaForm from '$elpisWidgets/schema-form/schema-form.vue';
|
|
25
|
+
|
|
26
|
+
const { api, components } = inject('schemaViewData');
|
|
27
|
+
|
|
28
|
+
const emit = defineEmits([ 'command' ])
|
|
29
|
+
|
|
30
|
+
const name = ref('editForm');
|
|
31
|
+
|
|
32
|
+
const schemaFormRef = ref(null);
|
|
33
|
+
const isShow = ref(false);
|
|
34
|
+
const loading = ref(false);
|
|
35
|
+
const title = ref('');
|
|
36
|
+
const saveBtnText = ref('');
|
|
37
|
+
const mainKey = ref('');
|
|
38
|
+
const mainValue = ref();
|
|
39
|
+
const dtoModel = ref({});
|
|
40
|
+
|
|
41
|
+
const show = (rowData) => {
|
|
42
|
+
const { config } = components.value[name.value];
|
|
43
|
+
|
|
44
|
+
title.value = config.title;
|
|
45
|
+
saveBtnText.value = config.saveBtnText;
|
|
46
|
+
mainKey.value = config.mainKey; // 表单主键
|
|
47
|
+
mainValue.value = rowData[config.mainKey]; // 表单主键值
|
|
48
|
+
dtoModel.value = {};
|
|
49
|
+
|
|
50
|
+
isShow.value = true;
|
|
51
|
+
|
|
52
|
+
fetchFormData();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const fetchFormData = async() => {
|
|
56
|
+
if(loading.value) { return; }
|
|
57
|
+
|
|
58
|
+
loading.value = true;
|
|
59
|
+
const res = await $curl({
|
|
60
|
+
method: 'get',
|
|
61
|
+
url: api.value,
|
|
62
|
+
query: {
|
|
63
|
+
[mainKey.value]: mainValue.value
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
loading.value = false;
|
|
67
|
+
|
|
68
|
+
if(!res || !res.success || !res.data) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
dtoModel.value = res.data;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const close = () => {
|
|
76
|
+
isShow.value = false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const save = async() => {
|
|
80
|
+
if(loading.value) { return; }
|
|
81
|
+
|
|
82
|
+
if(!schemaFormRef.value.validate()) { return; }
|
|
83
|
+
|
|
84
|
+
loading.value = true;
|
|
85
|
+
const res = await $curl({
|
|
86
|
+
method: 'put',
|
|
87
|
+
url: api.value,
|
|
88
|
+
data: {
|
|
89
|
+
[mainKey.value]: mainValue.value,
|
|
90
|
+
...schemaFormRef.value.getValue(),
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
loading.value = false;
|
|
94
|
+
|
|
95
|
+
if(res?.success) {
|
|
96
|
+
ElNotification.success({
|
|
97
|
+
title: '修改成功',
|
|
98
|
+
message: '修改成功'
|
|
99
|
+
});
|
|
100
|
+
isShow.value = false;
|
|
101
|
+
} else {
|
|
102
|
+
ElNotification.error({
|
|
103
|
+
title: '修改失败',
|
|
104
|
+
message: '修改失败'
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
close();
|
|
109
|
+
|
|
110
|
+
emit('command', {event: 'loadTableData'});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
defineExpose({ name, show })
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
<style lang="less" scoped>
|
|
117
|
+
|
|
118
|
+
</style>
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { ref, watch, onMounted, nextTick } from 'vue';
|
|
2
|
+
import { useRoute } from 'vue-router';
|
|
3
|
+
import { useMenuStore } from '$elpisStore/menu.js';
|
|
4
|
+
|
|
5
|
+
export const useSchema = function() {
|
|
6
|
+
const route = useRoute();
|
|
7
|
+
const menuStore = useMenuStore();
|
|
8
|
+
|
|
9
|
+
const api = ref('');
|
|
10
|
+
const apiParams = ref({});
|
|
11
|
+
const tableSchema = ref({});
|
|
12
|
+
const tableConfig = ref();
|
|
13
|
+
const searchSchema = ref({});
|
|
14
|
+
const searchConfig = ref();
|
|
15
|
+
const components = ref({});
|
|
16
|
+
|
|
17
|
+
// 构造 schemaConfig 相关配置,输送给 schemaView 解释
|
|
18
|
+
const buildData = () => {
|
|
19
|
+
const { key, sider_key: siderKey } = route.query;
|
|
20
|
+
|
|
21
|
+
const mItem = menuStore.findMenuItem({
|
|
22
|
+
key: 'key',
|
|
23
|
+
value: siderKey ?? key
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if(mItem && mItem.schemaConfig) {
|
|
27
|
+
const { schemaConfig: sConfig } = mItem;
|
|
28
|
+
|
|
29
|
+
const configSchema = JSON.parse(JSON.stringify(sConfig.schema));
|
|
30
|
+
|
|
31
|
+
api.value = sConfig.api ?? '';
|
|
32
|
+
|
|
33
|
+
tableSchema.value = {};
|
|
34
|
+
tableConfig.value = undefined;
|
|
35
|
+
searchSchema.value = {};
|
|
36
|
+
searchConfig.value = undefined;
|
|
37
|
+
components.value = {};
|
|
38
|
+
nextTick(() => {
|
|
39
|
+
// 构造 tableSchema 和 tableConfig
|
|
40
|
+
tableSchema.value = buildDtoSchema(configSchema, 'table');
|
|
41
|
+
tableConfig.value = sConfig.tableConfig;
|
|
42
|
+
|
|
43
|
+
// 构造 searchSchema 和 searchConfig
|
|
44
|
+
const dtoSearchSchema = buildDtoSchema(configSchema, 'search');
|
|
45
|
+
for(const key in dtoSearchSchema.properties) {
|
|
46
|
+
if(route.query[key] !== undefined) {
|
|
47
|
+
dtoSearchSchema.properties[key].option.default = route.query[key];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
searchSchema.value = dtoSearchSchema;
|
|
51
|
+
searchConfig.value = sConfig.searchConfig;
|
|
52
|
+
|
|
53
|
+
// 构造 components = { comKey: { schema: {}, config: {} }}
|
|
54
|
+
const { componentConfig } = sConfig;
|
|
55
|
+
if(componentConfig && Object.keys(componentConfig).length > 0) {
|
|
56
|
+
const dtoComponents = {};
|
|
57
|
+
|
|
58
|
+
for(const comName in componentConfig){
|
|
59
|
+
dtoComponents[comName] = {
|
|
60
|
+
schema: buildDtoSchema(configSchema, comName),
|
|
61
|
+
config: componentConfig[comName]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
components.value = dtoComponents;
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 通用构建 schema 方法(清除噪音 => 就是当前组件不需要关心的其他组件配置。)
|
|
71
|
+
const buildDtoSchema = (_schema, comName) => {
|
|
72
|
+
if(!_schema?.properties) { return; }
|
|
73
|
+
|
|
74
|
+
const dtoSchema = {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 提取有效 schema 字段信息
|
|
80
|
+
for(const key in _schema.properties) {
|
|
81
|
+
const props = _schema.properties[key];
|
|
82
|
+
if(props[`${comName}Option`]) {
|
|
83
|
+
let dtoProps = {};
|
|
84
|
+
// 提取 props 中非option的部分,存放到 dtoProps 中
|
|
85
|
+
for(const pKey in props) {
|
|
86
|
+
if(pKey.indexOf('Option') < 0) {
|
|
87
|
+
dtoProps[pKey] = props[pKey];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// 处理 comName Option(将当前组件的 xxxOption 统一改名为 option,方便组件内部统一读取)
|
|
91
|
+
dtoProps = Object.assign({}, dtoProps, { option: props[`${comName}Option`]});
|
|
92
|
+
|
|
93
|
+
// 处理 required 字段
|
|
94
|
+
const { required } = _schema;
|
|
95
|
+
if(required && required.find(pk => pk === key)) {
|
|
96
|
+
if(dtoProps.option) {
|
|
97
|
+
dtoProps.option.required = true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
dtoSchema.properties[key] = dtoProps;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return dtoSchema;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
watch(
|
|
109
|
+
[() => route.query.key, () => route.query.sider_key, () => menuStore.menuList],
|
|
110
|
+
() => {
|
|
111
|
+
buildData();
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
immediate: true,
|
|
115
|
+
deep: true
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// onMounted(() => {
|
|
120
|
+
// buildData();
|
|
121
|
+
// })
|
|
122
|
+
|
|
123
|
+
return { api, apiParams, tableSchema, tableConfig, searchSchema, searchConfig, components };
|
|
124
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-row class="schema-view">
|
|
3
|
+
<search-panel
|
|
4
|
+
v-if="searchSchema?.properties && Object.keys(searchSchema.properties).length > 0"
|
|
5
|
+
@search="onSearch"
|
|
6
|
+
></search-panel>
|
|
7
|
+
<table-panel
|
|
8
|
+
ref="tablePanelRef"
|
|
9
|
+
@operate="onTableOperate"
|
|
10
|
+
></table-panel>
|
|
11
|
+
<component
|
|
12
|
+
v-for="(item, key) in components"
|
|
13
|
+
:key="key"
|
|
14
|
+
:is="ComponentConfig[key]?.component"
|
|
15
|
+
ref="comListRef"
|
|
16
|
+
@command="onComponentCommand"
|
|
17
|
+
></component>
|
|
18
|
+
</el-row>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup>
|
|
22
|
+
import { provide, ref } from 'vue';
|
|
23
|
+
import SearchPanel from './complex-view/search-panel/search-panel.vue';
|
|
24
|
+
import TablePanel from './complex-view/table-panel/table-panel.vue';
|
|
25
|
+
import ComponentConfig from './components/component-config.js';
|
|
26
|
+
import { useSchema } from './hook/schema.js';
|
|
27
|
+
|
|
28
|
+
const { api, apiParams, tableSchema, tableConfig, searchSchema, searchConfig, components } = useSchema();
|
|
29
|
+
|
|
30
|
+
provide('schemaViewData', {
|
|
31
|
+
api, apiParams, tableSchema, tableConfig, searchSchema, searchConfig, components
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const tablePanelRef = ref(null);
|
|
35
|
+
const comListRef = ref([]);
|
|
36
|
+
|
|
37
|
+
const onSearch = (searchValObj) => {
|
|
38
|
+
apiParams.value = searchValObj;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// table 事件映射
|
|
42
|
+
const EventHandlerMap = {
|
|
43
|
+
showComponent: showComponent
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const onTableOperate = ({ btnConfig, rowData }) => {
|
|
47
|
+
const { eventKey } = btnConfig;
|
|
48
|
+
if(EventHandlerMap[eventKey]) {
|
|
49
|
+
EventHandlerMap[eventKey]({ btnConfig, rowData });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// showComponent 展示动态组件
|
|
54
|
+
function showComponent ({ btnConfig, rowData }) {
|
|
55
|
+
const { comName } = btnConfig.eventOption;
|
|
56
|
+
if(!comName) { return; }
|
|
57
|
+
|
|
58
|
+
const comRef = comListRef.value.find(item => item.name === comName);
|
|
59
|
+
if(comRef) {
|
|
60
|
+
comRef.show(rowData);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 响应组件事件
|
|
65
|
+
const onComponentCommand = (data) => {
|
|
66
|
+
const { event } = data;
|
|
67
|
+
if(event === 'loadTableData') {
|
|
68
|
+
tablePanelRef.value.loadTableData();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<style lang="less" scoped>
|
|
74
|
+
.schema-view {
|
|
75
|
+
display: flex;
|
|
76
|
+
flex-direction: column;
|
|
77
|
+
height: 100%;
|
|
78
|
+
width: 100%;
|
|
79
|
+
}
|
|
80
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-sub-menu :index="menuItem.key">
|
|
3
|
+
<template #title>{{ menuItem.name }}</template>
|
|
4
|
+
<div v-for="item in menuItem.subMenu" :key="item.key">
|
|
5
|
+
<sub-menu
|
|
6
|
+
v-if="item.subMenu && item.subMenu.length > 0"
|
|
7
|
+
:menu-item="item"
|
|
8
|
+
>
|
|
9
|
+
</sub-menu>
|
|
10
|
+
<el-menu-item v-else :index="item.key">{{ item.name }}</el-menu-item>
|
|
11
|
+
</div>
|
|
12
|
+
</el-sub-menu>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script setup>
|
|
16
|
+
const { menuItem } = defineProps([ 'menuItem' ]);
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<style lang="less" scoped>
|
|
20
|
+
|
|
21
|
+
</style>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<sider-container>
|
|
3
|
+
<template #menu-content>
|
|
4
|
+
<el-menu
|
|
5
|
+
:key="activeKey"
|
|
6
|
+
:default-active="activeKey"
|
|
7
|
+
:ellipsis="false"
|
|
8
|
+
@select="onMenuSelect"
|
|
9
|
+
>
|
|
10
|
+
<template v-for="item in menuList" :key="item.key">
|
|
11
|
+
<!-- group -->
|
|
12
|
+
<sub-menu
|
|
13
|
+
v-if="item.subMenu && item.subMenu.length > 0"
|
|
14
|
+
:menu-item="item"
|
|
15
|
+
>
|
|
16
|
+
</sub-menu>
|
|
17
|
+
<!-- module -->
|
|
18
|
+
<el-menu-item v-else :index="item.key">{{ item.name }}</el-menu-item>
|
|
19
|
+
</template>
|
|
20
|
+
</el-menu>
|
|
21
|
+
</template>
|
|
22
|
+
<template #main-content>
|
|
23
|
+
<router-view></router-view>
|
|
24
|
+
</template>
|
|
25
|
+
</sider-container>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup>
|
|
29
|
+
import { ref, onMounted, watch } from 'vue';
|
|
30
|
+
import { useRouter, useRoute } from 'vue-router';
|
|
31
|
+
import { useMenuStore } from '$elpisStore/menu.js';
|
|
32
|
+
import SiderContainer from '$elpisWidgets/sider-container/sider-container.vue';
|
|
33
|
+
import SubMenu from './complex-view/sub-menu/sub-menu.vue';
|
|
34
|
+
|
|
35
|
+
const router = useRouter();
|
|
36
|
+
const route = useRoute();
|
|
37
|
+
const menuStore = useMenuStore();
|
|
38
|
+
|
|
39
|
+
const menuList = ref([]);
|
|
40
|
+
const setMenuList = () => {
|
|
41
|
+
const menuItem = menuStore.findMenuItem({
|
|
42
|
+
key: 'key',
|
|
43
|
+
value: route.query.key
|
|
44
|
+
});
|
|
45
|
+
if(menuItem && menuItem.siderConfig && menuItem.siderConfig.menu) {
|
|
46
|
+
menuList.value = menuItem.siderConfig.menu;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const activeKey = ref('');
|
|
51
|
+
const setActiveKey = () => {
|
|
52
|
+
let siderMenuItem = menuStore.findMenuItem({
|
|
53
|
+
key: 'key',
|
|
54
|
+
value: route.query.sider_key
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// 如果首次加载 sider-view,用户未选中左侧菜单,需要默认选中第一个
|
|
58
|
+
if(!siderMenuItem){
|
|
59
|
+
const hMenuItem = menuStore.findMenuItem({
|
|
60
|
+
key: 'key',
|
|
61
|
+
value: route.query.key
|
|
62
|
+
});
|
|
63
|
+
if(hMenuItem && hMenuItem.siderConfig && hMenuItem.siderConfig.menu) {
|
|
64
|
+
const siderMenuList = hMenuItem.siderConfig.menu;
|
|
65
|
+
siderMenuItem = menuStore.findFirstMenuItem(siderMenuList); // 找出左侧菜单中的第一项
|
|
66
|
+
if(siderMenuItem) {
|
|
67
|
+
// TODO: 处理选中菜单逻辑
|
|
68
|
+
handleMenuSelect(siderMenuItem.key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
activeKey.value = siderMenuItem?.key;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// watch(() => route.query.key, () => {
|
|
76
|
+
// setMenuList();
|
|
77
|
+
// setActiveKey();
|
|
78
|
+
// })
|
|
79
|
+
// watch(() => menuStore.menuList, () => {
|
|
80
|
+
// setMenuList();
|
|
81
|
+
// setActiveKey();
|
|
82
|
+
// },{ deep: true }
|
|
83
|
+
// )
|
|
84
|
+
// onMounted(() => {
|
|
85
|
+
// setMenuList();
|
|
86
|
+
// setActiveKey();
|
|
87
|
+
// })
|
|
88
|
+
|
|
89
|
+
const onMenuSelect = (menuKey) => {
|
|
90
|
+
handleMenuSelect(menuKey);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const handleMenuSelect = (menuKey) => {
|
|
94
|
+
const menuItem = menuStore.findMenuItem({
|
|
95
|
+
key: 'key',
|
|
96
|
+
value: menuKey
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const { moduleType, key, customConfig } = menuItem;
|
|
100
|
+
|
|
101
|
+
// 如果是当前页面,不处理
|
|
102
|
+
if(key == route.query.sider_key) { return; }
|
|
103
|
+
|
|
104
|
+
const pathMap = {
|
|
105
|
+
iframe: '/iframe',
|
|
106
|
+
schema: '/schema',
|
|
107
|
+
custom: customConfig?.path,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
router.push({
|
|
111
|
+
path: `/sider${pathMap[moduleType]}`,
|
|
112
|
+
query: {
|
|
113
|
+
key: route.query.key,
|
|
114
|
+
sider_key: menuKey,
|
|
115
|
+
proj_key: route.query.proj_key
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
watch(
|
|
121
|
+
[() => route.query.key, () => menuStore.menuList],
|
|
122
|
+
() => {
|
|
123
|
+
setMenuList();
|
|
124
|
+
setActiveKey();
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
immediate: true,
|
|
128
|
+
deep: true
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
</script>
|
|
132
|
+
|
|
133
|
+
<style lang="less" scoped>
|
|
134
|
+
|
|
135
|
+
</style>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-config-provider :locale="zhCn">
|
|
3
|
+
<header-view :proj-name="projName" @menu-select="onMenuSelect">
|
|
4
|
+
<template #main-content>
|
|
5
|
+
<router-view></router-view>
|
|
6
|
+
</template>
|
|
7
|
+
</header-view>
|
|
8
|
+
</el-config-provider>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup>
|
|
12
|
+
import { ref, onMounted } from 'vue';
|
|
13
|
+
import { useRouter, useRoute } from 'vue-router';
|
|
14
|
+
import $curl from '$elpisCommon/curl.js'
|
|
15
|
+
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
|
16
|
+
import HeaderView from './complex-view/header-view/header-view.vue';
|
|
17
|
+
import { useMenuStore } from '$elpisStore/menu.js';
|
|
18
|
+
import { useProjectStore } from '$elpisStore/project.js';
|
|
19
|
+
|
|
20
|
+
const router = useRouter();
|
|
21
|
+
const route = useRoute();
|
|
22
|
+
const menuStore = useMenuStore();
|
|
23
|
+
const projectStore = useProjectStore();
|
|
24
|
+
const projName = ref('');
|
|
25
|
+
|
|
26
|
+
onMounted(() => {
|
|
27
|
+
getProjectList();
|
|
28
|
+
getProjectConfig();
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// 请求 /api/project/list 接口,并缓存到 project-store 中
|
|
32
|
+
async function getProjectList() {
|
|
33
|
+
const res = await $curl({
|
|
34
|
+
method: 'get',
|
|
35
|
+
url: '/api/project/list',
|
|
36
|
+
query: {
|
|
37
|
+
proj_key: route.query.proj_key,
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if(!res || !res.success || !res.data) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
projectStore.setProjectList(res.data);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 请求 /api/project 接口,并缓存到 menu-store 中
|
|
49
|
+
async function getProjectConfig() {
|
|
50
|
+
const res = await $curl({
|
|
51
|
+
method: 'get',
|
|
52
|
+
url: '/api/project',
|
|
53
|
+
query: {
|
|
54
|
+
proj_key: route.query.proj_key,
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if(!res || !res.success || !res.data) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { name, menu } = res.data;
|
|
63
|
+
|
|
64
|
+
projName.value = name;
|
|
65
|
+
|
|
66
|
+
menuStore.setMenuList(menu);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const onMenuSelect = (menuItem) => {
|
|
70
|
+
const { moduleType, key, customConfig } = menuItem;
|
|
71
|
+
|
|
72
|
+
// 如果是当前页面,不处理
|
|
73
|
+
if(key == route.query.key) { return; }
|
|
74
|
+
|
|
75
|
+
const pathMap = {
|
|
76
|
+
sider: '/sider',
|
|
77
|
+
iframe: '/iframe',
|
|
78
|
+
schema: '/schema',
|
|
79
|
+
custom: customConfig?.path,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
router.push({
|
|
83
|
+
path: pathMap[moduleType],
|
|
84
|
+
query: {
|
|
85
|
+
key,
|
|
86
|
+
proj_key: route.query.proj_key
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<style lang="less" scoped>
|
|
93
|
+
:deep(.el-main) {
|
|
94
|
+
padding: 0;
|
|
95
|
+
}
|
|
96
|
+
</style>
|