@neatui/vue 2.7.1 → 2.7.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neatui/vue",
3
- "version": "2.7.1",
3
+ "version": "2.7.2",
4
4
  "description": "basic component ui",
5
5
  "main": "./src/index.ts",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div ui-flex="row lm :wrap" class="neatui-upload mb-sm-sub mr-sm-sub" v-bind="{ ...attrs }">
3
- <div v-for="(file, idx) in state.files" :key="idx" ui-flex="col cm" class="flex-fixed bg-fore n-sl w-ms h-ms b-solid bk-case b-xs r-sm pr">
3
+ <div v-for="(file, idx) in state.files" :key="idx" ui-flex="col cm" class="flex-fixed bg-fore n-sl w-ms h-ms b-solid bk-case b-xs r-sm pr" v-bind="itemAttrs">
4
4
  <!-- 删除按钮 -->
5
5
  <div class="pa ot-ss or-ss bg-fore co-risk lh-xs r-xl parent-hover:show" @click="remove(idx)">
6
6
  <svg class="block" viewBox="0 0 1024 1024" style="width: 1em; height: 1em">
@@ -147,7 +147,7 @@
147
147
  </svg>
148
148
  </template>
149
149
 
150
- <!-- 上传状态显示 -->
150
+ <!-- 上传状态 -->
151
151
  <div v-if="file.status === 'uploading'" class="upload-status">
152
152
  <div class="loading-spinner"></div>
153
153
  <span class="status-text">上传中...</span>
@@ -155,14 +155,11 @@
155
155
  <div v-else-if="file.status === 'error'" class="upload-status error">
156
156
  <span class="status-text">上传失败</span>
157
157
  </div>
158
-
159
- <!-- 文件名 -->
160
- <!-- <p ui-omit="1" class="fs-ss ac">{{ file.name }}</p> -->
161
158
  </div>
162
159
 
163
160
  <!-- 上传按钮 -->
164
161
  <div v-if="showUploadButton">
165
- <slot>
162
+ <slot name="btn">
166
163
  <label ui-flex="row cm" ui-form="@a type:upload tips:hover" :class="`upload w-ms h-ms n-sl bg-fore b-solid bk-case b-xs r-sm ${uploadClass}`" :style="uploadStyle">
167
164
  <div v-if="tips" ui-form-tips>
168
165
  {{ tips }}
@@ -189,6 +186,7 @@
189
186
  modelValue?: string | any[];
190
187
  max?: number;
191
188
  attrs?: object;
189
+ itemAttrs?: object;
192
190
  uploadClass?: string;
193
191
  uploadStyle?: string;
194
192
  tips?: string;
@@ -196,6 +194,7 @@
196
194
  upload?: (formData: FormData) => Promise<any>;
197
195
  maxSize?: number;
198
196
  compress?: boolean;
197
+ disabled?: boolean;
199
198
  }
200
199
 
201
200
  const props = withDefaults(defineProps<Props>(), {
@@ -203,12 +202,14 @@
203
202
  uploadClass: '',
204
203
  uploadStyle: '',
205
204
  attrs: () => ({}),
206
- tips: '', // 提示文案
207
- accept: '*', // 文件类型
208
- upload: async () => null, // 请求上传接口
209
- maxSize: 10, // 文件大小限制(MB)
210
- compress: true, // 是否启用图片压缩
211
- max: 0 // 文件数量限制
205
+ itemAttrs: () => ({}),
206
+ tips: '',
207
+ accept: '*',
208
+ upload: async () => null,
209
+ maxSize: 10,
210
+ compress: true,
211
+ max: 0,
212
+ disabled: false,
212
213
  });
213
214
 
214
215
  interface UploadFile {
@@ -226,7 +227,7 @@
226
227
  isInternalUpdate: boolean; // 标记是否为内部更新
227
228
  }>({
228
229
  files: [],
229
- isInternalUpdate: false
230
+ isInternalUpdate: false,
230
231
  });
231
232
 
232
233
  const emit = defineEmits(['update:modelValue', 'change', 'error']);
