@neatui/vue 2.17.5 → 3.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/.prettierrc +12 -0
- package/README.md +82 -0
- package/package.json +7 -4
- package/src/components/basic/IFollowView.vue +47 -34
- package/src/components/basic/IPickerView.vue +16 -8
- package/src/components/basic/IRouterView.vue +133 -74
- package/src/components/basic/IScrollView.vue +40 -28
- package/src/components/basic/LayerView/Layer.vue +33 -24
- package/src/components/basic/LayerView/index.ts +3 -1
- package/src/components/form/DateView@v3.vue +2 -1
- package/src/components/form/Input@v3.vue +9 -6
- package/src/components/form/TextArea.vue +3 -3
- package/src/components/loader/FormLoader/FormLoader.vue +29 -4
- package/src/components/loader/LayerLoader/LayerLoader.vue +2 -1
- package/src/components/loader/LimitLoader/LimitLoader@v3.vue +3 -3
- package/src/components/loader/TableLoader/TableLoader.vue +3 -3
- package/src/components/loader/index.ts +1 -1
- package/src/components/tools/FormDraftsView.vue +15 -11
- package/src/components/tools/FormVerifyView.vue +4 -3
- package/src/components/tools/MoreTools.vue +4 -3
- package/src/components/tools/Pagination@a.vue +74 -38
- package/src/components/tools/Pagination@b.vue +2 -1
- package/src/store/frame.ts +18 -10
- package/src/utils/theme.ts +3 -0
- package/src/components/basic/follow.ts +0 -133
- package/src/components/loader/FormLoader/FormLoader@v2.vue +0 -422
- package/src/components/loader/FormLoader/NodeLoader.vue +0 -85
- package/src/components/loader/FormLoader@v1/FormLoader.vue +0 -506
- package/src/components/loader/FormLoader@v1/FormRender.vue +0 -302
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
:ui-form="`${theme} sz:${sz} ${state.error.co} type:input ${block ? ' :block' : ''} ${disabled ? 'disabled' : ''} ${readonly ? 'readonly' : ''}`"
|
|
3
|
+
:ui-form="`${theme} sz:${sz} ${state.error.co} type:input ${block ? ' :block' : ''} ${disabled ? 'disabled' : ''} ${readonly ? 'readonly' : ''} ${preview ? 'preview' : ''}`"
|
|
4
4
|
@click="emits('click', $event)"
|
|
5
5
|
>
|
|
6
6
|
<div v-if="state.interacted" :ui-form-tips="state.error.ontips">{{ state.error.tips }}</div>
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
:type="inputType"
|
|
21
21
|
:placeholder="placeholder"
|
|
22
22
|
:disabled="disabled"
|
|
23
|
-
:readonly="readonly"
|
|
24
|
-
autocomplete="new-password"
|
|
23
|
+
:readonly="readonly || preview"
|
|
25
24
|
@focus="emits('focus', $event, state)"
|
|
26
25
|
@input="input($event)"
|
|
27
26
|
@change="emits('change', $event, state)"
|
|
@@ -29,10 +28,10 @@
|
|
|
29
28
|
/>
|
|
30
29
|
</slot>
|
|
31
30
|
<div ui-form-suffix="">
|
|
32
|
-
<i ui-form-clean="" v-if="clearable && props.modelValue && !readonly && !disabled" @click.stop="clear"></i>
|
|
31
|
+
<i ui-form-clean="" v-if="clearable && props.modelValue && !readonly && !preview && !disabled" @click.stop="clear"></i>
|
|
33
32
|
<div
|
|
34
33
|
class="o-ls"
|
|
35
|
-
v-if="type === 'password' && cipher"
|
|
34
|
+
v-if="type === 'password' && cipher && !preview"
|
|
36
35
|
:ui-form-cipher="cipherVisible ? '1' : ''"
|
|
37
36
|
@click.stop="toggleCipher"
|
|
38
37
|
></div>
|
|
@@ -70,7 +69,9 @@
|
|
|
70
69
|
* minlength 最小长度
|
|
71
70
|
* disabled 是否禁用
|
|
72
71
|
* readonly 是否只读
|
|
72
|
+
* preview 是否预览展示
|
|
73
73
|
* clearable 是否显示清除按钮
|
|
74
|
+
* cipher 密码框是否显示明密文切换
|
|
74
75
|
* rules 校验规则
|
|
75
76
|
* verify 是否显示校验
|
|
76
77
|
* prefix 前缀内容
|
|
@@ -94,6 +95,7 @@
|
|
|
94
95
|
minlength?: null | number;
|
|
95
96
|
disabled?: boolean;
|
|
96
97
|
readonly?: boolean;
|
|
98
|
+
preview?: boolean;
|
|
97
99
|
clearable?: boolean;
|
|
98
100
|
cipher?: boolean;
|
|
99
101
|
verify?: boolean;
|
|
@@ -116,6 +118,7 @@
|
|
|
116
118
|
minlength: null,
|
|
117
119
|
disabled: false,
|
|
118
120
|
readonly: false,
|
|
121
|
+
preview: false,
|
|
119
122
|
clearable: true,
|
|
120
123
|
cipher: true,
|
|
121
124
|
verify: false,
|
|
@@ -217,7 +220,7 @@
|
|
|
217
220
|
}
|
|
218
221
|
|
|
219
222
|
const toggleCipher = () => {
|
|
220
|
-
if (props.disabled || props.readonly) return;
|
|
223
|
+
if (props.disabled || props.readonly || props.preview) return;
|
|
221
224
|
cipherVisible.value = !cipherVisible.value;
|
|
222
225
|
};
|
|
223
226
|
// 输入
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
verify?: boolean;
|
|
95
95
|
prefix?: Array<any>;
|
|
96
96
|
suffix?: Array<any>;
|
|
97
|
-
interacted?: boolean;
|
|
97
|
+
interacted?: boolean | number | null;
|
|
98
98
|
autoHeight?: boolean;
|
|
99
99
|
rows?: number;
|
|
100
100
|
}
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
verify: false,
|
|
117
117
|
prefix: () => [],
|
|
118
118
|
suffix: () => [],
|
|
119
|
-
interacted:
|
|
119
|
+
interacted: null,
|
|
120
120
|
// textarea 特有属性默认值
|
|
121
121
|
autoHeight: false,
|
|
122
122
|
rows: 3,
|
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
|
|
125
125
|
const state: any = reactive({
|
|
126
126
|
// 用户是否交互
|
|
127
|
-
interacted:
|
|
127
|
+
interacted: null,
|
|
128
128
|
// 值
|
|
129
129
|
value: props.modelValue,
|
|
130
130
|
// 错误
|
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
<template v-for="(col, idx) in state.form" :key="idx">
|
|
4
4
|
<div v-if="!col.clear" :id="col._fid" :ui-col="col.media" :ui-flex="`${vertical ? 'col xm' : 'row lt'}`">
|
|
5
5
|
<template v-if="col.label">
|
|
6
|
-
<h6 v-if="vertical" :style="`${col.rules?.some((validator: any) => validator.required) ? 'text-indent: -1em' : ''}`" class="
|
|
6
|
+
<h6 v-if="vertical" :style="`${col.rules?.some((validator: any) => validator.required) ? 'text-indent: -1em' : ''}`" :class="labelClass">
|
|
7
7
|
<template v-if="col.label == '*'"></template>
|
|
8
8
|
<template v-else>
|
|
9
9
|
<b v-if="col.rules?.some((validator: any) => validator.required)" class="co-risk pr" style="left: 0.1em">﹡</b>
|
|
10
|
-
<span>{{ col
|
|
10
|
+
<span>{{ formatLabel(col) }}</span>
|
|
11
11
|
</template>
|
|
12
12
|
</h6>
|
|
13
|
-
<p v-else :class="`${clh({ ...(col.model[1] || {}) })}`" :style="`width:${state._lw + 'em'}; max-width:50%;`" class="ar nr-sm flex-fixed o-ls">
|
|
13
|
+
<p v-else :class="[labelClass, `${clh({ ...(col.model[1] || {}) })}`]" :style="`width:${state._lw + 'em'}; max-width:50%;`" class="ar nr-sm flex-fixed o-ls">
|
|
14
14
|
<template v-if="col.label == '*'"></template>
|
|
15
15
|
<template v-else>
|
|
16
16
|
<b v-if="col.rules?.some((validator: any) => validator.required)" class="co-risk">﹡</b>
|
|
17
|
-
<span>{{ col
|
|
17
|
+
<span>{{ formatLabel(col) }}</span>
|
|
18
18
|
</template>
|
|
19
19
|
</p>
|
|
20
20
|
</template>
|
|
@@ -262,6 +262,16 @@
|
|
|
262
262
|
type: [String, Number],
|
|
263
263
|
default: '',
|
|
264
264
|
},
|
|
265
|
+
// label冒号控制,false不显示,string自定义字符
|
|
266
|
+
colon: {
|
|
267
|
+
type: [Boolean, String],
|
|
268
|
+
default: true,
|
|
269
|
+
},
|
|
270
|
+
// 统一的label class
|
|
271
|
+
labelClass: {
|
|
272
|
+
type: [String, Array, Object],
|
|
273
|
+
default: '',
|
|
274
|
+
},
|
|
265
275
|
// 是否垂直排列
|
|
266
276
|
vertical: {
|
|
267
277
|
type: Boolean,
|
|
@@ -269,6 +279,21 @@
|
|
|
269
279
|
},
|
|
270
280
|
});
|
|
271
281
|
|
|
282
|
+
// label展示文案,支持全局/单个字段开关冒号
|
|
283
|
+
const formatLabel = (col: any = {}) => {
|
|
284
|
+
const { label = '', colon: localColon = undefined } = col || {};
|
|
285
|
+
if (!label) return '';
|
|
286
|
+
if (/[::]\s*$/.test(label)) return label;
|
|
287
|
+
const suffix = (() => {
|
|
288
|
+
if (localColon === false) return '';
|
|
289
|
+
if (isString(localColon)) return localColon;
|
|
290
|
+
if (localColon === true) return ':';
|
|
291
|
+
if (isString(props.colon)) return props.colon;
|
|
292
|
+
return props.colon ? ':' : '';
|
|
293
|
+
})();
|
|
294
|
+
return suffix ? `${label}${suffix}` : label;
|
|
295
|
+
};
|
|
296
|
+
|
|
272
297
|
// 数据
|
|
273
298
|
const state: any = reactive({
|
|
274
299
|
form: [],
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
import { nextTick, reactive, ref, watch, onMounted } from 'vue';
|
|
77
77
|
import LayerLoader from './LayerLoader.vue';
|
|
78
78
|
import { useBasicStore, useWorksStore, storeToRefs } from '@/stores';
|
|
79
|
-
import { deepcopy } from '@fekit/utils';
|
|
79
|
+
import { deepcopy, env } from '@fekit/utils';
|
|
80
80
|
import scrollto from '@fekit/scrollto';
|
|
81
81
|
|
|
82
82
|
// 事件定义
|
|
@@ -131,6 +131,7 @@
|
|
|
131
131
|
unfoldActivePath(props.data, newActive);
|
|
132
132
|
|
|
133
133
|
nextTick(() => {
|
|
134
|
+
if (!env.client) return;
|
|
134
135
|
const el = document.getElementById(`LID${newActive.replace(/\./g, '')}`);
|
|
135
136
|
if (el && !store.isMouseInLayer) {
|
|
136
137
|
scrollto({
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
<script lang="ts" setup>
|
|
51
51
|
import { computed, reactive, watch } from 'vue';
|
|
52
52
|
import { useRoute } from 'vue-router';
|
|
53
|
-
import { deepcopy, isObject, isString } from '@fekit/utils';
|
|
53
|
+
import { deepcopy, env, isObject, isString } from '@fekit/utils';
|
|
54
54
|
import { IFollowView } from '../../basic';
|
|
55
55
|
import { Input, Select, SelectTree, MoreSelect, SearchMoreSelect, DatePicker, DateRangePicker, Cascader } from '../../form';
|
|
56
56
|
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
watch(
|
|
109
109
|
() => props.limit,
|
|
110
110
|
() => {
|
|
111
|
-
const _cache = JSON.parse(localStorage.getItem(`${state.name}_limit`) || '{}');
|
|
111
|
+
const _cache = env.client ? JSON.parse(window.localStorage.getItem(`${state.name}_limit`) || '{}') : {};
|
|
112
112
|
state.limit = deepcopy(props.limit)?.map((item: any) => {
|
|
113
113
|
if (_cache[item.field] && Object.prototype.hasOwnProperty.call(_cache[item.field], '_close')) {
|
|
114
114
|
item._close = Number(_cache[item.field]?._close);
|
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
});
|
|
162
|
-
localStorage.setItem(`${state.name}_limit`, JSON.stringify(_limit));
|
|
162
|
+
if (env.client) window.localStorage.setItem(`${state.name}_limit`, JSON.stringify(_limit));
|
|
163
163
|
},
|
|
164
164
|
{ deep: true },
|
|
165
165
|
);
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
import { IFollowView, Icon } from '../../basic';
|
|
153
153
|
import { itable } from '@fekit/itable';
|
|
154
154
|
import TableRender from './TableRender.vue';
|
|
155
|
-
import { isFunction, isObject, hasOwnProp, deepcopy } from '@fekit/utils';
|
|
155
|
+
import { isFunction, isObject, hasOwnProp, deepcopy, env } from '@fekit/utils';
|
|
156
156
|
|
|
157
157
|
const route = useRoute();
|
|
158
158
|
const router = useRouter();
|
|
@@ -264,7 +264,7 @@
|
|
|
264
264
|
watch(
|
|
265
265
|
() => props.table,
|
|
266
266
|
() => {
|
|
267
|
-
const _cache = JSON.parse(localStorage.getItem(`${name}_table`) || '{}');
|
|
267
|
+
const _cache = env.client ? JSON.parse(window.localStorage.getItem(`${name}_table`) || '{}') : {};
|
|
268
268
|
state.__last = 0;
|
|
269
269
|
state.table = deepcopy(props.table || [])
|
|
270
270
|
?.map((col: TableColumn, idx: number) => {
|
|
@@ -340,7 +340,7 @@
|
|
|
340
340
|
state.__last = idx;
|
|
341
341
|
}
|
|
342
342
|
});
|
|
343
|
-
localStorage.setItem(`${name}_table`, JSON.stringify(_table));
|
|
343
|
+
if (env.client) window.localStorage.setItem(`${name}_table`, JSON.stringify(_table));
|
|
344
344
|
},
|
|
345
345
|
{ deep: true },
|
|
346
346
|
);
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
import { ref, reactive, watch, onMounted, onUnmounted, computed } from 'vue';
|
|
42
42
|
import { useRoute } from 'vue-router';
|
|
43
43
|
import scrollTo from '@fekit/scrollto';
|
|
44
|
-
import { diff, deepcopy, isArray } from '@fekit/utils';
|
|
44
|
+
import { diff, deepcopy, env, isArray } from '@fekit/utils';
|
|
45
45
|
|
|
46
46
|
// 通过枚举值查找枚举名
|
|
47
47
|
const findEnumName = (enums: any, val: any, join: boolean | string = ' Ι ') => {
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
const route: any = useRoute();
|
|
70
70
|
const props: any = defineProps({
|
|
71
71
|
area: {
|
|
72
|
-
type:
|
|
73
|
-
default: document.body
|
|
72
|
+
type: Object,
|
|
73
|
+
default: () => (env.client ? document.body : null)
|
|
74
74
|
},
|
|
75
75
|
// 数据
|
|
76
76
|
data: {
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
// 对比数据
|
|
102
102
|
diffs: computed(() => {
|
|
103
103
|
const cloud = props.data || {};
|
|
104
|
-
const local = JSON.parse(localStorage.getItem(state.cacheKey) || '{}');
|
|
104
|
+
const local = env.client ? JSON.parse(window.localStorage.getItem(state.cacheKey) || '{}') : {};
|
|
105
105
|
|
|
106
106
|
const diffs = Object.keys(local).length
|
|
107
107
|
? (diff(cloud, local) || []).map((item: any) => {
|
|
@@ -162,6 +162,7 @@
|
|
|
162
162
|
|
|
163
163
|
// 定位字段
|
|
164
164
|
const fScrollToField = (domId: any) => {
|
|
165
|
+
if (!env.client || !props.area) return;
|
|
165
166
|
const dom = document.querySelector(domId);
|
|
166
167
|
if (dom) {
|
|
167
168
|
scrollTo({
|
|
@@ -182,7 +183,8 @@
|
|
|
182
183
|
|
|
183
184
|
// 使用草稿
|
|
184
185
|
const fConfirmDrafts = ({ field = '', path = [] }: any = {}) => {
|
|
185
|
-
|
|
186
|
+
if (!env.client) return;
|
|
187
|
+
const local = JSON.parse(window.localStorage.getItem(state.cacheKey) || '{}');
|
|
186
188
|
if (field) {
|
|
187
189
|
state.field = field;
|
|
188
190
|
// 无限树型遍历
|
|
@@ -221,7 +223,8 @@
|
|
|
221
223
|
};
|
|
222
224
|
// 放弃草稿
|
|
223
225
|
const fDiscardDrafts = ({ field = '', path = [] }: any = {}) => {
|
|
224
|
-
|
|
226
|
+
if (!env.client) return;
|
|
227
|
+
const local = JSON.parse(window.localStorage.getItem(state.cacheKey) || '{}');
|
|
225
228
|
if (field) {
|
|
226
229
|
// 无限树型遍历
|
|
227
230
|
let _cloud = state.cloud || {};
|
|
@@ -232,7 +235,7 @@
|
|
|
232
235
|
});
|
|
233
236
|
_local[field] = _cloud[field];
|
|
234
237
|
|
|
235
|
-
localStorage.setItem(state.cacheKey, JSON.stringify(local));
|
|
238
|
+
window.localStorage.setItem(state.cacheKey, JSON.stringify(local));
|
|
236
239
|
emits('discard', field);
|
|
237
240
|
} else {
|
|
238
241
|
state.diffs.forEach((item: any) => {
|
|
@@ -252,7 +255,7 @@
|
|
|
252
255
|
}
|
|
253
256
|
state.show = 0;
|
|
254
257
|
});
|
|
255
|
-
localStorage.setItem(state.cacheKey, JSON.stringify(local));
|
|
258
|
+
window.localStorage.setItem(state.cacheKey, JSON.stringify(local));
|
|
256
259
|
emits('discard');
|
|
257
260
|
state.show = 0;
|
|
258
261
|
}
|
|
@@ -312,14 +315,15 @@
|
|
|
312
315
|
watch(
|
|
313
316
|
() => props.data,
|
|
314
317
|
(edit: any, old: any) => {
|
|
318
|
+
if (!env.client) return;
|
|
315
319
|
if (edit === old) {
|
|
316
320
|
if (state.field) {
|
|
317
|
-
const local = JSON.parse(localStorage.getItem(state.cacheKey) || '{}');
|
|
321
|
+
const local = JSON.parse(window.localStorage.getItem(state.cacheKey) || '{}');
|
|
318
322
|
local[state.field] = state.cloud[state.field];
|
|
319
|
-
localStorage.setItem(state.cacheKey, JSON.stringify(local));
|
|
323
|
+
window.localStorage.setItem(state.cacheKey, JSON.stringify(local));
|
|
320
324
|
state.field = null;
|
|
321
325
|
} else {
|
|
322
|
-
localStorage.setItem(state.cacheKey, JSON.stringify(edit));
|
|
326
|
+
window.localStorage.setItem(state.cacheKey, JSON.stringify(edit));
|
|
323
327
|
}
|
|
324
328
|
}
|
|
325
329
|
},
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
import { computed, reactive, toRefs, watch, onMounted, ref, onUnmounted } from 'vue';
|
|
32
32
|
import { useRoute } from 'vue-router';
|
|
33
33
|
import scrollTo from '@fekit/scrollto';
|
|
34
|
-
import { deepcopy, isArray, isObject, isFunction } from '@fekit/utils';
|
|
34
|
+
import { deepcopy, env, isArray, isObject, isFunction } from '@fekit/utils';
|
|
35
35
|
|
|
36
36
|
const route: any = useRoute();
|
|
37
37
|
|
|
38
38
|
const props: any = defineProps({
|
|
39
39
|
area: {
|
|
40
|
-
type:
|
|
41
|
-
default: document.body
|
|
40
|
+
type: Object,
|
|
41
|
+
default: () => (env.client ? document.body : null)
|
|
42
42
|
},
|
|
43
43
|
data: {
|
|
44
44
|
type: Object,
|
|
@@ -128,6 +128,7 @@
|
|
|
128
128
|
);
|
|
129
129
|
|
|
130
130
|
const fScrollToField = (path: any) => {
|
|
131
|
+
if (!env.client || !props.area) return;
|
|
131
132
|
const dom = document.querySelector(path);
|
|
132
133
|
if (dom) {
|
|
133
134
|
scrollTo({
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
<script setup lang="ts">
|
|
31
31
|
import { reactive, computed } from 'vue';
|
|
32
32
|
import { useRoute } from 'vue-router';
|
|
33
|
+
import { env } from '@fekit/utils';
|
|
33
34
|
const emits = defineEmits(['update:confs', 'update:param']);
|
|
34
35
|
const props = defineProps({
|
|
35
36
|
confs: {
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
});
|
|
53
54
|
|
|
54
55
|
const init = () => {
|
|
55
|
-
const _cache = JSON.parse(localStorage.getItem(`${state.name}`) || '{}');
|
|
56
|
+
const _cache = env.client ? JSON.parse(window.localStorage.getItem(`${state.name}`) || '{}') : {};
|
|
56
57
|
state.tools.confs = _cache.confs || JSON.parse(JSON.stringify(props.confs));
|
|
57
58
|
state.tools.param = _cache.param || JSON.parse(JSON.stringify(props.param));
|
|
58
59
|
emits('update:confs', state.tools.confs);
|
|
@@ -63,12 +64,12 @@
|
|
|
63
64
|
const setLinkMode = (linkMode: any) => {
|
|
64
65
|
state.tools.confs.linkMode = linkMode;
|
|
65
66
|
emits('update:confs', state.tools.confs);
|
|
66
|
-
localStorage.setItem(state.name, JSON.stringify(state.tools));
|
|
67
|
+
if (env.client) window.localStorage.setItem(state.name, JSON.stringify(state.tools));
|
|
67
68
|
};
|
|
68
69
|
|
|
69
70
|
const setPageSize = (pageSize: any) => {
|
|
70
71
|
state.tools.param.pageSize = pageSize;
|
|
71
72
|
emits('update:param', state.tools.param);
|
|
72
|
-
localStorage.setItem(state.name, JSON.stringify(state.tools));
|
|
73
|
+
if (env.client) window.localStorage.setItem(state.name, JSON.stringify(state.tools));
|
|
73
74
|
};
|
|
74
75
|
</script>
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
<div ui-pagination="@a" class="w-full pading" ui-flex="row rm">
|
|
3
3
|
<div class="ml-sl ar mob-ac" ui-flex="row rm">
|
|
4
4
|
<div class="mx-ss-sub" ui-flex="row">
|
|
5
|
-
<div v-if="state.showTotal" ui-flex="row cm" ui-hide="<pad">共 {{
|
|
6
|
-
<button :ui-btn="`@a s read :border :square ${
|
|
5
|
+
<div v-if="state.showTotal" ui-flex="row cm" ui-hide="<pad">共 {{ total }} 条</div>
|
|
6
|
+
<button :ui-btn="`@a s read :border :square ${currentPage > 1 ? '' : ':disabled'}`" :disabled="currentPage <= 1" @click="prev">
|
|
7
7
|
<svg
|
|
8
8
|
style="width: 1em; height: 1em"
|
|
9
9
|
t="1701228540398"
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
</button>
|
|
24
24
|
<div ui-tips="@a co:well ux:hover">
|
|
25
25
|
<div ui-btn="@a s read :border">
|
|
26
|
-
<code>{{
|
|
26
|
+
<code>{{ displayPage }}/{{ totalPages }}</code>
|
|
27
27
|
<svg
|
|
28
28
|
style="width: 1em; height: 1em"
|
|
29
29
|
t="1701229420486"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
<div class="w-mm max-h-ls" ui-scroll=":y s">
|
|
46
46
|
<ul class="n-sl n-ss-sub dib-sub">
|
|
47
47
|
<li v-for="item in state.lists" :key="item">
|
|
48
|
-
<button :ui-btn="`@a s ${item ===
|
|
48
|
+
<button :ui-btn="`@a s ${item === currentPage ? 'main' : 'read :border'}`" @click="page(item)">
|
|
49
49
|
<code>{{ item }}</code>
|
|
50
50
|
</button>
|
|
51
51
|
</li>
|
|
@@ -53,7 +53,11 @@
|
|
|
53
53
|
</div>
|
|
54
54
|
</div>
|
|
55
55
|
</div>
|
|
56
|
-
<button
|
|
56
|
+
<button
|
|
57
|
+
:ui-btn="`@a s read :border :square ${currentPage < totalPages ? '' : ':disabled'}`"
|
|
58
|
+
:disabled="currentPage >= totalPages"
|
|
59
|
+
@click="next"
|
|
60
|
+
>
|
|
57
61
|
<svg style="width: 1em; height: 1em" class="icon co-read" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2426" width="64" height="64">
|
|
58
62
|
<path
|
|
59
63
|
d="M341.63640988 781.41564784c20.63742578 21.22981847 54.12571728 21.22981847 74.81456101 0l265.95100164-273.75421824c20.62728703-21.26747667 20.62728703-55.75008988 0-76.97556317l-265.95100164-273.74407946c-20.68449855-21.26747667-54.17423844-21.26747667-74.81456101 0-20.67291141 21.23706043-20.67291142 55.71170748 0 76.98497771l228.55639458 235.24869384-228.55567038 235.22117436c-20.67218722 21.26747667-20.67146302 55.74357212 0 77.02263594l0 0z"
|
|
@@ -94,10 +98,9 @@
|
|
|
94
98
|
<li>
|
|
95
99
|
<h6 class="fs-xs o-mm nowrap">每页显示数量</h6>
|
|
96
100
|
<ul>
|
|
97
|
-
<li @click="size(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
<li @click="size(100)" class="ux-hover r-sm"><code :class="`${modelValue.pageSize === 100 ? 'co-main' : ''}`">100条/页</code></li>
|
|
101
|
+
<li v-for="item in pageSizeOptions" :key="item" @click="size(item)" class="ux-hover r-sm">
|
|
102
|
+
<code :class="`${pageSize === item ? 'co-main' : ''}`">{{ item }}条/页</code>
|
|
103
|
+
</li>
|
|
101
104
|
</ul>
|
|
102
105
|
</li>
|
|
103
106
|
</ul>
|
|
@@ -111,6 +114,7 @@
|
|
|
111
114
|
<script setup lang="ts">
|
|
112
115
|
import { reactive, computed } from 'vue';
|
|
113
116
|
import { useRoute } from 'vue-router';
|
|
117
|
+
import { env } from '@fekit/utils';
|
|
114
118
|
|
|
115
119
|
interface ModelValueProps {
|
|
116
120
|
pageSize?: number;
|
|
@@ -141,6 +145,8 @@
|
|
|
141
145
|
modelValue: ModelValueProps;
|
|
142
146
|
data: DataProps;
|
|
143
147
|
defined?: DefinedProps;
|
|
148
|
+
pageSizes?: number[];
|
|
149
|
+
pageSizeOptions?: number[];
|
|
144
150
|
}
|
|
145
151
|
|
|
146
152
|
const route: any = useRoute();
|
|
@@ -149,9 +155,16 @@
|
|
|
149
155
|
modelValue: () => ({}),
|
|
150
156
|
data: () => ({}),
|
|
151
157
|
defined: () => ({}),
|
|
158
|
+
pageSizes: () => [],
|
|
159
|
+
pageSizeOptions: () => [10, 20, 50, 100],
|
|
152
160
|
});
|
|
153
161
|
|
|
154
162
|
const emits = defineEmits(['update:modelValue', 'page', 'size']);
|
|
163
|
+
const toNumber = (value: any, fallback = 0) => {
|
|
164
|
+
const number = Number(value);
|
|
165
|
+
return Number.isFinite(number) ? number : fallback;
|
|
166
|
+
};
|
|
167
|
+
|
|
155
168
|
// Auto-detect common pagination keys when `defined` is omitted.
|
|
156
169
|
const defined = computed<DefinedProps>(() => {
|
|
157
170
|
const result: DefinedProps = { ...(props.defined || {}) };
|
|
@@ -175,14 +188,48 @@
|
|
|
175
188
|
|
|
176
189
|
return result;
|
|
177
190
|
});
|
|
191
|
+
const fields = computed(() => ({
|
|
192
|
+
page: defined.value.page || 'page',
|
|
193
|
+
totalPages: defined.value.totalPages || 'totalPages',
|
|
194
|
+
total: defined.value.total || 'total',
|
|
195
|
+
pageSize: defined.value.pageSize || 'pageSize',
|
|
196
|
+
}));
|
|
197
|
+
|
|
198
|
+
const totalPages = computed(() => Math.max(0, Math.floor(toNumber(props.data[fields.value.totalPages], 0))));
|
|
199
|
+
const total = computed(() => Math.max(0, Math.floor(toNumber(props.data[fields.value.total], 0))));
|
|
200
|
+
const currentPage = computed(() => {
|
|
201
|
+
const page = Math.max(1, Math.floor(toNumber(props.data[fields.value.page] ?? props.modelValue[fields.value.page], 1)));
|
|
202
|
+
return totalPages.value > 0 ? Math.min(page, totalPages.value) : 1;
|
|
203
|
+
});
|
|
204
|
+
const displayPage = computed(() => (totalPages.value > 0 ? currentPage.value : 0));
|
|
205
|
+
const pageSize = computed(() => Math.max(0, Math.floor(toNumber(props.modelValue[fields.value.pageSize] ?? props.data[fields.value.pageSize], 0))));
|
|
206
|
+
const pageSizeOptions = computed(() => {
|
|
207
|
+
const options = Array.isArray(props.pageSizes) && props.pageSizes.length ? props.pageSizes : props.pageSizeOptions;
|
|
208
|
+
return options.filter((item: any) => Number.isFinite(Number(item)) && Number(item) > 0).map((item: any) => Number(item));
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const normalizePage = (value: any) => {
|
|
212
|
+
const page = Math.max(1, Math.floor(toNumber(value, currentPage.value)));
|
|
213
|
+
return totalPages.value > 0 ? Math.min(page, totalPages.value) : page;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const emitPage = (value: any) => {
|
|
217
|
+
if (totalPages.value <= 0) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const nextPage = normalizePage(value);
|
|
221
|
+
emits('update:modelValue', { ...props.modelValue, [fields.value.page]: nextPage });
|
|
222
|
+
emits('page', nextPage);
|
|
223
|
+
};
|
|
178
224
|
|
|
179
225
|
const state: any = reactive({
|
|
180
226
|
id: computed(() => (props.id || 'PAGINATION' + route.path?.replace(/\//g, '_')).toUpperCase()),
|
|
181
227
|
showTotal: true,
|
|
182
228
|
list: computed(() => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
229
|
+
if (totalPages.value <= 0) {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
const _page = currentPage.value;
|
|
186
233
|
const list = [_page];
|
|
187
234
|
if (_page > 1) {
|
|
188
235
|
list.unshift(_page - 1);
|
|
@@ -192,7 +239,7 @@
|
|
|
192
239
|
}
|
|
193
240
|
const _max = 5 - list.length;
|
|
194
241
|
for (let i = 1; i <= _max; i++) {
|
|
195
|
-
if (_page + i <=
|
|
242
|
+
if (_page + i <= totalPages.value) {
|
|
196
243
|
list.push(_page + i);
|
|
197
244
|
}
|
|
198
245
|
}
|
|
@@ -205,9 +252,8 @@
|
|
|
205
252
|
return list;
|
|
206
253
|
}),
|
|
207
254
|
lists: computed(() => {
|
|
208
|
-
const totalPagesKey = defined.value.totalPages || 'totalPages';
|
|
209
255
|
const _lists: any = [];
|
|
210
|
-
for (let i = 1; i <=
|
|
256
|
+
for (let i = 1; i <= totalPages.value; i++) {
|
|
211
257
|
_lists.push(i);
|
|
212
258
|
}
|
|
213
259
|
return _lists;
|
|
@@ -215,39 +261,29 @@
|
|
|
215
261
|
});
|
|
216
262
|
|
|
217
263
|
const prev = () => {
|
|
218
|
-
|
|
219
|
-
const _page = Number(props.data[pageKey]) - 1 < 1 ? 1 : Number(props.data[pageKey]) - 1;
|
|
220
|
-
emits('update:modelValue', { ...props.modelValue, ...{ [pageKey]: _page } });
|
|
221
|
-
emits('page', _page);
|
|
264
|
+
emitPage(currentPage.value - 1);
|
|
222
265
|
};
|
|
223
266
|
|
|
224
267
|
const next = () => {
|
|
225
|
-
|
|
226
|
-
const totalPagesKey = defined.value.totalPages || 'totalPages';
|
|
227
|
-
const _page =
|
|
228
|
-
Number(props.data[pageKey]) + 1 > props.data[totalPagesKey]
|
|
229
|
-
? props.data[totalPagesKey]
|
|
230
|
-
: Number(props.data[pageKey]) + 1;
|
|
231
|
-
emits('update:modelValue', { ...props.modelValue, ...{ [pageKey]: _page } });
|
|
232
|
-
emits('page', _page);
|
|
268
|
+
emitPage(currentPage.value + 1);
|
|
233
269
|
};
|
|
234
270
|
|
|
235
271
|
const page = (idx: any) => {
|
|
236
|
-
|
|
237
|
-
emits('update:modelValue', { ...props.modelValue, ...{ [pageKey]: idx } });
|
|
238
|
-
emits('page', idx);
|
|
272
|
+
emitPage(idx);
|
|
239
273
|
};
|
|
240
274
|
|
|
241
|
-
const size = (
|
|
242
|
-
const
|
|
243
|
-
emits('update:modelValue', { ...props.modelValue,
|
|
244
|
-
emits('size',
|
|
245
|
-
window.localStorage.setItem(state.id, `${
|
|
275
|
+
const size = (value: any) => {
|
|
276
|
+
const nextSize = Math.max(1, Math.floor(toNumber(value, pageSize.value || 10)));
|
|
277
|
+
emits('update:modelValue', { ...props.modelValue, [fields.value.pageSize]: nextSize });
|
|
278
|
+
emits('size', nextSize);
|
|
279
|
+
if (env.client) window.localStorage.setItem(state.id, `${nextSize}`);
|
|
246
280
|
};
|
|
247
281
|
|
|
248
282
|
// 缓存
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
283
|
+
if (env.client) {
|
|
284
|
+
const cache = window.localStorage.getItem(state.id);
|
|
285
|
+
if (cache) {
|
|
286
|
+
size(Number(cache));
|
|
287
|
+
}
|
|
252
288
|
}
|
|
253
289
|
</script>
|
|
@@ -127,6 +127,7 @@
|
|
|
127
127
|
<script setup lang="ts">
|
|
128
128
|
import { reactive, computed } from 'vue';
|
|
129
129
|
import { useRoute } from 'vue-router';
|
|
130
|
+
import { env } from '@fekit/utils';
|
|
130
131
|
|
|
131
132
|
const route: any = useRoute();
|
|
132
133
|
|
|
@@ -215,6 +216,6 @@
|
|
|
215
216
|
const size = (size: any) => {
|
|
216
217
|
emits('update:modelValue', { ...props.modelValue, ...{ pageSize: size } });
|
|
217
218
|
emits('size', size);
|
|
218
|
-
window.localStorage.setItem(state.id, `${size}`);
|
|
219
|
+
if (env.client) window.localStorage.setItem(state.id, `${size}`);
|
|
219
220
|
};
|
|
220
221
|
</script>
|