@codertqy/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 +2 -0
- package/.eslintrc +55 -0
- package/README.md +218 -0
- package/app/controller/base.js +40 -0
- package/app/controller/project.js +81 -0
- package/app/controller/view.js +22 -0
- package/app/extend/logger.js +43 -0
- package/app/middleware/api-params-verify.js +73 -0
- package/app/middleware/api-sign-verify.js +49 -0
- package/app/middleware/error-handler.js +31 -0
- package/app/middleware/project-handler.js +26 -0
- package/app/middleware.js +44 -0
- package/app/pages/assets/custom.css +14 -0
- package/app/pages/boot.js +56 -0
- package/app/pages/common/curl.js +84 -0
- package/app/pages/common/index.css +3 -0
- package/app/pages/common/utils.js +1 -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 +126 -0
- package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +45 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/search-panel/search-panel.vue +35 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/table-panel/table-panel.vue +120 -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 +122 -0
- package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +161 -0
- package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +95 -0
- package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +19 -0
- package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +135 -0
- package/app/pages/dashboard/dashboard.vue +86 -0
- package/app/pages/dashboard/entry.dashboard.js +48 -0
- package/app/pages/store/index.js +3 -0
- package/app/pages/store/menu.js +68 -0
- package/app/pages/store/project.js +12 -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 +107 -0
- package/app/pages/widgets/schema-form/complex-view/input/input.vue +138 -0
- package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +140 -0
- package/app/pages/widgets/schema-form/complex-view/select/select.vue +122 -0
- package/app/pages/widgets/schema-form/form-item-config.js +20 -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 +51 -0
- package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +63 -0
- package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +41 -0
- package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +49 -0
- package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +126 -0
- package/app/pages/widgets/schema-search-bar/search-item-config.js +22 -0
- package/app/pages/widgets/schema-table/schema-table.vue +259 -0
- package/app/pages/widgets/sider-container/sider-container.vue +27 -0
- package/app/public/output/entry.page1.tpl +40 -0
- package/app/public/output/entry.page2.tpl +11 -0
- package/app/public/static/logo.png +0 -0
- package/app/public/static/normalize.css +239 -0
- package/app/router/project.js +22 -0
- package/app/router/view.js +17 -0
- package/app/router-schema/project.js +34 -0
- package/app/service/base.js +15 -0
- package/app/service/project.js +59 -0
- package/app/view/entry.tpl +25 -0
- package/app/webpack/config/webpack.base.js +280 -0
- package/app/webpack/config/webpack.dev.js +57 -0
- package/app/webpack/config/webpack.prod.js +127 -0
- package/app/webpack/dev.js +63 -0
- package/app/webpack/libs/blank.js +1 -0
- package/app/webpack/prod.js +22 -0
- package/config/config.beta.js +1 -0
- package/config/config.default.js +3 -0
- package/config/config.prod.js +1 -0
- package/elpis-core/env.js +27 -0
- package/elpis-core/index.js +98 -0
- package/elpis-core/loader/config.js +58 -0
- package/elpis-core/loader/controller.js +93 -0
- package/elpis-core/loader/extend.js +63 -0
- package/elpis-core/loader/middleware.js +84 -0
- package/elpis-core/loader/router-schema.js +56 -0
- package/elpis-core/loader/router.js +50 -0
- package/elpis-core/loader/service.js +85 -0
- package/index.js +38 -0
- package/model/index.js +129 -0
- package/package.json +92 -0
- package/test/controller/project.test.js +214 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-config-provider :locale="zhCn">
|
|
3
|
+
<HeaderView :projName="projName" @menu-select="onMenuSelect">
|
|
4
|
+
<!-- 将内容传递给 header-view 并且header-view是一个过渡的,又通过slot透传给 header-container-->
|
|
5
|
+
<template #main-content>
|
|
6
|
+
<router-view></router-view>
|
|
7
|
+
</template>
|
|
8
|
+
</HeaderView>
|
|
9
|
+
</el-config-provider>
|
|
10
|
+
</template>
|
|
11
|
+
<script setup>
|
|
12
|
+
import zhCn from "element-plus/es/locale/lang/zh-cn";
|
|
13
|
+
import HeaderView from "./complex-view/header-view/header-view.vue";
|
|
14
|
+
import $curl from "$elpisCommon/curl";
|
|
15
|
+
import { useMenuStore } from "$elpisStore/menu.js";
|
|
16
|
+
import { useProjectStore } from "$elpisStore/project.js";
|
|
17
|
+
import { onMounted, ref } from "vue";
|
|
18
|
+
import { useRouter, useRoute } from "vue-router";
|
|
19
|
+
const router = useRouter();
|
|
20
|
+
const route = useRoute();
|
|
21
|
+
const menuStore = useMenuStore();
|
|
22
|
+
const projectStore = useProjectStore();
|
|
23
|
+
|
|
24
|
+
const projName = ref("");
|
|
25
|
+
|
|
26
|
+
onMounted(() => {
|
|
27
|
+
getProjectList();
|
|
28
|
+
getProject();
|
|
29
|
+
});
|
|
30
|
+
// 请求 /api/project/list 接口, 并缓存到 project-store 中
|
|
31
|
+
const getProjectList = async () => {
|
|
32
|
+
const res = await $curl({
|
|
33
|
+
method: "get",
|
|
34
|
+
url: "/api/project/list",
|
|
35
|
+
query: {
|
|
36
|
+
proj_key: route.query.proj_key,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
if (!res || !res.success || !res.data) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
projectStore.setProjectList(res.data);
|
|
43
|
+
};
|
|
44
|
+
// 请求 /api/project 接口, 并缓存到 menu-store 中
|
|
45
|
+
const getProject = async () => {
|
|
46
|
+
const res = await $curl({
|
|
47
|
+
method: "get",
|
|
48
|
+
url: "/api/project",
|
|
49
|
+
query: {
|
|
50
|
+
proj_key: route.query.proj_key,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
if (!res || !res.success || !res.data) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const { name, menu } = res.data;
|
|
57
|
+
projName.value = name;
|
|
58
|
+
menuStore.setMenuList(menu);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// 点击菜单回调方法
|
|
62
|
+
const onMenuSelect = (menuItem) => {
|
|
63
|
+
const { moduleType, key, customConfig } = menuItem;
|
|
64
|
+
|
|
65
|
+
// 如果是当前页面,不处理
|
|
66
|
+
if (key === route.query.key) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// 指定不同的模块、展示不同的页面
|
|
70
|
+
const pathMap = {
|
|
71
|
+
sider: "/sider",
|
|
72
|
+
iframe: "/iframe",
|
|
73
|
+
schema: "/schema",
|
|
74
|
+
custom: customConfig?.path,
|
|
75
|
+
};
|
|
76
|
+
router.push({
|
|
77
|
+
path: `/view/dashboard${pathMap[moduleType]}`,
|
|
78
|
+
query: {
|
|
79
|
+
key,
|
|
80
|
+
proj_key: route.query.proj_key,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<style scoped lang="less"></style>
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
{
|
|
10
|
+
path: "/view/dashboard/iframe",
|
|
11
|
+
component: () => import("./complex-view/iframe-view/iframe-view.vue"),
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
path: "/view/dashboard/schema",
|
|
15
|
+
component: () => import("./complex-view/schema-view/schema-view.vue"),
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const siderRoutes = [
|
|
20
|
+
{
|
|
21
|
+
path: "iframe",
|
|
22
|
+
component: () => import("./complex-view/iframe-view/iframe-view.vue"),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
path: "schema",
|
|
26
|
+
component: () => import("./complex-view/schema-view/schema-view.vue"),
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// 侧边栏菜单路由
|
|
31
|
+
routes.push({
|
|
32
|
+
path: "/view/dashboard/sider",
|
|
33
|
+
component: () => import("./complex-view/sider-view/sider-view.vue"),
|
|
34
|
+
children: siderRoutes,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// 业务扩展路由
|
|
38
|
+
if(typeof businessDashboardRouterConfig === "function"){
|
|
39
|
+
businessDashboardRouterConfig({ routes, siderRoutes });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 侧边栏 兜底 路由策略
|
|
43
|
+
routes.push({
|
|
44
|
+
path: "/view/dashboard/sider/:chapters*",
|
|
45
|
+
component: () => import("./complex-view/sider-view/sider-view.vue"),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
boot(dashboard, { routes });
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { defineStore } from "pinia";
|
|
2
|
+
import { ref } from "vue";
|
|
3
|
+
|
|
4
|
+
export const useMenuStore = defineStore("menu", () => {
|
|
5
|
+
// 菜单列表
|
|
6
|
+
const menuList = ref([]);
|
|
7
|
+
// 设置菜单列表
|
|
8
|
+
const setMenuList = (list) => {
|
|
9
|
+
menuList.value = list;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* 找出菜单目录
|
|
13
|
+
* @param {*} key 搜索字段与 value搜索值
|
|
14
|
+
* @param {*} mList 要搜索的菜单列表
|
|
15
|
+
*/
|
|
16
|
+
const findMenuItem = ({ key, value }, mList = menuList.value) => {
|
|
17
|
+
for (let i = 0; i < mList.length; i++) {
|
|
18
|
+
const menuItem = mList[i];
|
|
19
|
+
if (!menuItem) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const { menuType, moduleType } = menuItem;
|
|
23
|
+
|
|
24
|
+
if (menuItem[key] === value) {
|
|
25
|
+
return menuItem;
|
|
26
|
+
}
|
|
27
|
+
if (menuType === "group" && menuItem.subMenu) {
|
|
28
|
+
// 递归
|
|
29
|
+
const mItem = findMenuItem({ key, value }, menuItem.subMenu);
|
|
30
|
+
if (mItem) {
|
|
31
|
+
return mItem;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
moduleType === "sider" &&
|
|
37
|
+
menuItem.siderConfig &&
|
|
38
|
+
menuItem.siderConfig.menu
|
|
39
|
+
) {
|
|
40
|
+
const mItem = findMenuItem({ key, value }, menuItem.siderConfig.menu);
|
|
41
|
+
if (mItem) {
|
|
42
|
+
return mItem;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 找出左侧侧边栏第一个菜单目录
|
|
50
|
+
* @param {Array} mList 菜单列表
|
|
51
|
+
* @returns
|
|
52
|
+
*/
|
|
53
|
+
const findFirstMenuItem = (mList = menuList.value) => {
|
|
54
|
+
// 不存在直接return
|
|
55
|
+
if (!mList || !mList[0]) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// 存在,并且第一项存在subMenu数组,则继续进行递归获取里面数据的第一项
|
|
59
|
+
let firstMenuItem = mList[0];
|
|
60
|
+
if (firstMenuItem.subMenu) {
|
|
61
|
+
// 递归调用自己,直到找出第一项
|
|
62
|
+
firstMenuItem = findFirstMenuItem(firstMenuItem.subMenu);
|
|
63
|
+
}
|
|
64
|
+
return firstMenuItem;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return { menuList, setMenuList, findMenuItem, findFirstMenuItem };
|
|
68
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineStore } from "pinia";
|
|
2
|
+
import { ref } from "vue";
|
|
3
|
+
|
|
4
|
+
export const useProjectStore = defineStore("project", () => {
|
|
5
|
+
// 菜单列表
|
|
6
|
+
const projectList = ref([]);
|
|
7
|
+
// 设置菜单列表
|
|
8
|
+
const setProjectList = (list) => {
|
|
9
|
+
projectList.value = list;
|
|
10
|
+
};
|
|
11
|
+
return { projectList, setProjectList };
|
|
12
|
+
});
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-container class="header-container">
|
|
3
|
+
<el-header class="header">
|
|
4
|
+
<el-row type="flex" align="middle" class="header-row">
|
|
5
|
+
<!-- 左上方 标题与logo -->
|
|
6
|
+
<el-row type="flex" align="middle" class="title-panel">
|
|
7
|
+
<img src="./asserts//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" class="avatar" alt="" />
|
|
17
|
+
<el-dropdown @command="handlUserCommand">
|
|
18
|
+
<span class="username">
|
|
19
|
+
{{ userName }}
|
|
20
|
+
<el-icon class="el-icon--right">
|
|
21
|
+
<arrow-down />
|
|
22
|
+
</el-icon>
|
|
23
|
+
</span>
|
|
24
|
+
<template #dropdown>
|
|
25
|
+
<el-dropdown-menu>
|
|
26
|
+
<el-dropdown-item class="logout">退出登录</el-dropdown-item>
|
|
27
|
+
</el-dropdown-menu>
|
|
28
|
+
</template>
|
|
29
|
+
</el-dropdown>
|
|
30
|
+
</el-row>
|
|
31
|
+
</el-row>
|
|
32
|
+
</el-header>
|
|
33
|
+
|
|
34
|
+
<el-main class="main-container">
|
|
35
|
+
<!-- 插槽 外部拓展填充区域 -->
|
|
36
|
+
<slot name="main-content"></slot>
|
|
37
|
+
</el-main>
|
|
38
|
+
</el-container>
|
|
39
|
+
</template>
|
|
40
|
+
<script setup>
|
|
41
|
+
import { ref } from "vue";
|
|
42
|
+
|
|
43
|
+
defineProps({
|
|
44
|
+
title: String,
|
|
45
|
+
});
|
|
46
|
+
const userName = ref("codertqy");
|
|
47
|
+
const handlUserCommand = (command) => {
|
|
48
|
+
if (command === "logout") {
|
|
49
|
+
console.log("logout");
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<style scoped lang="less">
|
|
55
|
+
.header-container {
|
|
56
|
+
height: 100%;
|
|
57
|
+
min-width: 1000px;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
.header {
|
|
60
|
+
max-height: 120px;
|
|
61
|
+
border-bottom: 1px solid #e8e8e8;
|
|
62
|
+
.header-row {
|
|
63
|
+
height: 60px;
|
|
64
|
+
padding: 0 20px;
|
|
65
|
+
// 左侧
|
|
66
|
+
.title-panel {
|
|
67
|
+
width: 180px;
|
|
68
|
+
min-width: 180px;
|
|
69
|
+
.logo {
|
|
70
|
+
margin-right: 10px;
|
|
71
|
+
width: 25px;
|
|
72
|
+
height: 25px;
|
|
73
|
+
border-radius: 50%;
|
|
74
|
+
}
|
|
75
|
+
.text {
|
|
76
|
+
font-size: 15px;
|
|
77
|
+
font-weight: 500;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// 右侧
|
|
81
|
+
.setting-panel {
|
|
82
|
+
margin-left: auto;
|
|
83
|
+
min-width: 180px;
|
|
84
|
+
.avatar {
|
|
85
|
+
margin-right: 12px;
|
|
86
|
+
width: 30px;
|
|
87
|
+
height: 30px;
|
|
88
|
+
border-radius: 50%;
|
|
89
|
+
}
|
|
90
|
+
.username {
|
|
91
|
+
font-size: 16px;
|
|
92
|
+
font-weight: 500;
|
|
93
|
+
cursor: pointer;
|
|
94
|
+
height: 60px;
|
|
95
|
+
line-height: 60px;
|
|
96
|
+
outline: none;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
.main-container {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
:deep(.el-header) {
|
|
105
|
+
padding: 0;
|
|
106
|
+
}
|
|
107
|
+
</style>
|
|
@@ -0,0 +1,138 @@
|
|
|
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 v-if="schema.option?.required" type="flex" class="required"
|
|
6
|
+
>*</el-row
|
|
7
|
+
>
|
|
8
|
+
{{ schema.label }}
|
|
9
|
+
</el-row>
|
|
10
|
+
<!-- value -->
|
|
11
|
+
<el-row class="item-value">
|
|
12
|
+
<el-input
|
|
13
|
+
v-model="dtoValue"
|
|
14
|
+
v-bind="schema.option"
|
|
15
|
+
class="component"
|
|
16
|
+
:placeholder="placeholder"
|
|
17
|
+
:class="validTips ? 'valid-border' : ''"
|
|
18
|
+
@focus="onFocus"
|
|
19
|
+
@blur="onBlur"
|
|
20
|
+
></el-input>
|
|
21
|
+
</el-row>
|
|
22
|
+
<!-- 错误信息 -->
|
|
23
|
+
<el-row v-if="validTips" class="valid-tips">{{ validTips }}</el-row>
|
|
24
|
+
</el-row>
|
|
25
|
+
</template>
|
|
26
|
+
<script setup>
|
|
27
|
+
import { ref, toRefs, watch, inject, onMounted } from "vue";
|
|
28
|
+
const ajv = inject("ajv");
|
|
29
|
+
|
|
30
|
+
const props = defineProps({
|
|
31
|
+
schemaKey: {
|
|
32
|
+
type: String,
|
|
33
|
+
},
|
|
34
|
+
schema: {
|
|
35
|
+
type: Object,
|
|
36
|
+
},
|
|
37
|
+
model: {
|
|
38
|
+
type: String,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
const { schemaKey, schema } = props;
|
|
42
|
+
const { model } = toRefs(props);
|
|
43
|
+
|
|
44
|
+
const dtoValue = ref();
|
|
45
|
+
const validTips = ref(null);
|
|
46
|
+
const placeholder = ref("");
|
|
47
|
+
const name = ref("input");
|
|
48
|
+
const initData = () => {
|
|
49
|
+
dtoValue.value = model.value ?? schema.option.default;
|
|
50
|
+
validTips.value = null;
|
|
51
|
+
|
|
52
|
+
// 将规则中相关的字段提取出来
|
|
53
|
+
const { minLength, maxLength, pattern } = schema;
|
|
54
|
+
const ruleList = [];
|
|
55
|
+
// 用户自定义placeholder传递过来的
|
|
56
|
+
if (schema.option?.placeholder) {
|
|
57
|
+
ruleList.push(schema.option.placeholder);
|
|
58
|
+
}
|
|
59
|
+
if (minLength) {
|
|
60
|
+
ruleList.push(`最小长度:${minLength}`);
|
|
61
|
+
}
|
|
62
|
+
if (maxLength) {
|
|
63
|
+
ruleList.push(`最大长度:${maxLength}`);
|
|
64
|
+
}
|
|
65
|
+
if (pattern) {
|
|
66
|
+
ruleList.push(`格式:${pattern}`);
|
|
67
|
+
}
|
|
68
|
+
placeholder.value = ruleList.join("|");
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
onMounted(() => {
|
|
72
|
+
initData();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
watch(
|
|
76
|
+
[model, schema],
|
|
77
|
+
() => {
|
|
78
|
+
initData();
|
|
79
|
+
},
|
|
80
|
+
{ deep: true },
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const validate = () => {
|
|
84
|
+
validTips.value = null;
|
|
85
|
+
const { type } = schema;
|
|
86
|
+
// 校验是否必填
|
|
87
|
+
if (schema.option?.required && !dtoValue.value) {
|
|
88
|
+
validTips.value = "不能为空";
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
// 校验 schema
|
|
92
|
+
if (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 === "maxLength") {
|
|
100
|
+
validTips.value = `最大长度应为${params.limit}`;
|
|
101
|
+
} else if (keyword === "minLength") {
|
|
102
|
+
validTips.value = `最小长度应为${params.limit}`;
|
|
103
|
+
} else if (keyword === "pattern") {
|
|
104
|
+
validTips.value = `格式不正确`;
|
|
105
|
+
} else {
|
|
106
|
+
console.log(validate.errors[0]);
|
|
107
|
+
validTips.value = `不符合要求`;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
113
|
+
};
|
|
114
|
+
const getValue = () => {
|
|
115
|
+
return dtoValue.value !== undefined
|
|
116
|
+
? {
|
|
117
|
+
[schemaKey]: dtoValue.value,
|
|
118
|
+
}
|
|
119
|
+
: {};
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// 聚焦时,置空错误信息
|
|
123
|
+
const onFocus = () => {
|
|
124
|
+
validTips.value = null;
|
|
125
|
+
};
|
|
126
|
+
// 监听输入框失去焦点,触发校验
|
|
127
|
+
const onBlur = () => {
|
|
128
|
+
validate();
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
defineExpose({
|
|
132
|
+
name,
|
|
133
|
+
validate,
|
|
134
|
+
getValue,
|
|
135
|
+
});
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
<style scoped lang="less"></style>
|
|
@@ -0,0 +1,140 @@
|
|
|
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 v-if="schema.option?.required" type="flex" class="required"
|
|
6
|
+
>*</el-row
|
|
7
|
+
>
|
|
8
|
+
{{ schema.label }}
|
|
9
|
+
</el-row>
|
|
10
|
+
<!-- value -->
|
|
11
|
+
<el-row class="item-value">
|
|
12
|
+
<el-input-number
|
|
13
|
+
:controls="false"
|
|
14
|
+
v-model="dtoValue"
|
|
15
|
+
v-bind="schema.option"
|
|
16
|
+
class="component"
|
|
17
|
+
:placeholder="placeholder"
|
|
18
|
+
:class="validTips ? 'valid-border' : ''"
|
|
19
|
+
@focus="onFocus"
|
|
20
|
+
@blur="onBlur"
|
|
21
|
+
></el-input-number>
|
|
22
|
+
</el-row>
|
|
23
|
+
<!-- 错误信息 -->
|
|
24
|
+
<el-row v-if="validTips" class="valid-tips">{{ validTips }}</el-row>
|
|
25
|
+
</el-row>
|
|
26
|
+
</template>
|
|
27
|
+
<script setup>
|
|
28
|
+
import { ref, toRefs, watch, inject, onMounted } from "vue";
|
|
29
|
+
const ajv = inject("ajv");
|
|
30
|
+
|
|
31
|
+
const props = defineProps({
|
|
32
|
+
schemaKey: {
|
|
33
|
+
type: String,
|
|
34
|
+
},
|
|
35
|
+
schema: {
|
|
36
|
+
type: Object,
|
|
37
|
+
},
|
|
38
|
+
model: {
|
|
39
|
+
type: Number,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
const { schemaKey, schema } = props;
|
|
43
|
+
const { model } = toRefs(props);
|
|
44
|
+
|
|
45
|
+
const dtoValue = ref();
|
|
46
|
+
const validTips = ref(null);
|
|
47
|
+
const placeholder = ref("");
|
|
48
|
+
const name = ref("inputNumber");
|
|
49
|
+
|
|
50
|
+
const initData = () => {
|
|
51
|
+
dtoValue.value = model.value ?? schema.option.default;
|
|
52
|
+
validTips.value = null;
|
|
53
|
+
|
|
54
|
+
// 将规则中相关的字段提取出来
|
|
55
|
+
const { minimum, maximum } = schema;
|
|
56
|
+
const ruleList = [];
|
|
57
|
+
// 用户自定义placeholder传递过来的
|
|
58
|
+
if (schema.option?.placeholder) {
|
|
59
|
+
ruleList.push(schema.option.placeholder);
|
|
60
|
+
}
|
|
61
|
+
if (minimum !== undefined) {
|
|
62
|
+
ruleList.push(`最小值:${minimum}`);
|
|
63
|
+
}
|
|
64
|
+
if (maximum !== undefined) {
|
|
65
|
+
ruleList.push(`最大值:${maximum}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
placeholder.value = ruleList.join("|");
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
onMounted(() => {
|
|
72
|
+
initData();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
watch(
|
|
76
|
+
[model, schema],
|
|
77
|
+
() => {
|
|
78
|
+
initData();
|
|
79
|
+
},
|
|
80
|
+
{ deep: true },
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const validate = () => {
|
|
84
|
+
validTips.value = null;
|
|
85
|
+
const { type } = schema;
|
|
86
|
+
// 校验是否必填
|
|
87
|
+
if (schema.option?.required && !dtoValue.value) {
|
|
88
|
+
validTips.value = "不能为空";
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
// 校验 schema
|
|
92
|
+
if (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 === "minimum") {
|
|
100
|
+
validTips.value = `最小值应为${params.limit}`;
|
|
101
|
+
} else if (keyword === "maximum") {
|
|
102
|
+
validTips.value = `最大值应为${params.limit}`;
|
|
103
|
+
} else {
|
|
104
|
+
console.log(validate.errors[0]);
|
|
105
|
+
validTips.value = `不符合要求`;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
};
|
|
112
|
+
const getValue = () => {
|
|
113
|
+
return dtoValue.value !== undefined
|
|
114
|
+
? {
|
|
115
|
+
[schemaKey]: dtoValue.value,
|
|
116
|
+
}
|
|
117
|
+
: {};
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// 聚焦时,置空错误信息
|
|
121
|
+
const onFocus = () => {
|
|
122
|
+
validTips.value = null;
|
|
123
|
+
};
|
|
124
|
+
// 监听输入框失去焦点,触发校验
|
|
125
|
+
const onBlur = () => {
|
|
126
|
+
validate();
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
defineExpose({
|
|
130
|
+
name,
|
|
131
|
+
validate,
|
|
132
|
+
getValue,
|
|
133
|
+
});
|
|
134
|
+
</script>
|
|
135
|
+
|
|
136
|
+
<style scoped lang="less">
|
|
137
|
+
:deep(.el-input-number .el-input__inner) {
|
|
138
|
+
text-align: center;
|
|
139
|
+
}
|
|
140
|
+
</style>
|