cd-personselector 1.3.4 → 1.3.6

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": "cd-personselector",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "description": "人员选择器组件 - 支持多Tab、树形结构、搜索、懒加载、输入框选择模式",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -26,34 +26,49 @@
26
26
  >
27
27
  <template #panel>
28
28
  <div v-if="currentSearchResults.length > 0" class="cd-input-select__panel">
29
- <t-checkbox
30
- v-for="result in currentSearchResults"
31
- :key="result.id"
32
- :value="result.id"
33
- :checked="isSelected(result.id)"
34
- class="cd-input-select__option"
35
- @change="(checked: boolean) => handleCheckboxChange(checked, result)"
36
- >
37
- <div class="cd-input-select__option-content">
38
- <div v-if="result.avatar" class="cd-input-select__option-avatar">
39
- <img :src="result.avatar" :alt="result.name" />
40
- </div>
41
- <div v-else class="cd-input-select__option-avatar cd-input-select__option-avatar--placeholder"
42
- :style="{
43
- backgroundColor: isDepartment(result.id) ? '#e6f4ff' : getAvatarColor(result.name),
44
- color: isDepartment(result.id) ? '#0052d9' : '#fff'
45
- }">
46
- {{ getAvatarInitial(result.name) }}
47
- </div>
48
- <div class="cd-input-select__option-info">
49
- <div class="cd-input-select__option-name">{{ result.name }}</div>
50
- <div v-if="result.department || result.position" class="cd-input-select__option-desc">
51
- {{ result.department }}{{ result.department && result.position ? ' · ' : '' }}{{ result.position }}
29
+ <div class="cd-input-select__grid">
30
+ <t-checkbox
31
+ v-for="result in paginatedSearchResults"
32
+ :key="result.id"
33
+ :value="result.id"
34
+ :checked="isSelected(result.id)"
35
+ class="cd-input-select__option"
36
+ @change="(checked: boolean) => handleCheckboxChange(checked, result)"
37
+ >
38
+ <div class="cd-input-select__option-content">
39
+ <div v-if="result.avatar" class="cd-input-select__option-avatar">
40
+ <img :src="result.avatar" :alt="result.name" />
41
+ </div>
42
+ <div v-else class="cd-input-select__option-avatar cd-input-select__option-avatar--placeholder"
43
+ :style="{
44
+ backgroundColor: getNodeTypeColor(result.nodeType),
45
+ color: '#fff'
46
+ }">
47
+ {{ getNodeTypeIcon(result.nodeType) }}
52
48
  </div>
49
+ <div class="cd-input-select__option-info">
50
+ <div class="cd-input-select__option-name">{{ result.name }}</div>
51
+ <div v-if="result.isUser && (result.department || result.post)" class="cd-input-select__option-desc">
52
+ {{ result.department }}{{ result.department && result.post ? ' · ' : '' }}{{ result.post }}
53
+ </div>
54
+ <div v-else-if="result.fnumber" class="cd-input-select__option-desc">
55
+ {{ result.fnumber }}
56
+ </div>
57
+ </div>
58
+ <span class="cd-input-select__option-tag" :style="{ backgroundColor: getNodeTypeColor(result.nodeType) + '18', color: getNodeTypeColor(result.nodeType) }">
59
+ {{ getNodeTypeLabel(result.nodeType) }}
60
+ </span>
53
61
  </div>
54
- <i v-if="isDepartment(result.id)" class="ri-building-line cd-input-select__option-icon"></i>
62
+ </t-checkbox>
63
+ </div>
64
+ <div v-if="searchTotalPages > 1" class="cd-input-select__pager">
65
+ <span class="cd-input-select__pager-info">{{ currentSearchResults.length }} 条结果</span>
66
+ <div class="cd-input-select__pager-btns">
67
+ <t-button size="small" variant="text" :disabled="searchPage <= 1" @click="searchPage--">上一页</t-button>
68
+ <span class="cd-input-select__pager-num">{{ searchPage }}/{{ searchTotalPages }}</span>
69
+ <t-button size="small" variant="text" :disabled="searchPage >= searchTotalPages" @click="searchPage++">下一页</t-button>
55
70
  </div>
56
- </t-checkbox>
71
+ </div>
57
72
  </div>
58
73
  <div v-else-if="searchLoading" class="cd-input-select__loading">
59
74
  搜索中...
