@neatui/vue 2.17.3 → 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.
Files changed (29) hide show
  1. package/.prettierrc +12 -0
  2. package/README.md +82 -0
  3. package/package.json +7 -4
  4. package/src/components/basic/IFollowView.vue +47 -34
  5. package/src/components/basic/IPickerView.vue +16 -8
  6. package/src/components/basic/IRouterView.vue +133 -74
  7. package/src/components/basic/IScrollView.vue +110 -93
  8. package/src/components/basic/LayerView/Layer.vue +33 -24
  9. package/src/components/basic/LayerView/index.ts +3 -1
  10. package/src/components/form/DateView@v3.vue +2 -1
  11. package/src/components/form/Input@v3.vue +9 -6
  12. package/src/components/form/{Textarea.vue → TextArea.vue} +3 -3
  13. package/src/components/loader/FormLoader/FormLoader.vue +29 -4
  14. package/src/components/loader/LayerLoader/LayerLoader.vue +2 -1
  15. package/src/components/loader/LimitLoader/LimitLoader@v3.vue +3 -3
  16. package/src/components/loader/TableLoader/TableLoader.vue +3 -3
  17. package/src/components/loader/index.ts +1 -1
  18. package/src/components/tools/FormDraftsView.vue +15 -11
  19. package/src/components/tools/FormVerifyView.vue +4 -3
  20. package/src/components/tools/MoreTools.vue +4 -3
  21. package/src/components/tools/Pagination@a.vue +74 -38
  22. package/src/components/tools/Pagination@b.vue +2 -1
  23. package/src/store/frame.ts +18 -10
  24. package/src/utils/theme.ts +3 -0
  25. package/src/components/basic/follow.ts +0 -133
  26. package/src/components/loader/FormLoader/FormLoader@v2.vue +0 -422
  27. package/src/components/loader/FormLoader/NodeLoader.vue +0 -85
  28. package/src/components/loader/FormLoader@v1/FormLoader.vue +0 -506
  29. package/src/components/loader/FormLoader@v1/FormRender.vue +0 -302