@@ -314,7 +315,7 @@
314
315
  success: (result) => {
315
316
  resolve(new File([result], file.name, { type: result.type }));
316
317
  },
317
- error: reject
318
+ error: reject,
318
319
  });
319
320
  });
320
321
  };
@@ -395,7 +396,7 @@
395
396
  type: getFileType(file),
396
397
  path: previewUrl,
397
398
  status: 'uploading',
398
- size: file.size
399
+ size: file.size,
399
400
  };
400
401
  state.files.push(uploadFile);
401
402
  validFiles.push({ file, uploadFile });
@@ -475,7 +476,7 @@
475
476
  type: 'image',
476
477
  path: item,
477
478
  status: 'success' as const,
478
- result: item
479
+ result: item,
479
480
  };
480
481
  } else {
481
482
  // 如果是对象
@@ -484,7 +485,7 @@
484
485
  type: item.type || 'file',
485
486
  path: item.url || item.path || '',
486
487
  status: 'success' as const,
487
- result: item
488
+ result: item,
488
489
  };
489
490
  }
490
491
  });
@@ -495,8 +496,8 @@
495
496
  type: 'image',
496
497
  path: newValue,
497
498
  status: 'success',
498
- result: newValue
499
- }
499
+ result: newValue,
500
+ },
500
501
  ];
501
502
  } else if (newValue && typeof newValue === 'object') {
502
503
  const objValue = newValue as any;
@@ -506,14 +507,14 @@
506
507
  type: objValue.type || 'file',
507
508
  path: objValue.url || objValue.path || '',
508
509
  status: 'success',
509
- result: newValue
510
- }
510
+ result: newValue,
511
+ },
511
512
  ];
512
513
  } else {
513
514
  state.files = [];
514
515
  }
515
516
  },
516
- { immediate: true, deep: true }
517
+ { immediate: true, deep: true },
517
518
  );
518
519
 
519
520
  // 组件销毁时清理资源
@@ -1,186 +1,184 @@
1
1
  <template>