@@ -108,6 +123,13 @@ const emit = defineEmits<{
108
123
  }>();
109
124
  const searchLoading = ref(false);
110
125
  const currentSearchResults = ref<UserItem[]>([]);
126
+ const searchPage = ref(1);
127
+ const searchPageSize = 10;
128
+ const paginatedSearchResults = computed(() => {
129
+ const start = (searchPage.value - 1) * searchPageSize;
130
+ return currentSearchResults.value.slice(start, start + searchPageSize);
131
+ });
132
+ const searchTotalPages = computed(() => Math.ceil(currentSearchResults.value.length / searchPageSize));
111
133
  const showPersonSelector = ref(false);
112
134
  const selectedIds = ref<(string | number)[]>([]);
113
135
  const selectedOptions = ref<UserItem[]>([]);
@@ -137,6 +159,19 @@ const isDepartment = (id: string | number): boolean => {
137
159
  return String(id).startsWith('dept-');
138
160
  };
139
161
 
162
+ // nodeType 辅助函数
163
+ const nodeTypeMap: Record<string, { label: string; color: string; icon: string }> = {
164
+ department: { label: '部门', color: '#0052d9', icon: '部' },
165
+ user: { label: '人员', color: '#1890ff', icon: '人' },
166
+ post: { label: '岗位', color: '#52c41a', icon: '岗' },
167
+ position: { label: '职务', color: '#722ed1', icon: '职' },
168
+ business: { label: '商业', color: '#fa8c16', icon: '商' },
169
+ external: { label: '外部', color: '#eb2f96', icon: '外' },
170
+ };
171
+ const getNodeTypeLabel = (t?: string) => nodeTypeMap[t || '']?.label || '其他';
172
+ const getNodeTypeColor = (t?: string) => nodeTypeMap[t || '']?.color || '#999';
173
+ const getNodeTypeIcon = (t?: string) => nodeTypeMap[t || '']?.icon || '?';
174
+
140
175
  // 根据 value (ID) 获取标签主题颜色
141
176
  const getTagThemeByValue = (value: string | number): 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info' => {
142
177
  const idStr = String(value);
@@ -277,6 +312,7 @@ watch(inputValue, (keyword) => {
277
312
  keyword,
278
313
  callback: (results: UserItem[]) => {
279
314
  currentSearchResults.value = results;
315
+ searchPage.value = 1;
280
316
  searchLoading.value = false;
281
317
  if (results.length > 0) {
282
318
  popupVisible.value = true;
@@ -313,8 +349,37 @@ watch(inputValue, (keyword) => {
313
349
  margin: 0 !important;
314
350
  }
315
351
  &__panel {
316
- max-height: 300px;
352
+ max-height: 400px;
317
353
  overflow-y: auto;
354
+ width: 100%;
355
+ box-sizing: border-box;
356
+ padding: 4px;
357
+ }
358
+ &__grid {
359
+ display: grid;
360
+ grid-template-columns: 1fr 1fr;
361
+ gap: 4px;
362
+ }
363
+ &__pager {
364
+ display: flex;
365
+ align-items: center;
366
+ justify-content: space-between;
367
+ padding: 6px 8px;
368
+ border-top: 1px solid #e5e7eb;
369
+ margin-top: 4px;
370
+ &-info {
371
+ font-size: 12px;
372
+ color: #999;
373
+ }
374
+ &-btns {
375
+ display: flex;
376
+ align-items: center;
377
+ gap: 4px;
378
+ }
379
+ &-num {
380
+ font-size: 12px;
381
+ color: #666;
382
+ }
318
383
  }
319
384
  &__loading,
320
385
  &__empty {
@@ -383,5 +448,12 @@ watch(inputValue, (keyword) => {
383
448
  color: var(--td-brand-color, #0052d9);
384
449
  flex-shrink: 0;
385
450
  }
451
+ &-tag {
452
+ font-size: 11px;
453
+ padding: 1px 6px;
454
+ border-radius: 3px;
455
+ flex-shrink: 0;
456
+ white-space: nowrap;
457
+ }
386
458
  }
387
459
  </style>
@@ -6,6 +6,7 @@
6
6
  :footer="true"
7
7
  placement="center"
8
8
  destroy-on-close
9
+ attach="body"
9
10
  @confirm="handleConfirm"
10
11
  @close="handleClose"
11
12
  >