@@ -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">共 {{ data[defined.total || 'total'] || 0 }} 条</div>
6
- <button :ui-btn="`@a s read :border :square ${data[defined.page || 'page'] > 1 ? '' : ':disabled'}`" @click="prev">
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>{{ data[defined.page || 'page'] || 0 }}/{{ data[defined.totalPages || 'totalPages'] || 0 }}</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 === data[defined.page || 'page'] ? 'main' : 'read :border'}`" @click="page(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 :ui-btn="`@a s read :border :square ${data[defined.page || 'page'] < data[defined.totalPages || 'totalPages'] ? '' : ':disabled'}`" @click="next">
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(10)" class="ux-hover r-sm"><code :class="`${modelValue.pageSize === 10 ? 'co-main' : ''}`">10条/页</code></li>
98
- <li @click="size(20)" class="ux-hover r-sm"><code :class="`${modelValue.pageSize === 20 ? 'co-main' : ''}`">20条/页</code></li>
99
- <li @click="size(50)" class="ux-hover r-sm"><code :class="`${modelValue.pageSize === 50 ? 'co-main' : ''}`">50条/页</code></li>
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
- const pageKey = defined.value.page || 'page';
184
- const totalPagesKey = defined.value.totalPages || 'totalPages';
185
- const _page = Number(props.data[pageKey] || 1);
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 <= props.data[totalPagesKey]) {
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 <= props.data[totalPagesKey]; 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
- const pageKey = defined.value.page || 'page';
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
- const pageKey = defined.value.page || 'page';
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
- const pageKey = defined.value.page || 'page';
237
- emits('update:modelValue', { ...props.modelValue, ...{ [pageKey]: idx } });
238
- emits('page', idx);
272
+ emitPage(idx);
239
273
  };
240
274
 
241
- const size = (size: any) => {
242
- const pageSizeKey = defined.value.pageSize || 'pageSize';
243
- emits('update:modelValue', { ...props.modelValue, ...{ [pageSizeKey]: size } });
244
- emits('size', size);
245
- window.localStorage.setItem(state.id, `${size}`);
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
- const cache = window.localStorage.getItem(state.id);
250
- if (cache) {
251
- size(Number(cache));
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>
@@ -1,14 +1,22 @@
1
1
  import { defineStore } from 'pinia';
2
+ import { env } from '@fekit/utils';
3
+
4
+ const getStorageItem = (key: string) => (env.client ? window.localStorage.getItem(key) : null);
5
+ const setStorageItem = (key: string, value: string) => {
6
+ if (env.client) {
7
+ window.localStorage.setItem(key, value);
8
+ }
9
+ };
2
10
 
3
11
  // 基础数据
4
12
  export const useFrameStore = defineStore('frame', {
5
13
  state: () => {
6
14
  return {
7
- full: localStorage.getItem('neatui_full') || 0,
15
+ full: getStorageItem('neatui_full') || 0,
8
16
  // 字号
9
- size: localStorage.getItem('neatui_size') || document?.documentElement?.getAttribute('size') || 'm',
17
+ size: getStorageItem('neatui_size') || (env.client ? document.documentElement.getAttribute('size') : null) || 'm',
10
18
  // 主题
11
- theme: localStorage.getItem('neatui_theme') || document?.documentElement?.getAttribute('theme') || 'auto',
19
+ theme: getStorageItem('neatui_theme') || (env.client ? document.documentElement.getAttribute('theme') : null) || 'auto',
12
20
  // 侧边
13
21
  side: 1,
14
22
  };
@@ -19,30 +27,30 @@ export const useFrameStore = defineStore('frame', {
19
27
  async setfull(then = (res: any) => {}) {
20
28
  const full = this.full ? 0 : 1;
21
29
  this.full = full;
22
- localStorage.setItem('neatui_full', `${full}`);
30
+ setStorageItem('neatui_full', `${full}`);
23
31
  then(full);
24
32
  },
25
33
  // 设置系统主题
26
34
  async settheme(theme: any = '', then = (res: any) => {}) {
27
35
  if (theme) {
28
- localStorage.setItem('neatui_theme', theme);
36
+ setStorageItem('neatui_theme', theme);
29
37
  this.theme = theme;
30
- document.documentElement.setAttribute('theme', theme);
38
+ if (env.client) document.documentElement.setAttribute('theme', theme);
31
39
  then(theme);
32
40
  } else {
33
- document.documentElement.setAttribute('theme', this.theme);
41
+ if (env.client) document.documentElement.setAttribute('theme', this.theme);
34
42
  then(this.theme);
35
43
  }
36
44
  },
37
45
  // 设置系统字号
38
46
  async setsize(size: any = '', then = (res: any) => {}) {
39
47
  if (size) {
40
- localStorage.setItem('neatui_size', size);
48
+ setStorageItem('neatui_size', size);
41
49
  this.size = size;
42
- document.documentElement.setAttribute('size', size);
50
+ if (env.client) document.documentElement.setAttribute('size', size);
43
51
  then(size);
44
52
  } else {
45
- document.documentElement.setAttribute('size', this.size);
53
+ if (env.client) document.documentElement.setAttribute('size', this.size);
46
54
  then(this.size);
47
55
  }
48
56
  },
@@ -1,5 +1,8 @@
1
+ import { env } from '@fekit/utils';
2
+
1
3
  // 主题
2
4
  export const theme = (then: (theme: string) => void = () => {}) => {
5
+ if (!env.client) return;
3
6
  // 自动设置主题
4
7
  const auto = (scheme: any) => {
5
8
  const _theme = window.localStorage.getItem('theme') || (scheme.matches ? 'dark' : 'light');
@@ -1,133 +0,0 @@
1
- import ResizeObserver from 'resize-observer-polyfill';
2
- import raf from 'raf';
3
-
4
- class Follow {
5
- box: any[];
6
- dom: any;
7
- tip: any;
8
- ing: any;
9
- sub: any;
10
- obs: any;
11
- fun: (status: any) => void;
12
- private css: () => void;
13
- z: number;
14
- constructor(dom: any, tip: any, fun = (_status: any) => {}) {
15
- this.dom = dom;
16
- this.tip = tip;
17
- this.box = [];
18
- this.css = this.setcss.bind(this);
19
- this.fun = fun;
20
- this.sub = this.tip.firstElementChild;
21
- this.z = 0;
22
- }
23
- setcss() {
24
- if (!this.ing) {
25
- raf(() => {
26
- this.ing = false;
27
- const dr = this.dom?.getBoundingClientRect() || { width: 0, height: 0, top: 0, bottom: 0, left: 0, right: 0 };
28
- const sr = this.sub?.getBoundingClientRect() || { height: 0 };
29
- this.tip.style.inset = `${dr.top}px auto auto ${dr.left}px`;
30
- this.tip.style.width = dr.width + 'px';
31
- this.tip.style.height = dr.height + 'px';
32
- this.fun('');
33
- this.box.forEach((e: any) => {
34
- const _ar = { top: 0, bottom: window.innerHeight, left: 0, right: window.innerWidth };
35
- const ar = e === window ? _ar : e?.getBoundingClientRect() || _ar;
36
- if (dr.bottom < ar.top || dr.top > ar.bottom || dr.right < ar.left || dr.left > ar.right) {
37
- this.tip.style.opacity = '0';
38
- this.sub.style.pointerEvents = 'none';
39
- } else {
40
- this.tip.style.opacity = '';
41
- this.sub.style.pointerEvents = '';
42
- }
43
- if (dr.bottom + sr.height + 10 > ar.bottom) {
44
- this.fun('b');
45
- }
46
- });
47
- const tr = this.tip?.getBoundingClientRect() || { left: 0 };
48
- const x = this.sub.offsetLeft + tr.left;
49
- const arrow = this.sub.querySelector('[ui-tips-arrow]');
50
- if (x < 0) {
51
- this.sub.style.transform = `translate3d(${-x}px,0,0)`;
52
- if (arrow && arrow.style) {
53
- arrow.style.transform = `translateX(${x}px) rotate(45deg)`;
54
- }
55
- } else {
56
- this.sub.style.transform = '';
57
- if (arrow && arrow.style) {
58
- arrow.style.transform = '';
59
- }
60
- }
61
- });
62
- this.ing = true;
63
- }
64
- }
65
- listen() {
66
- // 向上查询有滚动条的元素
67
- const _find = (e: any) => {
68
- // 判断是否为DOM节点
69
- if (e) {
70
- if (e.nodeType === 1) {
71
- const css: any = window.getComputedStyle(e);
72
- // 查询所有父组节点的最高层级
73
- if (!isNaN(css.zIndex) && css.zIndex >= this.z) {
74
- this.z = Number(css.zIndex) + 10;
75
- } else {
76
- this.z += 1;
77
- }
78
- // 节点是否有滚动条
79
- if (
80
- (css.overflowY === 'auto' || css.overflowY === 'scroll' || css.overflowX === 'auto' || css.overflowX === 'scroll') &&
81
- (e.scrollHeight > e.clientHeight || e.scrollWidth > e.clientWidth)
82
- ) {
83
- this.box.push(e);
84
- }
85
- _find(e.parentNode);
86
- } else if (e.nodeType === 9) {
87
- console.log(42, e, e.nodeType);
88
- this.box.push(window);
89
- }
90
- }
91
- };
92
- _find(this.dom);
93
-
94
- // 自动层级
95
- if (window.getComputedStyle(this.tip).zIndex) {
96
- this.tip.style.zIndex = `${this.z}`;
97
- }
98
-
99
- this.css();
100
- // 监听窗口尺寸变化
101
- window.addEventListener('resize', this.css);
102
- window.addEventListener('click', this.css);
103
- // 监听滚动条的元素
104
- this.box.forEach((e: any) => {
105
- e.addEventListener('scroll', this.css);
106
- });
107
- // 监听移入事件
108
- this.dom?.addEventListener('mouseenter', this.css);
109
- // 监听参考元素变化
110
- this.obs = new ResizeObserver(this.css);
111
- if (this.dom) {
112
- this.obs?.observe(this.dom);
113
- }
114
- if (this.sub) {
115
- this.obs?.observe(this.sub);
116
- }
117
- }
118
- remove() {
119
- // console.log('移除监听');
120
- // 清除窗口尺寸变化事件监听器
121
- window.removeEventListener('resize', this.css);
122
- window.removeEventListener('click', this.css);
123
- // 清除滚动条元素的事件监听器
124
- this.box.forEach((e: any) => {
125
- e.removeEventListener('scroll', this.css);
126
- });
127
- // 清除鼠标移入事件监听器
128
- this.dom?.removeEventListener('mouseenter', this.css);
129
- // 清除监听参考元素变化
130
- this.obs?.disconnect();
131
- }
132
- }
133
- export default Follow;