@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.
Files changed (86) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc +55 -0
  3. package/.tmp-check.js +47 -0
  4. package/README.md +287 -0
  5. package/app/controller/base.js +37 -0
  6. package/app/controller/project.js +76 -0
  7. package/app/controller/view.js +17 -0
  8. package/app/extend/logger.js +36 -0
  9. package/app/middleware/api-params-verify.js +78 -0
  10. package/app/middleware/api-sign-verify.js +31 -0
  11. package/app/middleware/error-handler.js +36 -0
  12. package/app/middleware/project-handler.js +26 -0
  13. package/app/middleware.js +69 -0
  14. package/app/pages/asserts/custom.css +10 -0
  15. package/app/pages/boot.js +44 -0
  16. package/app/pages/common/curl.js +76 -0
  17. package/app/pages/common/utils.js +2 -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 +131 -0
  20. package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +29 -0
  21. package/app/pages/dashboard/complex-view/schema-view/complex-view/search-pannel/search-pannel.vue +36 -0
  22. package/app/pages/dashboard/complex-view/schema-view/complex-view/table-pannel/table-pannel.vue +125 -0
  23. package/app/pages/dashboard/complex-view/schema-view/components/component-config.js +19 -0
  24. package/app/pages/dashboard/complex-view/schema-view/components/create-form/create-form.vue +119 -0
  25. package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +131 -0
  26. package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +149 -0
  27. package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +126 -0
  28. package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +111 -0
  29. package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +28 -0
  30. package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +138 -0
  31. package/app/pages/dashboard/dashboard.vue +121 -0
  32. package/app/pages/dashboard/entry.dashboard.js +52 -0
  33. package/app/pages/dashboard/router.js +3 -0
  34. package/app/pages/dashboard/todo/todo.vue +10 -0
  35. package/app/pages/store/index.js +3 -0
  36. package/app/pages/store/menu.js +71 -0
  37. package/app/pages/store/project.js +15 -0
  38. package/app/pages/widgets/header-container/asserts/avatar.png +0 -0
  39. package/app/pages/widgets/header-container/asserts/logo.png +0 -0
  40. package/app/pages/widgets/header-container/header-container.vue +118 -0
  41. package/app/pages/widgets/schema-form/complex-view/input/input.vue +165 -0
  42. package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +164 -0
  43. package/app/pages/widgets/schema-form/complex-view/select/select.vue +138 -0
  44. package/app/pages/widgets/schema-form/form-item-config.js +20 -0
  45. package/app/pages/widgets/schema-form/schema-form.vue +154 -0
  46. package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +49 -0
  47. package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +65 -0
  48. package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +44 -0
  49. package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +50 -0
  50. package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +134 -0
  51. package/app/pages/widgets/schema-search-bar/search-item-config.js +27 -0
  52. package/app/pages/widgets/schema-table/schema-table.vue +277 -0
  53. package/app/pages/widgets/sider-container/sider-container.vue +42 -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 +7 -0
  57. package/app/router/view.js +7 -0
  58. package/app/router-schema/project.js +34 -0
  59. package/app/service/base.js +10 -0
  60. package/app/service/project.js +43 -0
  61. package/app/view/entry.tpl +31 -0
  62. package/app/webpack/config/webpack.base.js +224 -0
  63. package/app/webpack/config/webpack.dev.js +53 -0
  64. package/app/webpack/config/webpack.prod.js +104 -0
  65. package/app/webpack/dev.js +54 -0
  66. package/app/webpack/libs/blank.js +1 -0
  67. package/app/webpack/prod.js +17 -0
  68. package/config/config.default.js +3 -0
  69. package/docs/dashboard-model.js +150 -0
  70. package/elpis-core/env.js +21 -0
  71. package/elpis-core/index.js +82 -0
  72. package/elpis-core/loader/config.js +58 -0
  73. package/elpis-core/loader/controller.js +55 -0
  74. package/elpis-core/loader/extend.js +42 -0
  75. package/elpis-core/loader/middleware.js +50 -0
  76. package/elpis-core/loader/router-schema.js +28 -0
  77. package/elpis-core/loader/router.js +42 -0
  78. package/elpis-core/loader/service.js +51 -0
  79. package/index.js +37 -0
  80. package/logs/application.log +50 -0
  81. package/logs/application.log.2026-06-13 +159 -0
  82. package/model/index.js +119 -0
  83. package/package.json +88 -0
  84. package/scripts/generate-cover.py +93 -0
  85. package/scripts/generate-diagrams.js +80 -0
  86. 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>