@jari-ace/element-plus-component 0.3.3 → 0.3.4

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 (28) hide show
  1. package/dist/components/upload/JaUploader.vue.d.ts +8 -0
  2. package/dist/components/upload/JaUploader.vue.d.ts.map +1 -1
  3. package/dist/components/upload/index.d.ts +12 -0
  4. package/dist/components/upload/index.d.ts.map +1 -1
  5. package/dist/components/upload/uploader.vue.d.ts +5 -0
  6. package/dist/components/upload/uploader.vue.d.ts.map +1 -1
  7. package/dist/components/upload/uploader.vue.js +14 -4
  8. package/dist/components/upload/uploader.vue.js.map +1 -1
  9. package/dist/components/userPicker/src/JaUserList.vue.d.ts +4 -0
  10. package/dist/components/userPicker/src/JaUserList.vue.d.ts.map +1 -1
  11. package/dist/components/userPicker/src/JaUserList.vue.js +9 -1
  12. package/dist/components/userPicker/src/JaUserList.vue.js.map +1 -1
  13. package/dist/components/userPicker/src/UserPicker.vue.d.ts +10 -0
  14. package/dist/components/userPicker/src/UserPicker.vue.d.ts.map +1 -1
  15. package/dist/components/userPicker/src/UserPicker.vue.js +46 -5
  16. package/dist/components/userPicker/src/UserPicker.vue.js.map +1 -1
  17. package/dist/components/userSelectDialog/src/userSelectDialog.vue.d.ts +19 -2
  18. package/dist/components/userSelectDialog/src/userSelectDialog.vue.d.ts.map +1 -1
  19. package/dist/components/userSelectDialog/src/userSelectDialog.vue.js +58 -12
  20. package/dist/components/userSelectDialog/src/userSelectDialog.vue.js.map +1 -1
  21. package/lib/index.css +1 -1
  22. package/lib/index.js +4334 -4243
  23. package/lib/index.umd.cjs +22 -17
  24. package/package.json +1 -1
  25. package/packages/components/upload/uploader.vue +68 -55
  26. package/packages/components/userPicker/src/JaUserList.vue +14 -2
  27. package/packages/components/userPicker/src/UserPicker.vue +47 -4
  28. package/packages/components/userSelectDialog/src/userSelectDialog.vue +44 -7
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jari-ace/element-plus-component",
3
3
  "private": false,
4
- "version": "0.3.3",
4
+ "version": "0.3.4",
5
5
  "main": "lib/index.umd.cjs",
6
6
  "module": "lib/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -6,21 +6,21 @@ import {
6
6
  useLoginUser,
7
7
  type ClassificationLevel, type UploadInitParams, useLoading
8
8
  } from "@jari-ace/app-bolts";
9
- import {nextTick, onMounted, onUnmounted, ref, watch} from "vue";
10
- import {ArrowDown, Download, Upload} from "@element-plus/icons-vue";
9
+ import { nextTick, onMounted, onUnmounted, ref, watch } from "vue";
10
+ import { ArrowDown, Download, Upload } from "@element-plus/icons-vue";
11
11
  import {
12
12
  useSystemClassificationLevelMap,
13
13
  useSystemClassificationLevels
14
14
  } from "../../hooks/useClassificationLevels";
15
15
  import Ace = Jari.Ace;
16
- import {ElMessageBox} from "element-plus";
16
+ import { ElMessageBox } from "element-plus";
17
17
  import "@uppy/core/css/style.min.css";
18
18
  import "@uppy/dashboard/css/style.min.css";
19
19
  import "@uppy/audio/css/style.min.css";
20
20
  import "@uppy/screen-capture/css/style.min.css";
21
21
  import "@uppy/webcam/css/style.min.css";
22
22
  import "@uppy/image-editor/css/style.min.css";
23
- import Uppy, {type Meta, type UppyFile} from "@uppy/core";
23
+ import Uppy, { type Meta, type UppyFile } from "@uppy/core";
24
24
  import Tus from "@uppy/tus";
25
25
  import Webcam from "@uppy/webcam";
26
26
  import ScreenCapture from "@uppy/screen-capture";
