zyf-model-engine 0.0.1
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/.editorconfig +15 -0
- package/.env +13 -0
- package/.env.development +20 -0
- package/.env.production +25 -0
- package/.env.test +25 -0
- package/.eslintignore +15 -0
- package/.eslintrc-auto-import.json +65 -0
- package/.eslintrc.cjs +66 -0
- package/.husky/commit-msg +4 -0
- package/.husky/pre-commit +5 -0
- package/.prettierignore +9 -0
- package/.prettierrc.cjs +41 -0
- package/.stylelintignore +4 -0
- package/.stylelintrc.cjs +40 -0
- package/.vscode/extensions.json +14 -0
- package/.vscode/settings.json +92 -0
- package/CHANGELOG.md +246 -0
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/auto-imports.d.ts +132 -0
- package/build/getEnv.ts +46 -0
- package/build/plugins.ts +138 -0
- package/build/proxy.ts +28 -0
- package/commitlint.config.cjs +84 -0
- package/components.d.ts +28 -0
- package/index.html +105 -0
- package/lint-staged.config.cjs +8 -0
- package/package.json +108 -0
- package/postcss.config.cjs +5 -0
- package/public/logo.png +0 -0
- package/src/App.vue +16 -0
- package/src/api/config/servicePort.ts +3 -0
- package/src/api/enums/httpEnum.ts +35 -0
- package/src/api/helper/axiosCancel.ts +50 -0
- package/src/api/helper/axiosFileCancel.ts +62 -0
- package/src/api/helper/checkStatus.ts +97 -0
- package/src/api/index.ts +142 -0
- package/src/api/interface/index.ts +27 -0
- package/src/assets/global-icons/icon-demo.svg +1 -0
- package/src/assets/iconfont/iconfont.scss +44 -0
- package/src/assets/iconfont/iconfont.ttf +0 -0
- package/src/assets/images/empty-img.png +0 -0
- package/src/assets/option-icons/add.svg +1 -0
- package/src/assets/option-icons/check.svg +1 -0
- package/src/assets/option-icons/config.svg +1 -0
- package/src/assets/option-icons/del.svg +1 -0
- package/src/assets/option-icons/detail.svg +1 -0
- package/src/assets/option-icons/edit.svg +2 -0
- package/src/assets/option-icons/export.svg +1 -0
- package/src/assets/option-icons/folder.svg +1 -0
- package/src/assets/option-icons/full-screen.svg +1 -0
- package/src/assets/option-icons/more.svg +1 -0
- package/src/assets/option-icons/return.svg +1 -0
- package/src/assets/option-icons/search.svg +1 -0
- package/src/components/CSvgIcon/index.scss +23 -0
- package/src/components/CSvgIcon/index.vue +74 -0
- package/src/components/CTree/index.scss +48 -0
- package/src/components/CTree/index.vue +176 -0
- package/src/components/CTree/interface/index.ts +6 -0
- package/src/components/Dialog/index.scss +74 -0
- package/src/components/Dialog/index.vue +87 -0
- package/src/components/Empty/index.scss +16 -0
- package/src/components/Empty/index.vue +43 -0
- package/src/components/Grid/components/GridItem.vue +68 -0
- package/src/components/Grid/index.vue +167 -0
- package/src/components/Grid/interface/index.ts +6 -0
- package/src/components/Loading/index.scss +67 -0
- package/src/components/Loading/index.vue +13 -0
- package/src/components/Overflow/index.vue +76 -0
- package/src/components/ProTable/components/ColSetting.vue +45 -0
- package/src/components/ProTable/components/Pagination.vue +29 -0
- package/src/components/ProTable/components/SearchForm/components/SearchFormItem.vue +93 -0
- package/src/components/ProTable/components/SearchForm/index.scss +12 -0
- package/src/components/ProTable/components/SearchForm/index.vue +91 -0
- package/src/components/ProTable/components/TableColumn.vue +56 -0
- package/src/components/ProTable/index.scss +14 -0
- package/src/components/ProTable/index.vue +274 -0
- package/src/components/ProTable/interface/index.ts +86 -0
- package/src/components/ScrollBox/index.vue +44 -0
- package/src/components/SvgIcon/index.vue +20 -0
- package/src/components/Tabs/index.vue +142 -0
- package/src/config/index.ts +12 -0
- package/src/config/nprogress.ts +12 -0
- package/src/config/piniaPersist.ts +19 -0
- package/src/config/serviceLoading.ts +45 -0
- package/src/enums/httpEnum.ts +24 -0
- package/src/hooks/interface/index.ts +32 -0
- package/src/hooks/useOnline.ts +27 -0
- package/src/hooks/useTable.ts +167 -0
- package/src/main.ts +35 -0
- package/src/plugins/button/button.vue +56 -0
- package/src/plugins/index.js +26 -0
- package/src/plugins/index.vue +24 -0
- package/src/plugins/msg/msg.vue +56 -0
- package/src/stores/commonStore.ts +9 -0
- package/src/stores/index.ts +8 -0
- package/src/stores/interface/index.ts +1 -0
- package/src/styles/common.scss +87 -0
- package/src/styles/element.scss +233 -0
- package/src/styles/reset.scss +20 -0
- package/src/styles/var.scss +2 -0
- package/src/typings/global.d.ts +37 -0
- package/src/typings/window.d.ts +8 -0
- package/src/utils/color.ts +59 -0
- package/src/utils/commonUtils.ts +175 -0
- package/src/utils/errorHandler.ts +28 -0
- package/src/utils/is.ts +147 -0
- package/src/utils/layoutUtils.ts +83 -0
- package/src/utils/localUtils.ts +42 -0
- package/src/utils/serviceDict.ts +9 -0
- package/src/vite-env.d.ts +6 -0
- package/tsconfig.json +50 -0
- package/vite.config.ts +76 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="tree">
|
|
3
|
+
<el-tree
|
|
4
|
+
ref="treeRef"
|
|
5
|
+
v-bind="{
|
|
6
|
+
data: treeData,
|
|
7
|
+
highlightCurren: true,
|
|
8
|
+
nodeKey: 'id',
|
|
9
|
+
defaultExpandAll: true,
|
|
10
|
+
indent: 7,
|
|
11
|
+
props: {
|
|
12
|
+
children: 'children',
|
|
13
|
+
label: 'name'
|
|
14
|
+
},
|
|
15
|
+
checkStrictly: true,
|
|
16
|
+
emptyText: '暂无数据',
|
|
17
|
+
...$attrs
|
|
18
|
+
}"
|
|
19
|
+
@check="onCheckTree"
|
|
20
|
+
:current-node-key="currentNodeKey"
|
|
21
|
+
>
|
|
22
|
+
<template v-if="slotDefault" #default="{ node, data }">
|
|
23
|
+
<div class="custom-tree-node">
|
|
24
|
+
<slot :data="data" :node="node"></slot>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
<template #empty>
|
|
28
|
+
<empty />
|
|
29
|
+
</template>
|
|
30
|
+
</el-tree>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup lang="ts">
|
|
35
|
+
import { ElTree } from "element-plus";
|
|
36
|
+
import { PropType } from "vue";
|
|
37
|
+
import { fetchType as myFetchType } from "@/api/interface/index";
|
|
38
|
+
import { treeDataProps } from "./interface/index";
|
|
39
|
+
const props = defineProps({
|
|
40
|
+
//列表请求方法
|
|
41
|
+
fetch: {
|
|
42
|
+
type: [Function, Array<treeDataProps>, String],
|
|
43
|
+
default: () => {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
fetchType: {
|
|
48
|
+
type: String as PropType<myFetchType>,
|
|
49
|
+
default: () => "post"
|
|
50
|
+
},
|
|
51
|
+
// 列表请求参数
|
|
52
|
+
fetchParams: {
|
|
53
|
+
type: Object,
|
|
54
|
+
default: () => {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
// 当前选择的节点
|
|
59
|
+
currentNodeKey: {
|
|
60
|
+
type: [String, Number],
|
|
61
|
+
default: () => ""
|
|
62
|
+
},
|
|
63
|
+
//是否监听请求方法
|
|
64
|
+
requestAuto: {
|
|
65
|
+
type: Boolean,
|
|
66
|
+
default: () => true
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const emit = defineEmits(["loadAfter", "onChecked"]);
|
|
71
|
+
//判断<slot/>是否有传值
|
|
72
|
+
const slotDefault = !!useSlots().default;
|
|
73
|
+
|
|
74
|
+
const treeData = ref<treeDataProps[]>([]);
|
|
75
|
+
// 加载table数据
|
|
76
|
+
const loadData = async () => {
|
|
77
|
+
if (props.fetch) {
|
|
78
|
+
try {
|
|
79
|
+
if (["[object AsyncFunction]", "[object String]"].includes(Object.prototype.toString.call(props.fetch))) {
|
|
80
|
+
let res = typeof "string"
|
|
81
|
+
? await http[props.fetchType](props.fetch as string, props.fetchParams || {})
|
|
82
|
+
: await (props.fetch as any)(props.fetchParams);
|
|
83
|
+
if (Array.isArray(res)) {
|
|
84
|
+
treeData.value = res;
|
|
85
|
+
} else {
|
|
86
|
+
treeData.value = [];
|
|
87
|
+
}
|
|
88
|
+
nextTick(() => {
|
|
89
|
+
emit("loadAfter", res);
|
|
90
|
+
});
|
|
91
|
+
} else {
|
|
92
|
+
treeData.value = props.fetch as treeDataProps[];
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.log(e);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const treeRef = ref<InstanceType<typeof ElTree>>();
|
|
101
|
+
|
|
102
|
+
// 获取被勾选项的id数组
|
|
103
|
+
const getCheckedKeys = () => {
|
|
104
|
+
return treeRef.value?.getCheckedKeys();
|
|
105
|
+
};
|
|
106
|
+
// 设置被勾选项的id数组
|
|
107
|
+
const setCheckedKeys = (val: (string | number)[]) => {
|
|
108
|
+
treeRef.value?.setCheckedKeys(val);
|
|
109
|
+
};
|
|
110
|
+
// 获取被勾选前选中节点的数组
|
|
111
|
+
const getCheckedNodes = () => {
|
|
112
|
+
return treeRef.value?.getCheckedNodes();
|
|
113
|
+
};
|
|
114
|
+
// 设置目前勾选的节点
|
|
115
|
+
const setCheckedNodes = (val: any[]) => {
|
|
116
|
+
treeRef.value?.setCheckedNodes(val);
|
|
117
|
+
};
|
|
118
|
+
// 设置目前勾选的节点 key/data
|
|
119
|
+
const setChecked = (key: string, checked: boolean, deep = true) => {
|
|
120
|
+
treeRef.value?.setChecked(key, checked, deep);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const onCheckTree = (data: any, checked: any, indeterminate: any) => {
|
|
124
|
+
let obj = { data, checked, indeterminate, allChecked: getCheckedKeys(), allCheckedNode: getCheckedNodes() };
|
|
125
|
+
emit("onChecked", obj);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const onTreeFilter = (val: string | number) => {
|
|
129
|
+
if (treeData.value?.length) {
|
|
130
|
+
// 将不符合输入名称多选框字列表隐藏掉
|
|
131
|
+
treeRef.value?.filter(val);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
watch(
|
|
135
|
+
[() => props.fetch, () => props.fetchParams],
|
|
136
|
+
() => {
|
|
137
|
+
loadData();
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
deep: true
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
watch(
|
|
144
|
+
() => props.currentNodeKey,
|
|
145
|
+
newV => {
|
|
146
|
+
if (newV) {
|
|
147
|
+
if (treeRef.value) {
|
|
148
|
+
treeRef.value.setCurrentKey(newV);
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
if (treeRef.value) {
|
|
152
|
+
treeRef.value.setCurrentKey(undefined);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
onMounted(() => {
|
|
158
|
+
props.requestAuto && loadData();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
defineExpose({
|
|
162
|
+
element: treeRef,
|
|
163
|
+
treeData,
|
|
164
|
+
loadData,
|
|
165
|
+
getCheckedKeys,
|
|
166
|
+
setCheckedKeys,
|
|
167
|
+
getCheckedNodes,
|
|
168
|
+
setCheckedNodes,
|
|
169
|
+
setChecked,
|
|
170
|
+
onTreeFilter
|
|
171
|
+
});
|
|
172
|
+
</script>
|
|
173
|
+
|
|
174
|
+
<style scoped lang="scss">
|
|
175
|
+
@import "./index.scss";
|
|
176
|
+
</style>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
.dialog-box {
|
|
2
|
+
:deep(.my-dialog) {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
.el-dialog__header {
|
|
6
|
+
padding: 20px;
|
|
7
|
+
padding-right: 36px;
|
|
8
|
+
margin: 0;
|
|
9
|
+
border-bottom: 1px solid #eeeeee;
|
|
10
|
+
.el-dialog__title {
|
|
11
|
+
font-size: 16px;
|
|
12
|
+
font-weight: bold;
|
|
13
|
+
line-height: 19px;
|
|
14
|
+
color: #333333;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
.el-dialog__body {
|
|
18
|
+
position: relative;
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
flex: 1;
|
|
21
|
+
padding: 0 !important;
|
|
22
|
+
.dialog-body {
|
|
23
|
+
padding: 20px 0 20px 20px;
|
|
24
|
+
.dialog-close-icon {
|
|
25
|
+
position: absolute;
|
|
26
|
+
top: 20px;
|
|
27
|
+
right: 20px;
|
|
28
|
+
z-index: 1;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
}
|
|
31
|
+
.dialog-close-icon:hover {
|
|
32
|
+
color: $primary-color;
|
|
33
|
+
}
|
|
34
|
+
> div {
|
|
35
|
+
width: 100%;
|
|
36
|
+
max-height: 760px;
|
|
37
|
+
padding-right: 20px;
|
|
38
|
+
overflow-y: auto;
|
|
39
|
+
}
|
|
40
|
+
.btn-group {
|
|
41
|
+
display: flex;
|
|
42
|
+
justify-content: flex-end;
|
|
43
|
+
.el-button {
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
padding-right: 14px;
|
|
47
|
+
padding-left: 14px;
|
|
48
|
+
.icon {
|
|
49
|
+
width: 16px;
|
|
50
|
+
height: 16px;
|
|
51
|
+
margin-right: 4px;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
.el-dialog__footer {
|
|
58
|
+
padding: 0 30px 30px;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
:deep(.hide-title) {
|
|
62
|
+
.el-dialog__header {
|
|
63
|
+
display: none !important;
|
|
64
|
+
}
|
|
65
|
+
.dialog-body {
|
|
66
|
+
> div {
|
|
67
|
+
width: 100%;
|
|
68
|
+
max-height: 800px;
|
|
69
|
+
padding-right: 30px;
|
|
70
|
+
overflow-y: auto;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="dialog-box" v-if="visible">
|
|
3
|
+
<el-dialog
|
|
4
|
+
:model-value="visible"
|
|
5
|
+
:width="toWidthRatio()"
|
|
6
|
+
align-center
|
|
7
|
+
:title="title"
|
|
8
|
+
:before-close="onClose"
|
|
9
|
+
:destroy-on-close="true"
|
|
10
|
+
:show-close="showCloseIcon"
|
|
11
|
+
:class="hideTitle ? 'my-dialog hide-title' : 'my-dialog'"
|
|
12
|
+
:modal="modal"
|
|
13
|
+
:close-on-click-modal="closeOnClickModal"
|
|
14
|
+
:append-to-body="appendToBody"
|
|
15
|
+
close-on-press-escape
|
|
16
|
+
>
|
|
17
|
+
<template #header v-if="slotHeader">
|
|
18
|
+
<slot name="header"></slot>
|
|
19
|
+
</template>
|
|
20
|
+
<div class="dialog-body" :style="{ width: '100%' }">
|
|
21
|
+
<section v-if="hideTitle" class="dialog-close-icon" @click="onClose">
|
|
22
|
+
<Close style="width: 1em; height: 1em" />
|
|
23
|
+
</section>
|
|
24
|
+
<slot></slot>
|
|
25
|
+
</div>
|
|
26
|
+
<template #footer v-if="slotFooter">
|
|
27
|
+
<slot name="footer"></slot>
|
|
28
|
+
</template>
|
|
29
|
+
</el-dialog>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script setup lang="ts">
|
|
34
|
+
const props = defineProps({
|
|
35
|
+
visible: {
|
|
36
|
+
type: Boolean,
|
|
37
|
+
required: true
|
|
38
|
+
},
|
|
39
|
+
title: {
|
|
40
|
+
type: String,
|
|
41
|
+
default: () => "默认标题"
|
|
42
|
+
},
|
|
43
|
+
width: {
|
|
44
|
+
type: [Number, String],
|
|
45
|
+
default: () => 600
|
|
46
|
+
},
|
|
47
|
+
showCloseIcon: {
|
|
48
|
+
type: Boolean,
|
|
49
|
+
default: () => true
|
|
50
|
+
},
|
|
51
|
+
// 控制是否隐藏 header 栏, 默认为 true
|
|
52
|
+
hideTitle: {
|
|
53
|
+
type: Boolean,
|
|
54
|
+
default: () => false
|
|
55
|
+
},
|
|
56
|
+
modal: {
|
|
57
|
+
type: Boolean,
|
|
58
|
+
default: () => true
|
|
59
|
+
},
|
|
60
|
+
appendToBody: {
|
|
61
|
+
type: Boolean,
|
|
62
|
+
default: () => false
|
|
63
|
+
},
|
|
64
|
+
closeOnClickModal: {
|
|
65
|
+
type: Boolean,
|
|
66
|
+
default: () => true
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const emit = defineEmits(["onClose"]);
|
|
71
|
+
//判断<slot/>是否有传值
|
|
72
|
+
const slotHeader = !!useSlots().header;
|
|
73
|
+
const slotFooter = !!useSlots().footer;
|
|
74
|
+
const toWidthRatio = () => {
|
|
75
|
+
return typeof props.width === "number" ? (props.width / 1920) * 100 + "vw" : props.width;
|
|
76
|
+
};
|
|
77
|
+
const onClose = () => {
|
|
78
|
+
if (axiosFileController.size() > 0) {
|
|
79
|
+
axiosFileController.removeAllPending();
|
|
80
|
+
}
|
|
81
|
+
emit("onClose");
|
|
82
|
+
};
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<style scoped lang="scss">
|
|
86
|
+
@import "./index.scss";
|
|
87
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.empty-box {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
width: 100%;
|
|
7
|
+
height: 100%;
|
|
8
|
+
p {
|
|
9
|
+
margin: 0;
|
|
10
|
+
margin-top: 9px;
|
|
11
|
+
font-size: 14px;
|
|
12
|
+
font-weight: 400;
|
|
13
|
+
line-height: 16px;
|
|
14
|
+
color: #999999;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="emptyRef" class="empty-box flx-center" :style="{ width, height }">
|
|
3
|
+
<img src="../../assets/images/empty-img.png" alt="" :style="refSize" />
|
|
4
|
+
<p class="title">{{ title }}</p>
|
|
5
|
+
</div>
|
|
6
|
+
</template>
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
const props = defineProps({
|
|
9
|
+
width: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: () => "100%"
|
|
12
|
+
},
|
|
13
|
+
height: {
|
|
14
|
+
type: String,
|
|
15
|
+
default: () => "100%"
|
|
16
|
+
},
|
|
17
|
+
imgStyle: {
|
|
18
|
+
type: Object,
|
|
19
|
+
default: () => {}
|
|
20
|
+
},
|
|
21
|
+
title: {
|
|
22
|
+
type: String,
|
|
23
|
+
default: () => "暂无数据"
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
const emptyRef = ref();
|
|
27
|
+
const refSize = ref<Record<string, any>>({
|
|
28
|
+
height: "30%"
|
|
29
|
+
});
|
|
30
|
+
watchEffect(() => {
|
|
31
|
+
if (emptyRef.value) {
|
|
32
|
+
refSize.value =
|
|
33
|
+
emptyRef.value.offsetHeight > emptyRef.value.offsetWidth
|
|
34
|
+
? { width: "30%" }
|
|
35
|
+
: {
|
|
36
|
+
height: "30%"
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
</script>
|
|
41
|
+
<style scoped lang="scss">
|
|
42
|
+
@import "./index.scss";
|
|
43
|
+
</style>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-show="isShow" :style="style">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
<script setup lang="ts" name="GridItem">
|
|
7
|
+
import { computed, inject, Ref, ref, useAttrs, watch } from "vue";
|
|
8
|
+
import { BreakPoint, Responsive } from "../interface/index";
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
offset?: number;
|
|
12
|
+
span?: number;
|
|
13
|
+
suffix?: boolean;
|
|
14
|
+
xs?: Responsive;
|
|
15
|
+
sm?: Responsive;
|
|
16
|
+
md?: Responsive;
|
|
17
|
+
lg?: Responsive;
|
|
18
|
+
xl?: Responsive;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
22
|
+
offset: 0,
|
|
23
|
+
span: 1,
|
|
24
|
+
suffix: false,
|
|
25
|
+
xs: undefined,
|
|
26
|
+
sm: undefined,
|
|
27
|
+
md: undefined,
|
|
28
|
+
lg: undefined,
|
|
29
|
+
xl: undefined
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const attrs = useAttrs() as { index: string };
|
|
33
|
+
const isShow = ref(true);
|
|
34
|
+
|
|
35
|
+
// 注入断点
|
|
36
|
+
const breakPoint = inject<Ref<BreakPoint>>("breakPoint", ref("xl"));
|
|
37
|
+
const shouldHiddenIndex = inject<Ref<number>>("shouldHiddenIndex", ref(-1));
|
|
38
|
+
watch(
|
|
39
|
+
() => [shouldHiddenIndex.value, breakPoint.value],
|
|
40
|
+
n => {
|
|
41
|
+
if (!!attrs.index) {
|
|
42
|
+
isShow.value = !(n[0] !== -1 && parseInt(attrs.index) >= Number(n[0]));
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{ immediate: true }
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const gap = inject("gap", 0);
|
|
49
|
+
const cols = inject("cols", ref(4));
|
|
50
|
+
const style = computed(() => {
|
|
51
|
+
let span = props[breakPoint.value]?.span ?? props.span;
|
|
52
|
+
let offset = props[breakPoint.value]?.offset ?? props.offset;
|
|
53
|
+
if (props.suffix) {
|
|
54
|
+
return {
|
|
55
|
+
gridColumnStart: cols.value - span - offset + 1,
|
|
56
|
+
gridColumnEnd: `span ${span + offset}`,
|
|
57
|
+
marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : "unset"
|
|
58
|
+
};
|
|
59
|
+
} else {
|
|
60
|
+
return {
|
|
61
|
+
gridColumn: `span ${span + offset > cols.value ? cols.value : span + offset}/span ${
|
|
62
|
+
span + offset > cols.value ? cols.value : span + offset
|
|
63
|
+
}`,
|
|
64
|
+
marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : "unset"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
</script>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :style="style">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts" name="Grid">
|
|
8
|
+
import {
|
|
9
|
+
ref,
|
|
10
|
+
watch,
|
|
11
|
+
useSlots,
|
|
12
|
+
computed,
|
|
13
|
+
provide,
|
|
14
|
+
onBeforeMount,
|
|
15
|
+
onMounted,
|
|
16
|
+
onUnmounted,
|
|
17
|
+
onDeactivated,
|
|
18
|
+
onActivated,
|
|
19
|
+
VNodeArrayChildren,
|
|
20
|
+
VNode
|
|
21
|
+
} from "vue";
|
|
22
|
+
import type { BreakPoint } from "./interface/index";
|
|
23
|
+
|
|
24
|
+
type Props = {
|
|
25
|
+
cols?: number | Record<BreakPoint, number>;
|
|
26
|
+
collapsed?: boolean;
|
|
27
|
+
collapsedRows?: number;
|
|
28
|
+
gap?: [number, number] | number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
32
|
+
cols: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
|
|
33
|
+
collapsed: false,
|
|
34
|
+
collapsedRows: 1,
|
|
35
|
+
gap: 0
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
onBeforeMount(() => props.collapsed && findIndex());
|
|
39
|
+
onMounted(() => {
|
|
40
|
+
resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent);
|
|
41
|
+
window.addEventListener("resize", resize);
|
|
42
|
+
});
|
|
43
|
+
onActivated(() => {
|
|
44
|
+
resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent);
|
|
45
|
+
window.addEventListener("resize", resize);
|
|
46
|
+
});
|
|
47
|
+
onUnmounted(() => {
|
|
48
|
+
window.removeEventListener("resize", resize);
|
|
49
|
+
});
|
|
50
|
+
onDeactivated(() => {
|
|
51
|
+
window.removeEventListener("resize", resize);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// 监听屏幕变化
|
|
55
|
+
const resize = (e: UIEvent) => {
|
|
56
|
+
let width = (e.target as Window).innerWidth;
|
|
57
|
+
switch (!!width) {
|
|
58
|
+
case width < 768:
|
|
59
|
+
breakPoint.value = "xs";
|
|
60
|
+
break;
|
|
61
|
+
case width >= 768 && width < 992:
|
|
62
|
+
breakPoint.value = "sm";
|
|
63
|
+
break;
|
|
64
|
+
case width >= 992 && width < 1200:
|
|
65
|
+
breakPoint.value = "md";
|
|
66
|
+
break;
|
|
67
|
+
case width >= 1200 && width < 1920:
|
|
68
|
+
breakPoint.value = "lg";
|
|
69
|
+
break;
|
|
70
|
+
case width >= 1920:
|
|
71
|
+
breakPoint.value = "xl";
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// 注入 gap 间距
|
|
77
|
+
provide("gap", Array.isArray(props.gap) ? props.gap[0] : props.gap);
|
|
78
|
+
|
|
79
|
+
// 注入响应式断点
|
|
80
|
+
let breakPoint = ref<BreakPoint>("xl");
|
|
81
|
+
provide("breakPoint", breakPoint);
|
|
82
|
+
|
|
83
|
+
// 注入要开始折叠的 index
|
|
84
|
+
const hiddenIndex = ref(-1);
|
|
85
|
+
provide("shouldHiddenIndex", hiddenIndex);
|
|
86
|
+
|
|
87
|
+
// 注入 cols
|
|
88
|
+
const gridCols = computed(() => {
|
|
89
|
+
if (typeof props.cols === "object") return props.cols[breakPoint.value] ?? props.cols;
|
|
90
|
+
return props.cols;
|
|
91
|
+
});
|
|
92
|
+
provide("cols", gridCols);
|
|
93
|
+
|
|
94
|
+
// 寻找需要开始折叠的字段 index
|
|
95
|
+
const slots = useSlots().default!();
|
|
96
|
+
|
|
97
|
+
const findIndex = () => {
|
|
98
|
+
let fields: VNodeArrayChildren = [];
|
|
99
|
+
let suffix: VNode | null = null;
|
|
100
|
+
slots.forEach((slot: any) => {
|
|
101
|
+
// suffix
|
|
102
|
+
if (typeof slot.type === "object" && slot.type.name === "GridItem" && slot.props?.suffix !== undefined) suffix = slot;
|
|
103
|
+
// slot children
|
|
104
|
+
if (typeof slot.type === "symbol" && Array.isArray(slot.children)) fields.push(...slot.children);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// 计算 suffix 所占用的列
|
|
108
|
+
let suffixCols = 0;
|
|
109
|
+
if (suffix) {
|
|
110
|
+
suffixCols =
|
|
111
|
+
((suffix as VNode).props![breakPoint.value]?.span ?? (suffix as VNode).props?.span ?? 1) +
|
|
112
|
+
((suffix as VNode).props![breakPoint.value]?.offset ?? (suffix as VNode).props?.offset ?? 0);
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
let find = false;
|
|
116
|
+
fields.reduce((prev = 0, current, index) => {
|
|
117
|
+
prev +=
|
|
118
|
+
((current as VNode)!.props![breakPoint.value]?.span ?? (current as VNode)!.props?.span ?? 1) +
|
|
119
|
+
((current as VNode)!.props![breakPoint.value]?.offset ?? (current as VNode)!.props?.offset ?? 0);
|
|
120
|
+
if (Number(prev) > props.collapsedRows * (gridCols.value as any) - suffixCols) {
|
|
121
|
+
hiddenIndex.value = index;
|
|
122
|
+
find = true;
|
|
123
|
+
throw "find it";
|
|
124
|
+
}
|
|
125
|
+
return prev;
|
|
126
|
+
}, 0);
|
|
127
|
+
if (!find) hiddenIndex.value = -1;
|
|
128
|
+
} catch (e) {
|
|
129
|
+
// console.warn(e);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// 断点变化时 执行 findIndex
|
|
134
|
+
watch(
|
|
135
|
+
() => breakPoint.value,
|
|
136
|
+
() => {
|
|
137
|
+
if (props.collapsed) findIndex();
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// 监听 collapsed
|
|
142
|
+
watch(
|
|
143
|
+
() => props.collapsed,
|
|
144
|
+
value => {
|
|
145
|
+
if (value) return findIndex();
|
|
146
|
+
hiddenIndex.value = -1;
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// 设置间距
|
|
151
|
+
const gridGap = computed(() => {
|
|
152
|
+
if (typeof props.gap === "number") return `${props.gap}px`;
|
|
153
|
+
if (Array.isArray(props.gap)) return `${props.gap[1]}px ${props.gap[0]}px`;
|
|
154
|
+
return "unset";
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// 设置 style
|
|
158
|
+
const style = computed(() => {
|
|
159
|
+
return {
|
|
160
|
+
display: "grid",
|
|
161
|
+
gridGap: gridGap.value,
|
|
162
|
+
gridTemplateColumns: `repeat(${gridCols.value}, minmax(0, 1fr))`
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
defineExpose({ breakPoint });
|
|
167
|
+
</script>
|