@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,45 @@
|
|
|
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: '/iframe',
|
|
10
|
+
component: () => import('./complex-view/iframe-view/iframe-view.vue')
|
|
11
|
+
});
|
|
12
|
+
routes.push({
|
|
13
|
+
path: '/schema',
|
|
14
|
+
component: () => import('./complex-view/schema-view/schema-view.vue')
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const siderRoutes = [
|
|
18
|
+
{
|
|
19
|
+
path: 'iframe',
|
|
20
|
+
component: () => import('./complex-view/iframe-view/iframe-view.vue')
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
path: 'schema',
|
|
24
|
+
component: () => import('./complex-view/schema-view/schema-view.vue')
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
// 侧边栏菜单路由
|
|
28
|
+
routes.push({
|
|
29
|
+
path: '/sider',
|
|
30
|
+
component: () => import('./complex-view/sider-view/sider-view.vue'),
|
|
31
|
+
children: siderRoutes
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 业务拓展路由
|
|
35
|
+
if(typeof businessDashboardRouterConfig === 'function') {
|
|
36
|
+
businessDashboardRouterConfig({ routes, siderRoutes });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 侧边栏兜底策略
|
|
40
|
+
routes.push({
|
|
41
|
+
path: '/sider/:chapters+',
|
|
42
|
+
component: () => import('./complex-view/sider-view/sider-view.vue')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
boot(dashboard, { routes });
|
|
@@ -0,0 +1,58 @@
|
|
|
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 = function(list) {
|
|
10
|
+
menuList.value = list;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 找出菜单目录
|
|
15
|
+
* @param key 搜索字段
|
|
16
|
+
* @param value 搜索值
|
|
17
|
+
* @param mList 要搜索的菜单列表
|
|
18
|
+
*/
|
|
19
|
+
const findMenuItem = ({ 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, subMenu, siderConfig } = menuItem;
|
|
25
|
+
|
|
26
|
+
if(menuItem[key] === value) {
|
|
27
|
+
return menuItem;
|
|
28
|
+
}
|
|
29
|
+
// group 类型:递归查找 subMenu
|
|
30
|
+
if(menuType === 'group' && subMenu) {
|
|
31
|
+
const mItem = findMenuItem({ key, value }, subMenu);
|
|
32
|
+
if(mItem) { return mItem; }
|
|
33
|
+
}
|
|
34
|
+
// sider 类型:递归查找 siderConfig.menu
|
|
35
|
+
if(moduleType === 'sider' && siderConfig && siderConfig.menu) {
|
|
36
|
+
const mItem = findMenuItem({ key, value }, siderConfig.menu);
|
|
37
|
+
if(mItem) { return mItem; }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 找出第一个菜单目录
|
|
46
|
+
* @param mList 菜单列表
|
|
47
|
+
*/
|
|
48
|
+
const findFirstMenuItem = (mList = menuList.value) => {
|
|
49
|
+
if(!mList || !mList[0]) { return; }
|
|
50
|
+
let firstMenuItem = mList[0];
|
|
51
|
+
if(firstMenuItem.subMenu) {
|
|
52
|
+
firstMenuItem = findMenuItem(firstMenuItem.subMenu);
|
|
53
|
+
}
|
|
54
|
+
return firstMenuItem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { menuList, setMenuList, findMenuItem, findFirstMenuItem };
|
|
58
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
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 = function(list) {
|
|
10
|
+
projectList.value = list;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return { projectList, setProjectList };
|
|
14
|
+
});
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-container class="header-container">
|
|
3
|
+
<el-header class="header">
|
|
4
|
+
<el-row type="flex" align="middle" class="header-row">
|
|
5
|
+
<!-- 左上方title -->
|
|
6
|
+
<el-row type="flex" align="middle" class="title-panel">
|
|
7
|
+
<img src="./asserts/logo.png" alt="logo.png" class="logo">
|
|
8
|
+
<el-row class="text">{{ title }}</el-row>
|
|
9
|
+
</el-row>
|
|
10
|
+
<!-- 插槽: 菜单区域 -->
|
|
11
|
+
<slot name="menu-content"></slot>
|
|
12
|
+
<!-- 右上方区域 -->
|
|
13
|
+
<el-row type="flex" align="middle" justify="end" class="setting-panel">
|
|
14
|
+
<!-- 插槽: 设置区域 -->
|
|
15
|
+
<slot name="setting-content"></slot>
|
|
16
|
+
<img src="./asserts/avatar.png" alt="avatar.png" class="avatar">
|
|
17
|
+
<el-dropdown @command="handleUserCommand">
|
|
18
|
+
<span class="username">
|
|
19
|
+
{{ userName }}<i class="el-icon-arrow-down el-icon--right"></i>
|
|
20
|
+
</span>
|
|
21
|
+
<template #dropdown>
|
|
22
|
+
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
|
|
23
|
+
</template>
|
|
24
|
+
</el-dropdown>
|
|
25
|
+
</el-row>
|
|
26
|
+
</el-row>
|
|
27
|
+
</el-header>
|
|
28
|
+
<el-main class="main-container">
|
|
29
|
+
<!-- 插槽: 核心内容填充区域 -->
|
|
30
|
+
<slot name="main-content"></slot>
|
|
31
|
+
</el-main>
|
|
32
|
+
</el-container>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script setup>
|
|
36
|
+
import { ref } from 'vue';
|
|
37
|
+
|
|
38
|
+
defineProps({
|
|
39
|
+
title: String
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const userName = ref('小江');
|
|
43
|
+
const handleUserCommand = (event) => {
|
|
44
|
+
console.log(event);
|
|
45
|
+
};
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<style lang="less" scoped>
|
|
49
|
+
.header-container {
|
|
50
|
+
height: 100%;
|
|
51
|
+
min-width: 1000px;
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
|
|
54
|
+
.header {
|
|
55
|
+
max-height: 120px;
|
|
56
|
+
border-bottom: 1px solid #E8E8E8;
|
|
57
|
+
|
|
58
|
+
.header-row {
|
|
59
|
+
height: 60px;
|
|
60
|
+
padding: 0 20px;
|
|
61
|
+
.title-panel {
|
|
62
|
+
width: 180px;
|
|
63
|
+
min-width: 180px;
|
|
64
|
+
|
|
65
|
+
.logo {
|
|
66
|
+
margin-right: 10px;
|
|
67
|
+
width: 25px;
|
|
68
|
+
height: 25px;
|
|
69
|
+
border-radius: 50%;
|
|
70
|
+
}
|
|
71
|
+
.text {
|
|
72
|
+
font-size: 15px;
|
|
73
|
+
font-weight: 500;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.setting-panel {
|
|
78
|
+
margin-left: auto;
|
|
79
|
+
min-width: 180px;
|
|
80
|
+
|
|
81
|
+
.avatar {
|
|
82
|
+
margin-right: 12px;
|
|
83
|
+
width: 30px;
|
|
84
|
+
height: 30px;
|
|
85
|
+
border-radius: 50%;
|
|
86
|
+
}
|
|
87
|
+
.username {
|
|
88
|
+
font-size: 16px;
|
|
89
|
+
font-weight: 500;
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
height: 60px;
|
|
92
|
+
line-height: 60px;
|
|
93
|
+
outline: none;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.main-container {}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
:deep(.el-header) {
|
|
104
|
+
padding: 0;
|
|
105
|
+
}
|
|
106
|
+
</style>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-row type="flex" align="middle" class="form-item">
|
|
3
|
+
<!-- label -->
|
|
4
|
+
<el-row class="item-label" justify="end">
|
|
5
|
+
<el-row
|
|
6
|
+
v-if="schema.option?.required"
|
|
7
|
+
type="flex"
|
|
8
|
+
class="required"
|
|
9
|
+
>*</el-row>
|
|
10
|
+
{{ schema.label }}
|
|
11
|
+
</el-row>
|
|
12
|
+
<!-- value -->
|
|
13
|
+
<el-row class="item-value">
|
|
14
|
+
<el-input
|
|
15
|
+
v-model="dtoValue"
|
|
16
|
+
v-bind="schema.option"
|
|
17
|
+
:placeholder="placeholder"
|
|
18
|
+
class="component"
|
|
19
|
+
:class="validTips ? 'valid-border' : ''"
|
|
20
|
+
@focus="onFocus"
|
|
21
|
+
@blur="onBlur"
|
|
22
|
+
:disabled="schema.option?.disable" />
|
|
23
|
+
</el-row>
|
|
24
|
+
<!-- 错误信息 -->
|
|
25
|
+
<el-row v-if="validTips" class="valid-tips">
|
|
26
|
+
{{ validTips }}
|
|
27
|
+
</el-row>
|
|
28
|
+
</el-row>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script setup>
|
|
32
|
+
import { ref, toRefs, watch, inject, onMounted } from 'vue';
|
|
33
|
+
const ajv = inject('ajv');
|
|
34
|
+
|
|
35
|
+
const props = defineProps({
|
|
36
|
+
schemaKey: String,
|
|
37
|
+
schema: Object,
|
|
38
|
+
model: String,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const { schemaKey, schema } = props;
|
|
42
|
+
const { model } = toRefs(props);
|
|
43
|
+
|
|
44
|
+
const name = ref('input');
|
|
45
|
+
const dtoValue = ref();
|
|
46
|
+
const placeholder = ref('');
|
|
47
|
+
const validTips = ref(null);
|
|
48
|
+
|
|
49
|
+
const initData = () => {
|
|
50
|
+
dtoValue.value = model.value ?? schema.option?.default;
|
|
51
|
+
validTips.value = null;
|
|
52
|
+
|
|
53
|
+
const { minLength, maxLength, pattern } = schema;
|
|
54
|
+
const ruleList = [];
|
|
55
|
+
if(schema.option?.placeholder) {
|
|
56
|
+
ruleList.push(schema.option.placeholder);
|
|
57
|
+
}
|
|
58
|
+
if(minLength) {
|
|
59
|
+
ruleList.push(`最小长度 ${minLength}`);
|
|
60
|
+
}
|
|
61
|
+
if(maxLength) {
|
|
62
|
+
ruleList.push(`最大长度 ${maxLength}`);
|
|
63
|
+
}
|
|
64
|
+
if(pattern) {
|
|
65
|
+
ruleList.push(`格式: ${pattern}`);
|
|
66
|
+
}
|
|
67
|
+
placeholder.value = ruleList.join('|');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
onMounted(() => {
|
|
71
|
+
initData();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
watch([ model, schema ], () => {
|
|
75
|
+
initData();
|
|
76
|
+
}, { deep: true });
|
|
77
|
+
|
|
78
|
+
const validate = () => {
|
|
79
|
+
validTips.value = null;
|
|
80
|
+
|
|
81
|
+
const { type } = schema;
|
|
82
|
+
|
|
83
|
+
// 校验是否必填
|
|
84
|
+
if (schema.option?.required && !dtoValue.value) {
|
|
85
|
+
validTips.value = '不能为空';
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ajv 校验 schema
|
|
90
|
+
if(dtoValue.value) {
|
|
91
|
+
const validate = ajv.compile(schema);
|
|
92
|
+
const valid = validate(dtoValue.value);
|
|
93
|
+
if (!valid && validate.errors && validate.errors[0]) {
|
|
94
|
+
const { keyword, params } = validate.errors[0];
|
|
95
|
+
if(keyword === 'type') {
|
|
96
|
+
validTips.value = `类型必须为 ${type}`;
|
|
97
|
+
} else if (keyword === 'maxLength') {
|
|
98
|
+
validTips.value = `最大长度应为 ${params.limit}`;
|
|
99
|
+
} else if (keyword === 'minLength') {
|
|
100
|
+
validTips.value = `最小长度应为 ${params.limit}`;
|
|
101
|
+
} else if (keyword === 'pattern') {
|
|
102
|
+
validTips.value = '格式不正确';
|
|
103
|
+
} else {
|
|
104
|
+
console.warn('未处理的校验错误', validate.errors[0]);
|
|
105
|
+
validTips.value = '不符合要求';
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const getValue = () => {
|
|
114
|
+
return dtoValue.value !== undefined ? { [schemaKey]: dtoValue.value } : {};
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const onFocus = () => {
|
|
118
|
+
validTips.value = null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const onBlur = () => {
|
|
122
|
+
validate();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
defineExpose({
|
|
126
|
+
name,
|
|
127
|
+
validate,
|
|
128
|
+
getValue,
|
|
129
|
+
});
|
|
130
|
+
</script>
|
|
131
|
+
|
|
132
|
+
<style lang="less" scoped>
|
|
133
|
+
|
|
134
|
+
</style>
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-row type="flex" align="middle" class="form-item">
|
|
3
|
+
<!-- label -->
|
|
4
|
+
<el-row class="item-label" justify="end">
|
|
5
|
+
<el-row
|
|
6
|
+
v-if="schema.option?.required"
|
|
7
|
+
type="flex"
|
|
8
|
+
class="required"
|
|
9
|
+
>*</el-row>
|
|
10
|
+
{{ schema.label }}
|
|
11
|
+
</el-row>
|
|
12
|
+
<!-- value -->
|
|
13
|
+
<el-row class="item-value">
|
|
14
|
+
<el-input-number
|
|
15
|
+
v-model="dtoValue"
|
|
16
|
+
v-bind="schema.option"
|
|
17
|
+
:controls="false"
|
|
18
|
+
:placeholder="placeholder"
|
|
19
|
+
class="component"
|
|
20
|
+
:class="validTips ? 'valid-border' : ''"
|
|
21
|
+
@focus="onFocus"
|
|
22
|
+
@blur="onBlur"
|
|
23
|
+
:disabled="schema.option?.disable" />
|
|
24
|
+
</el-row>
|
|
25
|
+
<!-- 错误信息 -->
|
|
26
|
+
<el-row v-if="validTips" class="valid-tips">
|
|
27
|
+
{{ validTips }}
|
|
28
|
+
</el-row>
|
|
29
|
+
</el-row>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script setup>
|
|
33
|
+
import { ref, toRefs, watch, inject, onMounted } from 'vue';
|
|
34
|
+
const ajv = inject('ajv');
|
|
35
|
+
|
|
36
|
+
const props = defineProps({
|
|
37
|
+
schemaKey: String,
|
|
38
|
+
schema: Object,
|
|
39
|
+
model: Number,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const { schemaKey, schema } = props;
|
|
43
|
+
const { model } = toRefs(props);
|
|
44
|
+
|
|
45
|
+
const name = ref('inputNumber');
|
|
46
|
+
const dtoValue = ref();
|
|
47
|
+
const placeholder = ref('');
|
|
48
|
+
const validTips = ref(null);
|
|
49
|
+
|
|
50
|
+
const initData = () => {
|
|
51
|
+
dtoValue.value = model.value ?? schema.option?.default;
|
|
52
|
+
validTips.value = null;
|
|
53
|
+
|
|
54
|
+
const { minimum, maximum } = schema;
|
|
55
|
+
const ruleList = [];
|
|
56
|
+
if(schema.option?.placeholder) {
|
|
57
|
+
ruleList.push(schema.option.placeholder);
|
|
58
|
+
}
|
|
59
|
+
if(minimum !== undefined) {
|
|
60
|
+
ruleList.push(`最小值 ${minimum}`);
|
|
61
|
+
}
|
|
62
|
+
if(maximum !== undefined) {
|
|
63
|
+
ruleList.push(`最大值 ${maximum}`);
|
|
64
|
+
}
|
|
65
|
+
placeholder.value = ruleList.join('|');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
onMounted(() => {
|
|
69
|
+
initData();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
watch([ model, schema ], () => {
|
|
73
|
+
initData();
|
|
74
|
+
}, { deep: true });
|
|
75
|
+
|
|
76
|
+
const isEmptyValue = (value) => {
|
|
77
|
+
return value === undefined || value === null || value === '';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const validate = () => {
|
|
81
|
+
validTips.value = null;
|
|
82
|
+
|
|
83
|
+
const { type } = schema;
|
|
84
|
+
|
|
85
|
+
// 校验是否必填
|
|
86
|
+
if (schema.option?.required && isEmptyValue(dtoValue.value)) {
|
|
87
|
+
validTips.value = '不能为空';
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ajv 校验 schema
|
|
92
|
+
if(!isEmptyValue(dtoValue.value)) {
|
|
93
|
+
const validate = ajv.compile(schema);
|
|
94
|
+
const valid = validate(dtoValue.value);
|
|
95
|
+
if (!valid && validate.errors && validate.errors[0]) {
|
|
96
|
+
const { keyword, params } = validate.errors[0];
|
|
97
|
+
if(keyword === 'type') {
|
|
98
|
+
validTips.value = `类型必须为 ${type}`;
|
|
99
|
+
} else if (keyword === 'maximum') {
|
|
100
|
+
validTips.value = `最大值应为 ${params.limit}`;
|
|
101
|
+
} else if (keyword === 'minimum') {
|
|
102
|
+
validTips.value = `最小值应为 ${params.limit}`;
|
|
103
|
+
} else {
|
|
104
|
+
console.warn('未处理的校验错误', validate.errors[0]);
|
|
105
|
+
validTips.value = '不符合要求';
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const getValue = () => {
|
|
114
|
+
return dtoValue.value !== undefined ? { [schemaKey]: dtoValue.value } : {};
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const onFocus = () => {
|
|
118
|
+
validTips.value = null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const onBlur = () => {
|
|
122
|
+
validate();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
defineExpose({
|
|
126
|
+
name,
|
|
127
|
+
validate,
|
|
128
|
+
getValue,
|
|
129
|
+
});
|
|
130
|
+
</script>
|
|
131
|
+
|
|
132
|
+
<style lang="less" scoped>
|
|
133
|
+
:deep(.el-input-number .el-input__inner) {
|
|
134
|
+
text-align: left;
|
|
135
|
+
}
|
|
136
|
+
</style>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-row type="flex" align="middle" class="form-item">
|
|
3
|
+
<!-- label -->
|
|
4
|
+
<el-row class="item-label" justify="end">
|
|
5
|
+
<el-row
|
|
6
|
+
v-if="schema.option?.required"
|
|
7
|
+
type="flex"
|
|
8
|
+
class="required"
|
|
9
|
+
>*</el-row>
|
|
10
|
+
{{ schema.label }}
|
|
11
|
+
</el-row>
|
|
12
|
+
<!-- value -->
|
|
13
|
+
<el-row class="item-value">
|
|
14
|
+
<el-select
|
|
15
|
+
v-model="dtoValue"
|
|
16
|
+
v-bind="schema.option"
|
|
17
|
+
class="component"
|
|
18
|
+
:class="validTips ? 'valid-border' : ''"
|
|
19
|
+
@change="onChange"
|
|
20
|
+
>
|
|
21
|
+
<el-option
|
|
22
|
+
v-for="item in schema.option?.enumList"
|
|
23
|
+
:key="item.value"
|
|
24
|
+
:label="item.label"
|
|
25
|
+
:value="item.value"
|
|
26
|
+
></el-option>
|
|
27
|
+
</el-select>
|
|
28
|
+
</el-row>
|
|
29
|
+
<!-- 错误信息 -->
|
|
30
|
+
<el-row v-if="validTips" class="valid-tips">
|
|
31
|
+
{{ validTips }}
|
|
32
|
+
</el-row>
|
|
33
|
+
</el-row>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script setup>
|
|
37
|
+
import { ref, toRefs, watch, inject, onMounted } from 'vue';
|
|
38
|
+
const ajv = inject('ajv');
|
|
39
|
+
|
|
40
|
+
const props = defineProps({
|
|
41
|
+
schemaKey: String,
|
|
42
|
+
schema: Object,
|
|
43
|
+
model: null, // 任意类型
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const { schemaKey, schema } = props;
|
|
47
|
+
const { model } = toRefs(props);
|
|
48
|
+
|
|
49
|
+
const name = ref('select');
|
|
50
|
+
const dtoValue = ref();
|
|
51
|
+
const validTips = ref(null);
|
|
52
|
+
|
|
53
|
+
const initData = () => {
|
|
54
|
+
dtoValue.value = model.value ?? schema.option?.default;
|
|
55
|
+
validTips.value = null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onMounted(() => {
|
|
59
|
+
initData();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
watch([ model, schema ], () => {
|
|
63
|
+
initData();
|
|
64
|
+
}, { deep: true });
|
|
65
|
+
|
|
66
|
+
const validate = () => {
|
|
67
|
+
validTips.value = null;
|
|
68
|
+
|
|
69
|
+
// 校验是否必填
|
|
70
|
+
if (schema.option?.required && !dtoValue.value) {
|
|
71
|
+
validTips.value = '不能为空';
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ajv 校验 schema
|
|
76
|
+
if(dtoValue.value) {
|
|
77
|
+
let dtoEnum = [];
|
|
78
|
+
if(schema.option?.enumList) {
|
|
79
|
+
dtoEnum = schema.option?.enumList.map(item => item.value);
|
|
80
|
+
}
|
|
81
|
+
const validate = ajv.compile({
|
|
82
|
+
schema,
|
|
83
|
+
...{ enum: dtoEnum },
|
|
84
|
+
});
|
|
85
|
+
const valid = validate(dtoValue.value);
|
|
86
|
+
if (!valid && validate.errors && validate.errors[0]) {
|
|
87
|
+
if(validate.errors[0].keyword === 'enum') {
|
|
88
|
+
validTips.value = '取值超出枚举范围';
|
|
89
|
+
} else {
|
|
90
|
+
console.warn('未处理的校验错误', validate.errors[0]);
|
|
91
|
+
validTips.value = '不符合要求';
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const getValue = () => {
|
|
100
|
+
return dtoValue.value !== undefined ? { [schemaKey]: dtoValue.value } : {};
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const onChange = () => {
|
|
104
|
+
validate();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
defineExpose({
|
|
108
|
+
name,
|
|
109
|
+
validate,
|
|
110
|
+
getValue,
|
|
111
|
+
});
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
<style lang="less" scoped>
|
|
115
|
+
|
|
116
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import input from "./complex-view/input/input.vue";
|
|
2
|
+
import inputNumber from "./complex-view/input-number/input-number.vue";
|
|
3
|
+
import select from "./complex-view/select/select.vue";
|
|
4
|
+
|
|
5
|
+
// 业务拓展 form-item 配置
|
|
6
|
+
import BusinessFormItemConfig from '$businessFormItemConfig';
|
|
7
|
+
|
|
8
|
+
const FormItemConfig = {
|
|
9
|
+
input: {
|
|
10
|
+
component: input
|
|
11
|
+
},
|
|
12
|
+
inputNumber: {
|
|
13
|
+
component: inputNumber
|
|
14
|
+
},
|
|
15
|
+
select: {
|
|
16
|
+
component: select
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default {
|
|
21
|
+
...FormItemConfig,
|
|
22
|
+
...BusinessFormItemConfig
|
|
23
|
+
};
|