@@ -41,7 +41,7 @@ import {
41
41
  ElIcon,
42
42
  ElTag
43
43
  } from "element-plus";
44
- import type {AceFile} from "./types";
44
+ import type { AceFile } from "./types";
45
45
 
46
46
  const props = defineProps<{
47
47
  /**
@@ -138,8 +138,14 @@ const emits = defineEmits<{
138
138
  * 单个文件上传失败事件
139
139
  */
140
140
  uploadError: [file: AceFile, error: Error]
141
-
142
-
141
+ /**
142
+ * 下载完成事件
143
+ */
144
+ downloaded: [file: FileInfo]
145
+ /**
146
+ * 打包下载完成事件
147
+ */
148
+ zipDownloaded: [file: FileInfo[]]
143
149
  }>();
144
150
  let uppy: Uppy | undefined;
145
151
  curAttachToken.value = props.attachToken;
@@ -150,6 +156,13 @@ onMounted(async () => {
150
156
  classificationLevels.value = await useSystemClassificationLevels();
151
157
  classificationLevelMap.value = await useSystemClassificationLevelMap();
152
158
  allowedClassificationLevels.value = classificationLevels.value.filter(l => l.value >= cl);
159
+ console.log(`附件控件密级调试:
160
+ 表单密级:`, props.classificationLevel, `
161
+ 用户密级:`, user.value?.classifiedLevel, `
162
+ 表单/用户密级比较后取最低密级:`, cl, `
163
+ 系统密级:`, classificationLevels.value, `
164
+ 最终可选密级:`, allowedClassificationLevels.value
165
+ )
153
166
  if (props.attachToken) {
154
167
  await initLoad();
155
168
  } else {
@@ -188,7 +201,7 @@ function createUppyInstance() {
188
201
  withCredentials: true,
189
202
  retryDelays: undefined,
190
203
  chunkSize: !uploadInitParams.value?.chunkSize || uploadInitParams.value?.chunkSize
191
- == 0 ? Infinity : uploadInitParams.value?.chunkSize,
204
+ == 0 ? Infinity : uploadInitParams.value?.chunkSize,
192
205
  parallelUploads: 1,
193
206
  headers: {
194
207
  aceAttachId: attachId.value!,
@@ -264,17 +277,17 @@ function createUppyInstance() {
264
277
  emits('upload', uploadID, files);
265
278
  }).on('progress', progress => {
266
279
  emits('progress', progress)
267
- }).on('upload-progress', file=> {
280
+ }).on('upload-progress', file => {
268
281
  emits('uploadProgress', file)
269
- }).on('upload-pause', (file, isPaused)=>{
282
+ }).on('upload-pause', (file, isPaused) => {
270
283
  emits("uploadPause", file, isPaused)
271
284
  }).on('upload-success', file => {
272
285
  emits('uploadSuccess', file)
273
- }).on('complete', (result)=> {
286
+ }).on('complete', (result) => {
274
287
  emits('complete', result.successful, result.failed)
275
- }).on('error', (error)=> {
288
+ }).on('error', (error) => {
276
289
  emits('error', error)
277
- }).on('upload-error', (file,error)=> {
290
+ }).on('upload-error', (file, error) => {
278
291
  emits('uploadError', file, error)
279
292
  })
280
293
  }
@@ -292,9 +305,9 @@ async function onUploadBtnClick() {
292
305
  if (dashboard) {
293
306
  dashboard.setOptions({
294
307
  note: (
295
- cfg.uploadNote ? cfg.uploadNote : ""
296
- )
297
- + `(正在上传${classificationLevelMap.value?.get(selectedFileClassificationLevel.value)}文件)`
308
+ cfg.uploadNote ? cfg.uploadNote : ""
309
+ )
310
+ + `(正在上传${classificationLevelMap.value?.get(selectedFileClassificationLevel.value)}文件)`
298
311
  });
299
312
  }
300
313
  dashboard?.openModal();
@@ -317,9 +330,9 @@ function handleClassificationLevelSelCmd(level: number) {
317
330
  if (cfg) {
318
331
  dashboard.setOptions({
319
332
  note: (
320
- cfg.uploadNote ? cfg.uploadNote : ""
321
- )
322
- + `(正在上传${classificationLevelMap.value?.get(selectedFileClassificationLevel.value)}文件)`
333
+ cfg.uploadNote ? cfg.uploadNote : ""
334
+ )
335
+ + `(正在上传${classificationLevelMap.value?.get(selectedFileClassificationLevel.value)}文件)`
323
336
  });
324
337
  }
325
338
  dashboard?.openModal();
@@ -357,8 +370,11 @@ function previewFile(fileToken: string) {
357
370
  pdfViewerVisible.value = true;
358
371
  }
359
372
 
360
- function downloadFile(fileToken: string) {
361
- api.downloadFile(fileToken);
373
+ async function downloadFile(fileToken: string) {
374
+ await api.downloadFile(fileToken);
375
+ var file = files.value.filter(f => fileToken === f.token)
376
+ if (file && file.length > 0)
377
+ emits('downloaded', file[0]);
362
378
  }
363
379
 
364
380
  function viewUppyDashboard() {
@@ -368,11 +384,12 @@ function viewUppyDashboard() {
368
384
  }
369
385
  }
370
386
 
371
- function downloadAll() {
387
+ async function downloadAll() {
372
388
  if (!curAttachToken.value) {
373
389
  return;
374
390
  }
375
- api.downloadZipFile(curAttachToken.value);
391
+ await api.downloadZipFile(curAttachToken.value);
392
+ emits('zipDownloaded', files.value);
376
393
  }
377
394
 
378
395
  function checkAllowUpload() {
@@ -451,42 +468,39 @@ watch(() => props.attachToken, () => {
451
468
  <template>
452
469
  <div class="ja-uploader">
453
470
  <div class="ja-uploader-tools">
454
- <el-button plain type="success" :icon="Upload" @click="onUploadBtnClick"
455
- :loading="loading" :disabled="loading" size="small"
456
- v-if="allowedClassificationLevels.length <= 1 && checkAllowUpload()">
471
+ <el-button plain type="success" :icon="Upload" @click="onUploadBtnClick" :loading="loading"
472
+ :disabled="loading" size="small" v-if="allowedClassificationLevels.length <= 1 && checkAllowUpload()">
457
473
  上传{{ uploadInitParams?.fileConfig.enableClassifiedLevel ? "(非密)" : "" }}
458
474
  </el-button>
459
- <el-button plain :icon="Download" @click="downloadAll" v-if="checkAllowDownload()"
460
- :loading="loading" :disabled="loading || files?.length === 0" size="small">
475
+ <el-button plain :icon="Download" @click="downloadAll" v-if="checkAllowDownload()" :loading="loading"
476
+ :disabled="loading || files?.length === 0" size="small">
461
477
  全部下载
462
478
  </el-button>
463
- <el-dropdown v-if="allowedClassificationLevels.length > 1 && checkAllowUpload()"
464
- @command="handleClassificationLevelSelCmd" size="small">
479
+ <el-dropdown v-if="allowedClassificationLevels.length > 1 && checkAllowUpload()"
480
+ @command="handleClassificationLevelSelCmd" size="small">
465
481
  <el-button type="success" size="small">
466
482
  上传
467
483
  <el-icon class="el-icon--right">
468
- <arrow-down/>
484
+ <arrow-down />
469
485
  </el-icon>
470
486
  </el-button>
471
487
  <template #dropdown>
472
488
  <el-dropdown-menu>
473
- <el-dropdown-item v-for="cl in allowedClassificationLevels"
474
- :key="cl.value"
475
- :command="cl.value">{{ cl.label }}
489
+ <el-dropdown-item v-for="cl in allowedClassificationLevels" :key="cl.value"
490
+ :command="cl.value">{{ cl.label }}
476
491
  </el-dropdown-item>
477
492
  </el-dropdown-menu>
478
493
  </template>
479
494
  </el-dropdown>
480
- <div class="container" @click="viewUppyDashboard"
481
- v-if="uploadingProgress < 100 && uploadingProgress > 0">
495
+ <div class="container" @click="viewUppyDashboard" v-if="uploadingProgress < 100 && uploadingProgress > 0">
482
496
  <p>
483
497
  <span class="loader-shimmer">正在上传,已完成{{ uploadingProgress }}%...</span>
484
498
  </p>
485
499
  </div>
486
500
  </div>
487
- <el-table :data="files" :show-header="true" style="width: 100%" empty-text="暂无文件"
488
- :height="props.height" :max-height="props.maxHeight">
489
- <el-table-column prop="fileName"/>
501
+ <el-table :data="files" :show-header="true" style="width: 100%" empty-text="暂无文件" :height="props.height"
502
+ :max-height="props.maxHeight">
503
+ <el-table-column prop="fileName" />
490
504
  <el-table-column prop="fileSize" width="100">
491
505
  <template #default="scope">
492
506
  {{ prettyBytes(scope.row.fileSize) }}
@@ -495,7 +509,7 @@ watch(() => props.attachToken, () => {
495
509
  <el-table-column prop="classifiedLevel" width="60">
496
510
  <template #default="scope">
497
511
  <el-tag
498
- :type="scope.row.classifiedLevel < 50? ( scope.row.classifiedLevel < 30 ? 'danger' : 'warning'): 'info'">
512
+ :type="scope.row.classifiedLevel < 50 ? (scope.row.classifiedLevel < 30 ? 'danger' : 'warning') : 'info'">
499
513
  {{ classificationLevelMap?.get(scope.row.classifiedLevel) }}
500
514
  </el-tag>
501
515
  </template>
@@ -503,13 +517,11 @@ watch(() => props.attachToken, () => {
503
517
  <el-table-column align="right" width="150" fixed="right">
504
518
  <template #default="scope">
505
519
  <el-button link type="warning" @click="previewFile(scope.row.token)"
506
- v-if="checkAllowPreview(scope.row)">预览
520
+ v-if="checkAllowPreview(scope.row)">预览
507
521
  </el-button>
508
- <el-button link type="primary" @click="downloadFile(scope.row.token)"
509
- v-if="checkAllowDownload()">下载
522
+ <el-button link type="primary" @click="downloadFile(scope.row.token)" v-if="checkAllowDownload()">下载
510
523
  </el-button>
511
- <el-button link type="danger" @click="delUploadedFile(scope.row)"
512
- v-if="checkAllowDelete()">删除
524
+ <el-button link type="danger" @click="delUploadedFile(scope.row)" v-if="checkAllowDelete()">删除
513
525
  </el-button>
514
526
  </template>
515
527
  </el-table-column>
@@ -552,7 +564,8 @@ watch(() => props.attachToken, () => {
552
564
  font-size: 0.9em;
553
565
  font-weight: 500;
554
566
  position: relative;
555
- overflow: hidden; /* 隐藏闪光效果的溢出 */
567
+ overflow: hidden;
568
+ /* 隐藏闪光效果的溢出 */
556
569
  vertical-align: middle;
557
570
  }
558
571
 
@@ -560,17 +573,17 @@ watch(() => props.attachToken, () => {
560
573
  content: '';
561
574
  position: absolute;
562
575
  top: 0;
563
- left: -100%; /* 从左侧外部开始 */
564
- width: 50%; /* 闪光宽度 */
576
+ left: -100%;
577
+ /* 从左侧外部开始 */
578
+ width: 50%;
579
+ /* 闪光宽度 */
565
580
  height: 100%;
566
581
 
567
582
  /* 闪光效果:透明 -> 亮白色 -> 透明 */
568
- background: linear-gradient(
569
- 90deg,
583
+ background: linear-gradient(90deg,
570
584
  transparent,
571
585
  rgba(255, 255, 255, 0.8),
572
- transparent
573
- );
586
+ transparent);
574
587
 
575
588
  /* 动画: 1.5秒, 缓入缓出, 无限循环 */
576
589
  animation: shimmer 1.5s ease-in-out infinite;
@@ -580,11 +593,11 @@ watch(() => props.attachToken, () => {
580
593
  0% {
581
594
  left: -100%;
582
595
  }
596
+
583
597
  100% {
584
- left: 150%; /* 移动到右侧外部 */
598
+ left: 150%;
599
+ /* 移动到右侧外部 */
585
600
  }
586
601
  }
587
602
  }
588
-
589
-
590
603
  </style>
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
 
3
- import {ElEmpty, ElIcon, ElScrollbar, vLoading} from "element-plus";
3
+ import {ElEmpty, ElIcon, ElScrollbar, vLoading, ElMessage} from "element-plus";
4
4
  import {Check} from "@element-plus/icons-vue";
5
5
  import {JaUserInfoTag} from "../../userTag";
6
6
  import {computed, nextTick, onUnmounted, ref, watch} from "vue";
@@ -14,7 +14,11 @@ const props = defineProps<{
14
14
  pageSize: number,
15
15
  height?: number | string,
16
16
  loading: boolean,
17
- users: UserReference[]
17
+ users: UserReference[],
18
+ /**
19
+ * 最多选择数量,为0则不限制
20
+ */
21
+ maxSelect?: number
18
22
  }>();
19
23
  const activeIndex = ref(0);
20
24
  const scrollbar = ref<InstanceType<typeof ElScrollbar>>();
@@ -50,6 +54,14 @@ function switchUserSelect(u: UserReference) {
50
54
  }
51
55
  const index = selectedUsers.value.findIndex(su => su.id == u.id);
52
56
  if (index < 0) {
57
+ if (props.maxSelect && props.maxSelect > 0 && selectedUsers.value.length >= props.maxSelect) {
58
+ ElMessage({
59
+ message: `已超过最大选择数量 ${props.maxSelect}`,
60
+ plain: true,
61
+ type: 'warning',
62
+ })
63
+ return;
64
+ }
53
65
  selectedUsers.value.push(u);
54
66
  } else {
55
67
  selectedUsers.value.splice(index, 1);
@@ -3,6 +3,7 @@ import {
3
3
  ElButton,
4
4
  ElIcon,
5
5
  ElInput,
6
+ ElMessage,
6
7
  ElPopover,
7
8
  ElText,
8
9
  ElSpace,
@@ -79,7 +80,15 @@ const props = withDefaults( defineProps<{
79
80
  /**
80
81
  * 是否显示已选用户标签
81
82
  */
82
- showUserTag?:boolean
83
+ showUserTag?:boolean,
84
+ /**
85
+ * 最多选择数量,为0则不限制
86
+ */
87
+ maxSelect?:number,
88
+ /**
89
+ * 是否禁用
90
+ */
91
+ disabled?:boolean
83
92
 
84
93
  }>(),{
85
94
  trigger:'hover',
@@ -89,6 +98,8 @@ const props = withDefaults( defineProps<{
89
98
  maxShowCount: 20,
90
99
  maxHeight: '100%',
91
100
  showUserTag: true,
101
+ maxSelect: 0,
102
+ disabled: false,
92
103
  })
93
104
 
94
105
  // const props = defineProps({
@@ -183,6 +194,14 @@ function switchUserSelect(u: UserReference) {
183
194
  }
184
195
  const index = selectedUsers.value.findIndex(su => su.id == u.id);
185
196
  if (index < 0) {
197
+ if (props.maxSelect > 0 && selectedUsers.value.length >= props.maxSelect) {
198
+ ElMessage({
199
+ message: `已超过最大选择数量 ${props.maxSelect}`,
200
+ plain: true,
201
+ type: 'warning',
202
+ })
203
+ return;
204
+ }
186
205
  selectedUsers.value.push(u);
187
206
  } else {
188
207
  selectedUsers.value.splice(index, 1);
@@ -230,6 +249,9 @@ function onSaveToCustomGroupClick() {
230
249
  }
231
250
 
232
251
  function onDeselected(uid?: string) {
252
+ if (props.disabled) {
253
+ return;
254
+ }
233
255
  if (Array.isArray(selectedUsers.value)) {
234
256
  const index = selectedUsers.value.findIndex(su => su.id == uid);
235
257
  if (index >= 0) {
@@ -255,6 +277,9 @@ function onInputKeyEnter() {
255
277
  }
256
278
 
257
279
  function popup() {
280
+ if (props.disabled) {
281
+ return;
282
+ }
258
283
  popoverVisible.value = true;
259
284
  }
260
285
 
@@ -262,7 +287,15 @@ function selectAll() {
262
287
  const arr = (
263
288
  selectedUsers.value as UserReference[]
264
289
  );
265
- arr.push(...users.value.filter(u => !arr.some(su => su.id === u.id)));
290
+ const availableUsers = users.value.filter(u => !arr.some(su => su.id === u.id));
291
+ if (props.maxSelect > 0) {
292
+ const remaining = props.maxSelect - arr.length;
293
+ if (remaining > 0) {
294
+ arr.push(...availableUsers.slice(0, remaining));
295
+ }
296
+ } else {
297
+ arr.push(...availableUsers);
298
+ }
266
299
  }
267
300
 
268
301
  function clearSelection() {
@@ -292,11 +325,17 @@ const selectedForBinding = computed(() => {
292
325
  })
293
326
 
294
327
  function showUserSelectDialog() {
328
+ if (props.disabled) {
329
+ return;
330
+ }
295
331
  dialogUserSelector.value?.show();
296
332
  popoverVisible.value = false;
297
333
  }
298
334
 
299
335
  function handleMouseOver() {
336
+ if (props.disabled) {
337
+ return;
338
+ }
300
339
  popoverVisible.value = true
301
340
  }
302
341
 
@@ -314,12 +353,13 @@ watch(()=>props.classificationLevel, ()=>{
314
353
  <template>
315
354
  <ja-scrollbar :max-height="props.maxHeight" :height="props.height" v-bind="$attrs"
316
355
  style="overflow-x: hidden;">
317
- <div class="ja-user-picker__root" v-shortcut:[props.shortcut]="popup">
356
+ <div class="ja-user-picker__root" v-shortcut:[props.shortcut]="!props.disabled ? popup : undefined">
318
357
 
319
358
  <el-popover ref="bookmarkDropdown" @show="onPopoverShow" @hide="onPopoverHide"
320
359
  v-model:visible="popoverVisible"
321
360
  width="auto" teleported
322
361
  :trigger="props.trigger"
362
+ :disabled="props.disabled"
323
363
  >
324
364
  <template #reference>
325
365
  <div style="display: flex;align-items: center;">
@@ -327,6 +367,7 @@ watch(()=>props.classificationLevel, ()=>{
327
367
  v-if="$slots.default"
328
368
  name="default"></slot>
329
369
  <el-button v-else circle :icon="Plus" :size="'small'" ref="btn"
370
+ :disabled="props.disabled"
330
371
  @mouseover="handleMouseOver">
331
372
  </el-button>
332
373
  </div>
@@ -394,6 +435,7 @@ watch(()=>props.classificationLevel, ()=>{
394
435
  v-model="selectedUsers"
395
436
  v-model:page="pageParams.currentPage"
396
437
  :multiple="props.multiple"
438
+ :maxSelect="props.maxSelect"
397
439
  @item-clicked="onArrowKeyDown"
398
440
  @arrow-key-down="onArrowKeyDown"></ja-user-list>
399
441
  </div>
@@ -401,7 +443,7 @@ watch(()=>props.classificationLevel, ()=>{
401
443
  </el-popover>
402
444
  <div v-if="props.showUserTag" class="ja-user-picker__tag-container">
403
445
  <ja-user-info-tag v-for="u in selectedUsersForRender" :key="u.id" :user-id="u.id"
404
- :full-name="u.fullName" closable :has-avatar="u.hasAvatar"
446
+ :full-name="u.fullName" :closable="!props.disabled" :has-avatar="u.hasAvatar"
405
447
  @closed="onDeselected">
406
448
  </ja-user-info-tag>
407
449
  <div class="more-tag" v-if="selectedForBinding.length > props.maxShowCount">
@@ -417,6 +459,7 @@ watch(()=>props.classificationLevel, ()=>{
417
459
  :realm-id="localRealmId"
418
460
  :classificationLevel="props.classificationLevel"
419
461
  :customFilter="props.customFilter"
462
+ :maxSelect="props.maxSelect"
420
463
  v-model="selectedUsers"></ja-user-select-dialog>
421
464
  </div>
422
465
  </ja-scrollbar>
@@ -2,6 +2,7 @@
2
2
  import {
3
3
  ElDialog,
4
4
  ElIcon,
5
+ ElMessage,
5
6
  ElTableV2,
6
7
  ElText,
7
8
  ElTabs,
@@ -51,14 +52,18 @@ const props = defineProps({
51
52
  /**
52
53
  * 密级过滤
53
54
  */
54
- classificationLevel: {type: Number, required: false, default: false},
55
+ classificationLevel: {type: Number, required: false, default: 0},
55
56
  /**
56
57
  * 自定义筛选过滤条件,可以在此回调方法内使用qUser参数构造自定义的查询条件。例如 qUser => qUser.fullName.startWith('王')
57
58
  */
58
59
  customFilter: {
59
60
  type: Function as PropType<(qUser: InstanceType<typeof QUser>) => BooleanResultExpression>,
60
61
  required: false
61
- }
62
+ },
63
+ /**
64
+ * 最多选择数量,为0则不限制
65
+ */
66
+ maxSelect: {type: Number, required: false, default: 0}
62
67
  })
63
68
  const selectedUsers = defineModel<UserReference[] | UserReference | null>({
64
69
  required: true
@@ -212,6 +217,14 @@ function switchUserSelect(user: UserReference) {
212
217
  map.delete(user.id);
213
218
  tempSel.value.splice(index, 1);
214
219
  } else {
220
+ if (props.maxSelect > 0 && tempSel.value.length >= props.maxSelect) {
221
+ ElMessage({
222
+ message: `已超过最大选择数量 ${props.maxSelect}`,
223
+ type: 'warning',
224
+ plain: true,
225
+ })
226
+ return;
227
+ }
215
228
  map.set(user.id, user)
216
229
  tempSel.value.push(user);
217
230
  }
@@ -231,8 +244,17 @@ onUnmounted(() => {
231
244
  function selectAll() {
232
245
  if (Array.isArray(tempSel.value)) {
233
246
  const users = userQuery.queryResult.value.users.filter(u => !userSelected(u));
234
- users.forEach(u => map.set(u.id, u))
235
- tempSel.value.push(...users);
247
+ if (props.maxSelect > 0) {
248
+ const remaining = props.maxSelect - tempSel.value.length;
249
+ if (remaining > 0) {
250
+ const usersToAdd = users.slice(0, remaining);
251
+ usersToAdd.forEach(u => map.set(u.id, u))
252
+ tempSel.value.push(...usersToAdd);
253
+ }
254
+ } else {
255
+ users.forEach(u => map.set(u.id, u))
256
+ tempSel.value.push(...users);
257
+ }
236
258
  }
237
259
  }
238
260
 
@@ -247,13 +269,28 @@ function inverseSelection() {
247
269
  if (Array.isArray(tempSel.value)) {
248
270
  const users = userQuery.queryResult.value.users.filter(u => !userSelected(u))
249
271
  map.clear();
250
- users.forEach(u => map.set(u.id, u))
251
- tempSel.value.splice(0);
252
- tempSel.value.push(...users);
272
+ if (props.maxSelect > 0) {
273
+ const usersToAdd = users.slice(0, props.maxSelect);
274
+ usersToAdd.forEach(u => map.set(u.id, u))
275
+ tempSel.value.splice(0);
276
+ tempSel.value.push(...usersToAdd);
277
+ } else {
278
+ users.forEach(u => map.set(u.id, u))
279
+ tempSel.value.splice(0);
280
+ tempSel.value.push(...users);
281
+ }
253
282
  }
254
283
  }
255
284
 
256
285
  function onConfirm() {
286
+ if (props.multiple && Array.isArray(tempSel.value) && props.maxSelect > 0 && tempSel.value.length > props.maxSelect) {
287
+ ElMessage({
288
+ message: `已超过最大选择数量 ${props.maxSelect}`,
289
+ type: 'warning',
290
+ plain: true,
291
+ })
292
+ return;
293
+ }
257
294
  selectedUsers.value = tempSel.value;
258
295
  visible.value = false;
259
296
  }