@slice_cy/mint 1.2.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 (98) hide show
  1. package/README.md +697 -0
  2. package/app/common/base-response.js +10 -0
  3. package/app/common/business-error.js +10 -0
  4. package/app/common/error-code.js +22 -0
  5. package/app/common/return-utils.js +40 -0
  6. package/app/common/router-schema-validator.js +25 -0
  7. package/app/controller/base.js +24 -0
  8. package/app/controller/project.js +90 -0
  9. package/app/controller/view.js +13 -0
  10. package/app/docs/dsl-doc.js +148 -0
  11. package/app/extend/logger.js +30 -0
  12. package/app/extend/params-valid.js +6 -0
  13. package/app/extend/return-utils.js +9 -0
  14. package/app/middleware/api-sign-verify.js +25 -0
  15. package/app/middleware/err-handler.js +18 -0
  16. package/app/middleware/project-handler.js +25 -0
  17. package/app/pages/boot.js +34 -0
  18. package/app/pages/dashboard/complex-view/header-view/index.vue +108 -0
  19. package/app/pages/dashboard/complex-view/iframe-view/index.vue +37 -0
  20. package/app/pages/dashboard/complex-view/schema-view/complex-view/search-panel/index.vue +36 -0
  21. package/app/pages/dashboard/complex-view/schema-view/complex-view/table-panel/index.vue +123 -0
  22. package/app/pages/dashboard/complex-view/schema-view/components/component-config.js +14 -0
  23. package/app/pages/dashboard/complex-view/schema-view/components/createForm/index.vue +72 -0
  24. package/app/pages/dashboard/complex-view/schema-view/components/detailPanel/index.vue +52 -0
  25. package/app/pages/dashboard/complex-view/schema-view/components/editForm/index.vue +87 -0
  26. package/app/pages/dashboard/complex-view/schema-view/hook/use-schema.js +113 -0
  27. package/app/pages/dashboard/complex-view/schema-view/index.vue +114 -0
  28. package/app/pages/dashboard/complex-view/sider-view/index.vue +116 -0
  29. package/app/pages/dashboard/entry.dashboard.js +44 -0
  30. package/app/pages/dashboard/index.vue +60 -0
  31. package/app/pages/service/index.js +70 -0
  32. package/app/pages/store/index.js +4 -0
  33. package/app/pages/store/menu.js +86 -0
  34. package/app/pages/store/project.js +9 -0
  35. package/app/pages/utils/index.js +26 -0
  36. package/app/pages/widgets/header-container/assets/avatar.png +0 -0
  37. package/app/pages/widgets/header-container/assets/icon.png +0 -0
  38. package/app/pages/widgets/header-container/index.vue +108 -0
  39. package/app/pages/widgets/schema-form/components/input/index.vue +155 -0
  40. package/app/pages/widgets/schema-form/components/input-number/index.vue +148 -0
  41. package/app/pages/widgets/schema-form/components/select/index.vue +132 -0
  42. package/app/pages/widgets/schema-form/form-item-cofnig.js +18 -0
  43. package/app/pages/widgets/schema-form/index.vue +137 -0
  44. package/app/pages/widgets/schema-search-bar/component/date-range-picker/index.vue +55 -0
  45. package/app/pages/widgets/schema-search-bar/component/dynamic-select/index.vue +70 -0
  46. package/app/pages/widgets/schema-search-bar/component/input/index.vue +54 -0
  47. package/app/pages/widgets/schema-search-bar/component/select/index.vue +65 -0
  48. package/app/pages/widgets/schema-search-bar/index.vue +143 -0
  49. package/app/pages/widgets/schema-search-bar/search-item-config.js +20 -0
  50. package/app/pages/widgets/schema-table/index.vue +198 -0
  51. package/app/pages/widgets/sider-container/index.vue +30 -0
  52. package/app/post-router.js +9 -0
  53. package/app/pre-middleware.js +37 -0
  54. package/app/public/dist/entry.dashboard.tpl +2 -0
  55. package/app/public/dist/entry.project-list.tpl +2 -0
  56. package/app/public/dist/prod/css/async.609.bcbe5779.css +1 -0
  57. package/app/public/dist/prod/css/async.952.9bc7c180.css +1 -0
  58. package/app/public/dist/prod/css/common.1a98227b.css +1 -0
  59. package/app/public/dist/prod/css/entry.project-list.14f64dd9.css +1 -0
  60. package/app/public/dist/prod/js/142.56646c73.bundle.js +1 -0
  61. package/app/public/dist/prod/js/609.8270b20d.bundle.js +1 -0
  62. package/app/public/dist/prod/js/68.2813fafe.bundle.js +1 -0
  63. package/app/public/dist/prod/js/952.c76c04fb.bundle.js +1 -0
  64. package/app/public/dist/prod/js/common.9985f827.bundle.js +1 -0
  65. package/app/public/dist/prod/js/entry.dashboard.cd7591e4.bundle.js +1 -0
  66. package/app/public/dist/prod/js/entry.project-list.beb0fa9d.bundle.js +1 -0
  67. package/app/public/dist/prod/js/runtime.5602b906.bundle.js +1 -0
  68. package/app/public/dist/prod/js/vendor.709fbf32.bundle.js +53 -0
  69. package/app/public/dist/prod/static/images/avatar.78e68eaa.png +0 -0
  70. package/app/public/dist/static/logo.png +0 -0
  71. package/app/public/dist/static/normalize.css +1 -0
  72. package/app/public/static/logo.png +0 -0
  73. package/app/public/static/normalize.css +267 -0
  74. package/app/router/project.js +6 -0
  75. package/app/router/view.js +5 -0
  76. package/app/router-schema/project.js +14 -0
  77. package/app/service/base.js +22 -0
  78. package/app/service/project.js +49 -0
  79. package/app/view/entry.tpl +18 -0
  80. package/app/webpack/config/webpack.base.js +288 -0
  81. package/app/webpack/config/webpack.dev.js +53 -0
  82. package/app/webpack/config/webpack.prod.js +106 -0
  83. package/app/webpack/dev.js +34 -0
  84. package/app/webpack/libs/block.js +1 -0
  85. package/app/webpack/prod.js +21 -0
  86. package/config/config.default.js +5 -0
  87. package/index.js +37 -0
  88. package/mint-core/env.js +23 -0
  89. package/mint-core/index.js +101 -0
  90. package/mint-core/loader/config.js +59 -0
  91. package/mint-core/loader/controller.js +59 -0
  92. package/mint-core/loader/extend.js +42 -0
  93. package/mint-core/loader/middleware.js +54 -0
  94. package/mint-core/loader/router-schema.js +39 -0
  95. package/mint-core/loader/router.js +36 -0
  96. package/mint-core/loader/service.js +57 -0
  97. package/model/index.js +94 -0
  98. package/package.json +93 -0