2
- <slot>
3
- <div :ui-row="rows" class="n-sl-sub">
4
- <template v-for="(col, idx) in state.form" :key="idx">
5
- <div v-if="!col.clear" :id="col._fid" :ui-col="col.media" :ui-flex="`${vertical ? 'col xm' : 'row lt'}`">
6
- <template v-if="col.label">
7
- <h6 v-if="vertical" :style="`${col.rules?.some((validator: any) => validator.required) ? 'text-indent: -1em' : ''}`" class="o-ls">
8
- <template v-if="col.label == '*'"></template>
9
- <template v-else>
10
- <b v-if="col.rules?.some((validator: any) => validator.required)" class="co-risk pr" style="left: 0.1em">﹡</b>
11
- <span>{{ col.label ? col.label + ':' : '' }}</span>
12
- </template>
13
- </h6>
14
- <p v-else :class="`${clh({ ...(col.model[1] || {}) })}`" :style="`width:${state._lw + 'em'}; max-width:50%;`" class="ar nr-sm flex-fixed o-ls">
15
- <template v-if="col.label == '*'"></template>
16
- <template v-else>
17
- <b v-if="col.rules?.some((validator: any) => validator.required)" class="co-risk">﹡</b>
18
- <span>{{ col.label ? col.label + ':' : '' }}</span>
19
- </template>
20
- </p>
21
- </template>
22
- <Input v-if="col.model[0] === 'Input'" v-model="col._data[col.field]" :interacted="interacted" :rules="col.rules" class="full-w" v-bind="{ ...(col.model[1] || {}) }" />
23
- <Select
24
- v-else-if="col.model[0] === 'Select'"
25
- v-model="col._data[col.field]"
26
- :interacted="interacted"
27
- :options="col.enums"
28
- :rules="col?.rules"
29
- class="full-w"
30
- v-bind="{ ...(col.model[1] || {}) }"
31
- ></Select>
32
- <SelectList
33
- v-else-if="col.model[0] === 'SelectList'"
34
- v-model="col._data[col.field]"
35
- :interacted="interacted"
36
- :options="col.enums"
37
- :rules="col?.rules"
38
- class="full-w"
39
- v-bind="{ ...(col.model[1] || {}) }"
40
- />
41
- <SelectTags
42
- v-else-if="col.model[0] === 'SelectTags'"
43
- v-model="col._data[col.field]"
44
- :interacted="interacted"
45
- :options="col.enums"
46
- :rules="col?.rules"
47
- class="full-w"
48
- v-bind="{ ...(col.model[1] || {}) }"
49
- />
50
- <SelectPicker
51
- v-else-if="col.model[0] === 'SelectPicker'"
52
- v-model="col._data[col.field]"
53
- :interacted="interacted"
54
- :options="col.enums"
55
- :rules="col?.rules"
56
- class="full-w"
57
- v-bind="{ ...(col.model[1] || {}) }"
58
- />
59
- <SearchSelect
60
- v-else-if="col.model[0] === 'SearchSelect'"
61
- v-model="col._data[col.field]"
62
- :interacted="interacted"
63
- :options="col.enums"
64
- :rules="col.rules"
65
- class="full-w"
66
- v-bind="{ ...(col.model[1] || {}) }"
67
- />
68
- <MoreSelect
69
- v-else-if="col.model[0] === 'MoreSelect'"
70
- v-model="col._data[col.field]"
71
- :interacted="interacted"
72
- :options="col.enums"
73
- :rules="col.rules"
74
- class="full-w"
75
- v-bind="{ ...(col.model[1] || {}) }"
76
- />
77
- <MoreSelectList
78
- v-else-if="col.model[0] === 'MoreSelectList'"
79
- v-model="col._data[col.field]"
80
- :interacted="interacted"
81
- :options="col.enums"
82
- :rules="col.rules"
83
- class="full-w"
84
- v-bind="{ ...(col.model[1] || {}) }"
85
- />
86
- <MoreSelectTags
87
- v-else-if="col.model[0] === 'MoreSelectTags'"
88
- v-model="col._data[col.field]"
89
- :interacted="interacted"
90
- :options="col.enums"
91
- :rules="col.rules"
92
- class="full-w"
93
- v-bind="{ ...(col.model[1] || {}) }"
94
- />
95
- <MoreSelectPicker
96
- v-else-if="col.model[0] === 'MoreSelectPicker'"
97
- v-model="col._data[col.field]"
98
- :interacted="interacted"
99
- :options="col.enums"
100
- :rules="col.rules"
101
- class="full-w"
102
- v-bind="{ ...(col.model[1] || {}) }"
103
- />
104
- <SearchMoreSelect
105
- v-else-if="col.model[0] === 'SearchMoreSelect'"
106
- v-model="col._data[col.field]"
107
- :interacted="interacted"
108
- :options="col.enums"
109
- :rules="col.rules"
110
- class="full-w"
111
- v-bind="{ ...(col.model[1] || {}) }"
112
- />
113
- <PageSelect
114
- v-else-if="col.model[0] === 'PageSelect'"
115
- v-model="col._data[col.field]"
116
- :interacted="interacted"
117
- :options="col.enums"
118
- :rules="col.rules"
119
- class="full-w"
120
- v-bind="{ ...(col.model[1] || {}) }"
121
- />
122
- <SelectTree
123
- v-else-if="col.model[0] === 'SelectTree'"
124
- v-model="col._data[col.field]"
125
- :interacted="interacted"
126
- :options="col.enums"
127
- :rules="col.rules"
128
- class="full-w"
129
- v-bind="{ ...(col.model[1] || {}) }"
130
- />
131
- <DatePicker
132
- v-else-if="col.model[0] === 'DatePicker'"
133
- v-model="col._data[col.field]"
134
- :interacted="interacted"
135
- :rules="col.rules"
136
- class="full-w"
137
- v-bind="{ ...(col.model[1] || {}) }"
138
- />
139
- <DateRangePicker
140
- v-else-if="col.model[0] === 'DateRangePicker'"
141
- v-model="col._data[col.field]"
142
- :interacted="interacted"
143
- :rules="col.rules"
144
- class="full-w"
145
- v-bind="{ ...(col.model[1] || {}) }"
146
- />
147
- <ImgUpload v-else-if="col.model[0] === 'ImgUpload'" v-model="col._data[col.field]" :interacted="interacted" :rules="col.rules" v-bind="{ ...(col.model[1] || {}) }" />
148
- <Upload v-else-if="col.model[0] === 'Upload'" v-model="col._data[col.field]" :interacted="interacted" :rules="col.rules" v-bind="{ ...(col.model[1] || {}) }" />
149
- <TextArea
150
- v-else-if="col.model[0] === 'TextArea'"
151
- v-model="col._data[col.field]"
152
- :interacted="interacted"
153
- :rules="col.rules"
154
- v-bind="{ ...(col.model[1] || {}) }"
155
- ></TextArea>
156
- <Switch v-else-if="col.model[0] === 'Switch'" v-model="col._data[col.field]" :interacted="interacted" :rules="col.rules" v-bind="{ ...(col.model[1] || {}) }" />
157
- <Tree v-else-if="col.model[0] === 'Tree'" v-model="col._data[col.field]" :interacted="interacted" :rules="col.rules" class="mt-sm" v-bind="{ ...(col.model[1] || {}) }" />
158
- <Cascader
159
- v-else-if="col.model[0] === 'Cascader'"
160
- v-model="col._data[col.field]"
161
- :interacted="interacted"
162
- :options="col.enums"
163
- :rules="col.rules"
164
- class="full-w"
165
- v-bind="{ ...(col.model[1] || {}) }"
166
- />
167
- <template v-else>
168
- <slot v-if="col.slot" :name="col.slot" :col="col"></slot>
169
- <slot v-else-if="col.model[0]" :name="col.model[0]" :col="col"></slot>
170
- <slot v-else :name="col.field" :col="col"></slot>
171
- </template>
172
- </div>
173
- </template>
174
- <div :ui-flex="`${vertical ? 'col xm' : 'row xt'}`">
175
- <div v-if="vertical">
176
- <slot name="submit"></slot>
177
- </div>
178
- <div v-else :style="`padding-left:${state._lw + 'em'};`">
179
- <slot name="submit"></slot>
180
- </div>
2
+ <div :ui-row="rows" class="n-sl-sub">
3
+ <template v-for="(col, idx) in state.form" :key="idx">
4
+ <div v-if="!col.clear" :id="col._fid" :ui-col="col.media" :ui-flex="`${vertical ? 'col xm' : 'row lt'}`">
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">
7
+ <template v-if="col.label == '*'"></template>
8
+ <template v-else>
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>
11
+ </template>
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">
14
+ <template v-if="col.label == '*'"></template>
15
+ <template v-else>
16
+ <b v-if="col.rules?.some((validator: any) => validator.required)" class="co-risk">﹡</b>
17
+ <span>{{ col.label ? col.label + ':' : '' }}</span>
18
+ </template>
19
+ </p>
20
+ </template>
21
+ <Input v-if="col.model[0] === 'Input'" v-model="col._data[col.field]" :interacted="interacted" :rules="col.rules" class="full-w" v-bind="{ ...(col.model[1] || {}) }" />
22
+ <Select
23
+ v-else-if="col.model[0] === 'Select'"
24
+ v-model="col._data[col.field]"
25
+ :interacted="interacted"
26
+ :options="col.enums"
27
+ :rules="col?.rules"
28
+ class="full-w"
29
+ v-bind="{ ...(col.model[1] || {}) }"
30
+ ></Select>
31
+ <SelectList
32
+ v-else-if="col.model[0] === 'SelectList'"
33
+ v-model="col._data[col.field]"
34
+ :interacted="interacted"
35
+ :options="col.enums"
36
+ :rules="col?.rules"
37
+ class="full-w"
38
+ v-bind="{ ...(col.model[1] || {}) }"
39
+ />
40
+ <SelectTags
41
+ v-else-if="col.model[0] === 'SelectTags'"
42
+ v-model="col._data[col.field]"
43
+ :interacted="interacted"
44
+ :options="col.enums"
45
+ :rules="col?.rules"
46
+ class="full-w"
47
+ v-bind="{ ...(col.model[1] || {}) }"
48
+ />
49
+ <SelectPicker
50
+ v-else-if="col.model[0] === 'SelectPicker'"
51
+ v-model="col._data[col.field]"
52
+ :interacted="interacted"
53
+ :options="col.enums"
54
+ :rules="col?.rules"
55
+ class="full-w"
56
+ v-bind="{ ...(col.model[1] || {}) }"
57
+ />
58
+ <SearchSelect
59
+ v-else-if="col.model[0] === 'SearchSelect'"
60
+ v-model="col._data[col.field]"
61
+ :interacted="interacted"
62
+ :options="col.enums"
63
+ :rules="col.rules"
64
+ class="full-w"
65
+ v-bind="{ ...(col.model[1] || {}) }"
66
+ />
67
+ <MoreSelect
68
+ v-else-if="col.model[0] === 'MoreSelect'"
69
+ v-model="col._data[col.field]"
70
+ :interacted="interacted"
71
+ :options="col.enums"
72
+ :rules="col.rules"
73
+ class="full-w"
74
+ v-bind="{ ...(col.model[1] || {}) }"
75
+ />
76
+ <MoreSelectList
77
+ v-else-if="col.model[0] === 'MoreSelectList'"
78
+ v-model="col._data[col.field]"
79
+ :interacted="interacted"
80
+ :options="col.enums"
81
+ :rules="col.rules"
82
+ class="full-w"
83
+ v-bind="{ ...(col.model[1] || {}) }"
84
+ />
85
+ <MoreSelectTags
86
+ v-else-if="col.model[0] === 'MoreSelectTags'"
87
+ v-model="col._data[col.field]"
88
+ :interacted="interacted"
89
+ :options="col.enums"
90
+ :rules="col.rules"
91
+ class="full-w"
92
+ v-bind="{ ...(col.model[1] || {}) }"
93
+ />
94
+ <MoreSelectPicker
95
+ v-else-if="col.model[0] === 'MoreSelectPicker'"
96
+ v-model="col._data[col.field]"
97
+ :interacted="interacted"
98
+ :options="col.enums"
99
+ :rules="col.rules"
100
+ class="full-w"
101
+ v-bind="{ ...(col.model[1] || {}) }"
102
+ />
103
+ <SearchMoreSelect
104
+ v-else-if="col.model[0] === 'SearchMoreSelect'"
105
+ v-model="col._data[col.field]"
106
+ :interacted="interacted"
107
+ :options="col.enums"
108
+ :rules="col.rules"
109
+ class="full-w"
110
+ v-bind="{ ...(col.model[1] || {}) }"
111
+ />
112
+ <PageSelect
113
+ v-else-if="col.model[0] === 'PageSelect'"
114
+ v-model="col._data[col.field]"
115
+ :interacted="interacted"
116
+ :options="col.enums"
117
+ :rules="col.rules"
118
+ class="full-w"
119
+ v-bind="{ ...(col.model[1] || {}) }"
120
+ />
121
+ <SelectTree
122
+ v-else-if="col.model[0] === 'SelectTree'"
123
+ v-model="col._data[col.field]"
124
+ :interacted="interacted"
125
+ :options="col.enums"
126
+ :rules="col.rules"
127
+ class="full-w"
128
+ v-bind="{ ...(col.model[1] || {}) }"
129
+ />
130
+ <DatePicker
131
+ v-else-if="col.model[0] === 'DatePicker'"
132
+ v-model="col._data[col.field]"
133
+ :interacted="interacted"
134
+ :rules="col.rules"
135
+ class="full-w"
136
+ v-bind="{ ...(col.model[1] || {}) }"
137
+ />
138
+ <DateRangePicker
139
+ v-else-if="col.model[0] === 'DateRangePicker'"
140
+ v-model="col._data[col.field]"
141
+ :interacted="interacted"
142
+ :rules="col.rules"
143
+ class="full-w"
144
+ v-bind="{ ...(col.model[1] || {}) }"
145
+ />
146
+ <ImgUpload v-else-if="col.model[0] === 'ImgUpload'" v-model="col._data[col.field]" :interacted="interacted" :rules="col.rules" v-bind="{ ...(col.model[1] || {}) }" />
147
+ <Upload v-else-if="col.model[0] === 'Upload'" v-model="col._data[col.field]" :interacted="interacted" :rules="col.rules" v-bind="{ ...(col.model[1] || {}) }" />
148
+ <TextArea
149
+ v-else-if="col.model[0] === 'TextArea'"
150
+ v-model="col._data[col.field]"
151
+ :interacted="interacted"
152
+ :rules="col.rules"
153
+ v-bind="{ ...(col.model[1] || {}) }"
154
+ ></TextArea>
155
+ <Switch v-else-if="col.model[0] === 'Switch'" v-model="col._data[col.field]" :interacted="interacted" :rules="col.rules" v-bind="{ ...(col.model[1] || {}) }" />
156
+ <Tree v-else-if="col.model[0] === 'Tree'" v-model="col._data[col.field]" :interacted="interacted" :rules="col.rules" class="mt-sm" v-bind="{ ...(col.model[1] || {}) }" />
157
+ <Cascader
158
+ v-else-if="col.model[0] === 'Cascader'"
159
+ v-model="col._data[col.field]"
160
+ :interacted="interacted"
161
+ :options="col.enums"
162
+ :rules="col.rules"
163
+ class="full-w"
164
+ v-bind="{ ...(col.model[1] || {}) }"
165
+ />
166
+ <template v-else>
167
+ <slot v-if="col.slot" :name="col.slot" :col="col"></slot>
168
+ <slot v-else-if="col.model[0]" :name="col.model[0]" :col="col"></slot>
169
+ <slot v-else :name="col.field" :col="col"></slot>
170
+ </template>
171
+ </div>
172
+ </template>
173
+ <div :ui-flex="`${vertical ? 'col xm' : 'row xt'}`">
174
+ <div v-if="vertical">
175
+ <slot name="submit"></slot>
176
+ </div>
177
+ <div v-else :style="`padding-left:${state._lw + 'em'};`">
178
+ <slot name="submit"></slot>
181
179
  </div>
