@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
@@ -1,29 +1,34 @@
1
1
  <!-- 实验性插件 -->
2
2
  <template>
3
- <Teleport :to="area">
4
- <div
5
- v-if="state.view"
6
- :am-view="am"
7
- :data-key="id"
8
- :style="`${`${dark}` ? 'background-color: rgba(0, 0, 0, ' + dark + ');' : ''}${zIndex ? 'z-index:' + zIndex + ';' : ''}`"
9
- :view="state.view"
10
- class="fekit-layer"
11
- >
12
- <div :ui-flex="`col ${flex}`" :style="wrapStyle" class="fekit-layer-wrap" @animationend="anim" @click="_sys_hide">
13
- <slot :param="_tasks[id]?.params"></slot>
3
+ <div>
4
+ <Teleport :to="area">
5
+ <div
6
+ v-if="state.view"
7
+ :am-view="am"
8
+ :data-key="id"
9
+ :style="`${`${dark}` ? 'background-color: rgba(0, 0, 0, ' + dark + ');' : ''}${zIndex ? 'z-index:' + zIndex + ';' : ''}`"
10
+ :view="state.view"
11
+ class="fekit-layer"
12
+ >
13
+ <div :ui-flex="`col ${flex}`" :style="wrapStyle" class="fekit-layer-wrap" @animationend="anim" @click="_sys_hide">
14
+ <slot :param="_tasks[id]?.params"></slot>
15
+ </div>
14
16
  </div>
15
- </div>
16
- </Teleport>
17
+ </Teleport>
18
+ </div>
17
19
  </template>
18
20
  <script lang="ts" setup>
19
21
  import { onBeforeUnmount, reactive, watch } from 'vue';
20
- import { isFunction } from '@fekit/utils';
22
+ import { env, isFunction } from '@fekit/utils';
21
23
 
22
24
  // 弹层队列
23
- if (!(window as any)._FEKIT_LAYER_TASKS) {
24
- (window as any)._FEKIT_LAYER_TASKS = {};
25
- }
26
- const _tasks: any = (window as any)._FEKIT_LAYER_TASKS;
25
+ const _tasks: any = (() => {
26
+ if (!env.client) return {};
27
+ if (!(window as any)._FEKIT_LAYER_TASKS) {
28
+ (window as any)._FEKIT_LAYER_TASKS = {};
29
+ }
30
+ return (window as any)._FEKIT_LAYER_TASKS;
31
+ })();
27
32
 
28
33
  // 获取活跃的弹层
29
34
  const getActiveLayer = () => {
@@ -54,12 +59,14 @@
54
59
  wrapStyle: '',
55
60
  });
56
61
 
57
- const _area: any = document.querySelector(props.area);
58
- if (!_area) {
59
- const el = document.createElement('div');
60
- el.id = (props.area || '#fekit').substring(1);
61
- el.style.position = 'absolute';
62
- document.body.appendChild(el);
62
+ if (env.client) {
63
+ const _area: any = document.querySelector(props.area);
64
+ if (!_area) {
65
+ const el = document.createElement('div');
66
+ el.id = (props.area || '#fekit').substring(1);
67
+ el.style.position = 'absolute';
68
+ document.body.appendChild(el);
69
+ }
63
70
  }
64
71
 
65
72
  // 状态管理
@@ -71,6 +78,7 @@
71
78
  });
72
79
 
73
80
  function disableScroll() {
81
+ if (!env.client) return;
74
82
  state.top = document.documentElement.scrollTop;
75
83
  const sw = window.innerWidth - document.documentElement.clientWidth;
76
84
  state.sw = sw;
@@ -82,6 +90,7 @@
82
90
  }
83
91
 
84
92
  function enableScroll() {
93
+ if (!env.client) return;
85
94
  document.body.style.removeProperty('overflow');
86
95
  document.body.style.removeProperty('position');
87
96
  document.body.style.removeProperty('top');
@@ -1,5 +1,7 @@
1
1
  import Layer from './Layer.vue';
2
+ import { env } from '@fekit/utils';
3
+
2
4
  const LayerById = (id: any) => {
3
- return (window as any)._FEKIT_LAYER_TASKS[id];
5
+ return env.client ? (window as any)._FEKIT_LAYER_TASKS?.[id] : undefined;
4
6
  };
5
7
  export { Layer, LayerById };
@@ -217,7 +217,8 @@
217
217
  // 创建日期
218
218
  const cDate = (date: any = null) => {
219
219
  const normalized = normalizeDateInput(date);
220
- return isDateString(normalized) ? new Date(normalized) : new Date();
220
+ // iOS 兼容:Safari 不支持 "YYYY-MM-DD" 格式,需要替换为 "YYYY/MM/DD"
221
+ return isDateString(normalized) ? new Date(normalized?.replace(/-/g, '/')) : new Date();
221
222
  };
222
223
 
223
224
  // 快捷预设
@@ -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: false,
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: false,
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="o-ls">
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.label ? col.label + ':' : '' }}</span>
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.label ? col.label + ':' : '' }}</span>
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
  );
@@ -1,4 +1,4 @@
1
- import FormLoader from './FormLoader/FormLoader@v2.vue';
1
+ import FormLoader from './FormLoader/FormLoader.vue';
2
2
  import LimitLoader from './LimitLoader';
3
3
  export * from './TableLoader/index';
4
4
  import TableLoader from './TableLoader/TableLoader.vue';
@@ -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: HTMLElement,
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
- const local = JSON.parse(localStorage.getItem(state.cacheKey) || '{}');
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
- const local = JSON.parse(localStorage.getItem(state.cacheKey) || '{}');
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: HTMLElement,
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>