@@ -0,0 +1,116 @@
1
+ <template>
2
+ <sider-container>
3
+ <template #sider-content>
4
+ <a-menu
5
+ v-model:selected-keys="activeKey"
6
+ mode="vertical"
7
+ :items="menuList"
8
+ @click="onMenuSelect"
9
+ />
10
+ </template>
11
+ <template #main-content>
12
+ <router-view />
13
+ </template>
14
+ </sider-container>
15
+ </template>
16
+
17
+ <script setup>
18
+ import { ref, watch, onMounted } from "vue";
19
+ import SiderContainer from "$mintWidgets/sider-container/index.vue";
20
+ import { useRoute, useRouter } from "vue-router";
21
+ import { useMenuStore } from "$mintStore/menu";
22
+
23
+ const route = useRoute();
24
+ const router = useRouter();
25
+ const menuStore = useMenuStore();
26
+
27
+ const activeKey = ref("");
28
+ const menuList = ref([]);
29
+
30
+ watch(
31
+ [() => menuStore.menuList, () => route.query.key],
32
+ () => {
33
+ setMenuList();
34
+ setActiveKey();
35
+ },
36
+ { deep: true },
37
+ );
38
+
39
+ const setMenuList = () => {
40
+ const menuItem = menuStore.findMenuItem({
41
+ key: "key",
42
+ value: route.query.key,
43
+ });
44
+
45
+ if (!menuItem) return;
46
+
47
+ if (menuItem?.siderConfig?.menu) {
48
+ menuList.value = menuStore.transfomMenuList(menuItem.siderConfig.menu);
49
+ }
50
+ };
51
+
52
+ const onMenuSelect = ({ item }) => {
53
+ handleMenuSelect(item.originItemValue);
54
+ };
55
+
56
+ const handleMenuSelect = (menuItem) => {
57
+ if (route.query.sider_key === menuItem.key) return;
58
+ const { key, moduleType, customConfig } = menuItem;
59
+ const pathMap = {
60
+ sider: "/sider",
61
+ schema: "/schema",
62
+ custom: customConfig?.path,
63
+ iframe: "/iframe",
64
+ };
65
+
66
+ router.push({
67
+ path: `/view/dashboard/sider${pathMap[moduleType]}`,
68
+ query: {
69
+ project_key: route.query.project_key,
70
+ key: route.query.key,
71
+ sider_key: key,
72
+ },
73
+ });
74
+ };
75
+
76
+ const setActiveKey = () => {
77
+ // 获取顶层子菜单
78
+ const topMenu = menuStore.findMenuItem({
79
+ key: "key",
80
+ value: route.query.key,
81
+ });
82
+ if (!topMenu) return;
83
+
84
+ if (!topMenu?.siderConfig?.menu) return;
85
+ // 获取sider侧边栏菜单
86
+ const siderMenuList = topMenu.siderConfig.menu;
87
+ // 获取到就直接设置
88
+ const siderMenuItem = menuStore.findMenuItem(
89
+ {
90
+ key: "key",
91
+ value: route.query.sider_key,
92
+ },
93
+ siderMenuList,
94
+ );
95
+ if (siderMenuItem) {
96
+ activeKey.value = siderMenuItem?.key ? [siderMenuItem.key] : [];
97
+ return;
98
+ }
99
+ // 获取不到就取第一项(考虑嵌套情况,递归)
100
+ const firstMenuItem = menuStore.findFirstMenuItem(siderMenuList);
101
+ activeKey.value = firstMenuItem?.key ? [firstMenuItem.key] : [];
102
+ // 兼容url没带sider_key,然后菜单自动高亮了第一项,也需要自动跳转到第一项的路由页面
103
+ handleMenuSelect(firstMenuItem);
104
+ };
105
+
106
+ onMounted(() => {
107
+ setMenuList();
108
+ setActiveKey();
109
+ });
110
+ </script>
111
+
112
+ <style lang="less" scoped>
113
+ .sider-view {
114
+ height: 100%;
115
+ }
116
+ </style>
@@ -0,0 +1,44 @@
1
+ import App from "./index";
2
+ import boot from "../boot";
3
+ // import businessDashboardRouterConfig from "$businessDashboardRouterConfig";
4
+
5
+ const siderRoutes = [
6
+ {
7
+ path: "iframe",
8
+ component: () => import("$mintPage/dashboard/complex-view/iframe-view/index.vue"),
9
+ },
10
+ {
11
+ path: "schema",
12
+ component: () => import("$mintPage/dashboard/complex-view/schema-view/index.vue"),
13
+ },
14
+ ];
15
+
16
+ const routes = [
17
+ // 头部菜单路由
18
+ {
19
+ path: "/view/dashboard/iframe",
20
+ component: () => import("$mintPage/dashboard/complex-view/iframe-view/index.vue"),
21
+ },
22
+ {
23
+ path: "/view/dashboard/schema",
24
+ component: () => import("$mintPage/dashboard/complex-view/schema-view/index.vue"),
25
+ },
26
+
27
+ // 侧边栏菜单
28
+ {
29
+ path: "/view/dashboard/sider",
30
+ component: () => import("$mintPage/dashboard/complex-view/sider-view/index.vue"),
31
+ children: siderRoutes,
32
+ },
33
+ // 侧边栏兜底
34
+ {
35
+ path: "/view/dashboard/sider/:chapter",
36
+ component: () => import("$mintPage/dashboard/complex-view/sider-view/index.vue"),
37
+ },
38
+ ];
39
+
40
+ // if (typeof businessDashboardRouterConfig === "function") {
41
+ // businessDashboardRouterConfig({ routes, siderRoutes });
42
+ // }
43
+
44
+ boot(App, { routes });
@@ -0,0 +1,60 @@
1
+ <template>
2
+ <header-view
3
+ :project-name="projectName"
4
+ username="管理员"
5
+ @menu-select="onMenuSelect"
6
+ >
7
+ <template #main-content>
8
+ <router-view />
9
+ </template>
10
+ </header-view>
11
+ </template>
12
+
13
+ <script setup>
14
+ import HeaderView from "$mintPage/dashboard/complex-view/header-view/index.vue";
15
+ import { onMounted, ref } from "vue";
16
+ import { useRoute, useRouter } from "vue-router";
17
+ import { httpGet } from "$mintService";
18
+ import { useProjectStore } from "$mintStore/project";
19
+ import { useMenuStore } from "$mintStore/menu";
20
+
21
+ const route = useRoute();
22
+ const router = useRouter();
23
+ const projectStore = useProjectStore();
24
+ const menuStore = useMenuStore();
25
+ const projectKey = route.query.project_key;
26
+ const projectName = ref("");
27
+ const loadProject = async () => {
28
+ const { data } = await httpGet("/api/project", { project_key: projectKey });
29
+ projectName.value = data.name;
30
+ menuStore.setMenuList(data.menu);
31
+ };
32
+
33
+ const loadProjectList = async () => {
34
+ const { data } = await httpGet("/api/project/list", { project_key: projectKey });
35
+ projectStore.setProjectList(data);
36
+ };
37
+
38
+ const onMenuSelect = (menuItem) => {
39
+ const { key, moduleType, customConfig } = menuItem;
40
+ if (key === route.query.key) return;
41
+ const pathMap = {
42
+ sider: "/sider",
43
+ schema: "/schema",
44
+ custom: customConfig?.path,
45
+ iframe: "/iframe",
46
+ };
47
+ router.push({
48
+ path: `/view/dashboard${pathMap[moduleType]}`,
49
+ query: {
50
+ project_key: projectKey,
51
+ key,
52
+ },
53
+ });
54
+ };
55
+
56
+ onMounted(() => {
57
+ loadProject();
58
+ loadProjectList();
59
+ });
60
+ </script>
@@ -0,0 +1,70 @@
1
+ import axios from "axios";
2
+ import { message } from "ant-design-vue";
3
+ const md5 = require("md5");
4
+
5
+ const signKey = "mint";
6
+
7
+ // 创建实例
8
+ const service = axios.create({
9
+ baseURL: "", // 环境地址
10
+ timeout: 15000, // 15秒超时
11
+ headers: {
12
+ "Content-Type": "application/json;charset=utf-8",
13
+ },
14
+ });
15
+
16
+ // 请求拦截器
17
+ service.interceptors.request.use(
18
+ (config) => {
19
+ // 请求头添加签名
20
+ const st = new Date().getTime();
21
+ const signature = md5(`${signKey}_${st}`);
22
+ config.headers = {
23
+ ...config.headers,
24
+ s_sign: signature,
25
+ s_t: st,
26
+ };
27
+ if (config.url.indexOf("/api/proj/") > -1 && window.projectKey) {
28
+ config.headers.project_key = window.projectKey;
29
+ }
30
+ return config;
31
+ },
32
+ (error) => {
33
+ return Promise.reject(error);
34
+ },
35
+ );
36
+
37
+ // 响应拦截器
38
+ service.interceptors.response.use(
39
+ (response) => {
40
+ const { data } = response;
41
+ if (data.code === 200 || data.code === 0) {
42
+ return data;
43
+ }
44
+ message.error(data.message);
45
+ return Promise.reject(data);
46
+ },
47
+ (error) => {
48
+ message.error(error.message || "请求失败,请稍后重试");
49
+ return Promise.reject(error);
50
+ },
51
+ );
52
+
53
+ // 导出简写请求方法
54
+ export function httpGet(url, params, config = {}) {
55
+ return service.get(url, { params, ...config });
56
+ }
57
+
58
+ export function httpPost(url, data, config = {}) {
59
+ return service.post(url, data, config);
60
+ }
61
+
62
+ export function httpPut(url, data) {
63
+ return service.put(url, data);
64
+ }
65
+
66
+ export function httpDelete(url, params) {
67
+ return service.delete(url, { params });
68
+ }
69
+
70
+ export default service;
@@ -0,0 +1,4 @@
1
+ import { createPinia } from "pinia";
2
+
3
+ const pinia = createPinia();
4
+ export default pinia;
@@ -0,0 +1,86 @@
1
+ import { defineStore } from "pinia";
2
+ import { ref } from "vue";
3
+ import cloneDeep from "lodash/cloneDeep";
4
+ export const useMenuStore = defineStore("menu", () => {
5
+ const menuList = ref([]);
6
+ const setMenuList = (list) => {
7
+ menuList.value = list;
8
+ };
9
+
10
+ /**
11
+ * 递归从菜单树中获取查询菜单项
12
+ * @param {object} param
13
+ * @param {array} mList
14
+ * @returns
15
+ */
16
+ const findMenuItem = ({ key, value }, mList = menuList.value) => {
17
+ for (let i = 0; i < mList.length; i++) {
18
+ const menuItem = cloneDeep(mList[i]);
19
+ if (!menuItem) continue;
20
+
21
+ if (menuItem[key] === value) {
22
+ return menuItem;
23
+ }
24
+
25
+ const { menuType, moduleType, siderConfig } = menuItem;
26
+
27
+ if (menuType === "group") {
28
+ const findItem = findMenuItem({ key, value }, menuItem.subMenu);
29
+ if (findItem) return findItem;
30
+ }
31
+
32
+ if (menuType === "module" && moduleType === "sider" && siderConfig?.menu) {
33
+ const findItem = findMenuItem({ key, value }, siderConfig.menu);
34
+ if (findItem) return findItem;
35
+ }
36
+ }
37
+ return null;
38
+ };
39
+
40
+ /**
41
+ * 获取第一个菜单项
42
+ * @param {array} mList
43
+ * @returns
44
+ */
45
+ const findFirstMenuItem = (mList = menuList.value) => {
46
+ const firstMenuItem = cloneDeep(mList[0]);
47
+ const { menuType, subMenu } = firstMenuItem;
48
+ if (menuType === "group" && subMenu) {
49
+ const findItem = findFirstMenuItem(subMenu);
50
+ if (findItem) return findItem;
51
+ }
52
+ return firstMenuItem;
53
+ };
54
+
55
+ /**
56
+ * 负责递归菜单树结构,新增{ label , disabled,children 字段}
57
+ * @param {array} menuList
58
+ * @param {object} options
59
+ * @param {function} options.disabledCallback
60
+ */
61
+ const transfomMenuList = (mList = menuList.value, options = {}) => {
62
+ return cloneDeep(mList).map((item) => {
63
+ const { subMenu, menuType, moduleType, siderConfig } = item;
64
+ const obj = {
65
+ ...item,
66
+ label: item.name,
67
+ disabled: options?.disabledCallback && options.disabledCallback(item),
68
+ };
69
+ if (menuType === "group" && subMenu && subMenu.length) {
70
+ obj.children = transfomMenuList(subMenu, options);
71
+ }
72
+ if (
73
+ menuType === "module" &&
74
+ moduleType === "sider" &&
75
+ siderConfig?.menu &&
76
+ siderConfig?.menu?.length
77
+ ) {
78
+ obj.siderConfig.menu = transfomMenuList(siderConfig.menu, options);
79
+ }
80
+
81
+ return obj;
82
+ });
83
+ };
84
+
85
+ return { menuList, setMenuList, findMenuItem, findFirstMenuItem, transfomMenuList };
86
+ });
@@ -0,0 +1,9 @@
1
+ import { defineStore } from "pinia";
2
+ import { ref } from "vue";
3
+ export const useProjectStore = defineStore("project", () => {
4
+ const projectList = ref([]);
5
+ const setProjectList = (list) => {
6
+ projectList.value = list;
7
+ };
8
+ return { projectList, setProjectList };
9
+ });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 过滤配置了 [key]: false 的属性
3
+ * @param {*} schema
4
+ * @param {*} fieldName
5
+ * @returns
6
+ */
7
+ export const filterNotRenderData = (schema, fieldName) => {
8
+ if (!schema?.properties) return schema;
9
+ return Object.entries(schema.properties).reduce(
10
+ (acc, [key, value]) => {
11
+ if (
12
+ value?.option &&
13
+ Object.hasOwnProperty.call(value.option, fieldName) &&
14
+ !value.option[fieldName]
15
+ ) {
16
+ return acc;
17
+ }
18
+ acc.properties[key] = value;
19
+ return acc;
20
+ },
21
+ {
22
+ type: "object",
23
+ properties: {},
24
+ },
25
+ );
26
+ };
@@ -0,0 +1,108 @@
1
+ <template>
2
+ <a-layout>
3
+ <a-layout-header class="header">
4
+ <!-- 头部容器 -->
5
+ <div class="header-panel">
6
+ <!-- 左侧 -->
7
+ <div class="left-panel">
8
+ <a-flex
9
+ align="center"
10
+ class="title-panel"
11
+ >
12
+ <img src="./assets/avatar.png" />
13
+ <h2 class="title">{{ projectName }}</h2>
14
+ </a-flex>
15
+ </div>
16
+
17
+ <!-- 中间侧 -->
18
+ <div class="middle-panel">
19
+ <slot name="menu-content" />
20
+ </div>
21
+ <!-- 右侧 -->
22
+ <div class="right-panel">
23
+ <slot name="setting-content" />
24
+ <div class="avatar-wrap">
25
+ <img src="./assets/avatar.png" />
26
+ <a-dropdown>
27
+ <a>
28
+ {{ username }}
29
+ <DownOutlined />
30
+ </a>
31
+ <template #overlay>
32
+ <a-menu>
33
+ <a-menu-item>
34
+ <a href="javascript:;">退出登录</a>
35
+ </a-menu-item>
36
+ </a-menu>
37
+ </template>
38
+ </a-dropdown>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </a-layout-header>
43
+ <a-layout-content class="content">
44
+ <slot name="main-content" />
45
+ </a-layout-content>
46
+ </a-layout>
47
+ </template>
48
+
49
+ <script setup>
50
+ defineProps({
51
+ projectName: {
52
+ type: String,
53
+ default: "",
54
+ },
55
+ username: {
56
+ type: String,
57
+ default: "",
58
+ },
59
+ });
60
+ </script>
61
+
62
+ <style scoped lang="less">
63
+ .header {
64
+ background: #fff;
65
+ border-bottom: 1px solid #eee;
66
+ }
67
+
68
+ .content {
69
+ // padding: 48px;
70
+ background: #fff;
71
+ height: calc(100vh - 64px);
72
+ overflow-y: auto;
73
+ }
74
+
75
+ img {
76
+ width: 32px;
77
+ height: 32px;
78
+ margin-right: 16px;
79
+ }
80
+
81
+ .header-panel {
82
+ display: flex;
83
+ align-items: center;
84
+ height: 64px;
85
+ .title-panel {
86
+ .title {
87
+ margin: 0 20px 0 0;
88
+ }
89
+ }
90
+
91
+ .left-panel {
92
+ min-width: 120px;
93
+ display: flex;
94
+ align-items: center;
95
+ }
96
+
97
+ .middle-panel {
98
+ flex: 1;
99
+ }
100
+
101
+ .right-panel {
102
+ min-width: 120px;
103
+ display: flex;
104
+ gap: 12px;
105
+ align-items: center;
106
+ }
107
+ }
108
+ </style>
@@ -0,0 +1,155 @@
1
+ <template>
2
+ <div class="form-item">
3
+ <div class="item-label">
4
+ <a-flex
5
+ :gap="5"
6
+ align-items="center"
7
+ >
8
+ <span
9
+ v-if="schema?.option?.required"
10
+ class="required"
11
+ >
12
+ *
13
+ </span>
14
+ <span>{{ schema.label }}</span>
15
+ </a-flex>
16
+ </div>
17
+ <a-input
18
+ v-model:value="dtoValue"
19
+ :class="[validTip.length ? 'validate-border-error' : '']"
20
+ v-bind="componentAttrs"
21
+ @blur="onBlur"
22
+ @focus="onFocus"
23
+ ></a-input>
24
+
25
+ <div
26
+ v-if="validTip.length"
27
+ class="validate-tip-error"
28
+ >
29
+ {{ validTip }}
30
+ </div>
31
+ </div>
32
+ </template>
33
+
34
+ <script setup>
35
+ import { watch, onMounted, ref, computed, inject } from "vue";
36
+ const props = defineProps({
37
+ schema: {
38
+ type: Object,
39
+ default: () => ({}),
40
+ },
41
+ schemaKey: {
42
+ type: String,
43
+ default: "",
44
+ },
45
+ model: {
46
+ type: String,
47
+ default: "",
48
+ },
49
+ });
50
+
51
+ const name = "input";
52
+
53
+ const componentAttrs = computed(() => {
54
+ const { minLength, maxLength, pattern } = props.schema;
55
+ const { placeholder: pValue } = props.schema.option?.uiComponentConfig || {};
56
+ const ruleList = [];
57
+ if (minLength) {
58
+ ruleList.push(`最小长度:${minLength}`);
59
+ }
60
+ if (maxLength) {
61
+ ruleList.push(`最大长度:${maxLength}`);
62
+ }
63
+ if (pattern) {
64
+ ruleList.push(`格式要求:${pattern}`);
65
+ }
66
+ ruleList.push(pValue || "请输入");
67
+ return {
68
+ allowClear: true,
69
+ ...(props.schema.option?.uiComponentConfig || {}),
70
+ placeholder: ruleList.join(" | "),
71
+ disabled: props.schema.option?.disabled,
72
+ };
73
+ });
74
+
75
+ watch(
76
+ [() => props.schema, () => props.model, () => props.schemaKey],
77
+ () => {
78
+ initData();
79
+ },
80
+ { deep: true },
81
+ );
82
+
83
+ const dtoValue = ref();
84
+ const getValue = () => {
85
+ return dtoValue.value
86
+ ? {
87
+ [props.schemaKey]: dtoValue.value,
88
+ }
89
+ : {};
90
+ };
91
+
92
+ const getAjv = inject("getAjv");
93
+ const validTip = ref("");
94
+
95
+ const validate = async () => {
96
+ validTip.value = "";
97
+ if (props.schema.option?.required && !dtoValue.value) {
98
+ validTip.value = `${props.schema.label}必填`;
99
+ return false;
100
+ }
101
+ if (dtoValue.value) {
102
+ const ajv = await getAjv();
103
+ const validate = ajv.compile(props.schema);
104
+ const valid = validate(dtoValue.value);
105
+ if (!valid && validate.errors && validate.errors[0]) {
106
+ const { keyword, params } = validate.errors[0];
107
+ switch (keyword) {
108
+ case "type":
109
+ validTip.value = `${props.schema.label}类型错误`;
110
+ break;
111
+ case "minLength":
112
+ validTip.value = `${props.schema.label}长度不能小于${params.limit}`;
113
+ break;
114
+ case "maxLength":
115
+ validTip.value = `${props.schema.label}长度不能大于${params.limit}`;
116
+ break;
117
+ case "pattern":
118
+ validTip.value = `${props.schema.label}格式错误`;
119
+ break;
120
+ default:
121
+ validTip.value = `${props.schema.label}不符合要求`;
122
+ break;
123
+ }
124
+ return false;
125
+ }
126
+ }
127
+
128
+ return true;
129
+ };
130
+
131
+ const onFocus = () => {
132
+ validTip.value = "";
133
+ };
134
+
135
+ const onBlur = () => {
136
+ void validate();
137
+ };
138
+
139
+ function initData() {
140
+ dtoValue.value = props.model || props.schema.option?.default || undefined;
141
+ validTip.value = "";
142
+ }
143
+
144
+ onMounted(() => {
145
+ initData();
146
+ });
147
+
148
+ defineExpose({
149
+ getValue,
150
+ validate,
151
+ name,
152
+ });
153
+ </script>
154
+
155
+ <style scoped lang="less"></style>