182
180
  </div>
183
- </slot>
181
+ </div>
184
182
  </template>
185
183
  <script lang="ts" setup>
186
184
  import { watch, reactive, onBeforeUnmount, nextTick } from 'vue';
@@ -1,2 +1,49 @@
1
+ import { isFunction } from '@fekit/utils';
1
2
  import TableLoader from './TableLoader.vue';
3
+
4
+ // 类型定义
5
+ interface ListItem {
6
+ __selected?: boolean;
7
+ [key: string]: any;
8
+ }
9
+
10
+ type KeyExtractor<T = ListItem> = string | ((item: T) => any);
11
+
2
12
  export default TableLoader;
13
+
14
+ // 是否有选中记录
15
+ export const hasSelected = (lists: ListItem[] = []): boolean => {
16
+ return lists.some((item) => item.__selected === true);
17
+ };
18
+
19
+ // 获取选中记录的数量
20
+ export const getSelectedCount = (lists: ListItem[] = []): number => {
21
+ return lists.filter((item) => item.__selected === true).length;
22
+ };
23
+
24
+ // 获取选中的记录
25
+ export const getSelected = <T extends Record<string, any> = ListItem>(lists: T[] = [], key: KeyExtractor<T> = ''): any[] => {
26
+ return (
27
+ lists
28
+ ?.filter((item: T) => {
29
+ return item.__selected === true;
30
+ })
31
+ ?.map((item: T) => {
32
+ return isFunction(key) ? (key as (item: T) => any)(item) : key ? item[key as string] : item;
33
+ }) || []
34
+ );
35
+ };
36
+
37
+ // 全选计算属性
38
+ export function allSelected(lists: ListItem[] = []) {
39
+ return {
40
+ get(): boolean {
41
+ return lists.length > 0 && lists.every((item) => item.__selected === true);
42
+ },
43
+ set(value: boolean): void {
44
+ lists.forEach((item) => {
45
+ item.__selected = value;
46
+ });
47
+ }
48
+ };
49
+ }