@blueking/bk-user-selector 0.0.1-beta.2 → 0.0.2-beta.1

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.
@@ -1,5 +1,236 @@
1
- import { ref, onBeforeMount, watch, createBlock, openBlock, unref, withCtx, createElementBlock, Fragment, renderList, onMounted, computed, nextTick, withDirectives, createCommentVNode, createVNode, createElementVNode, normalizeClass, withModifiers, toDisplayString, vModelText, createTextVNode, pushScopeId, popScopeId } from "vue";
2
- import { Select, Popover, Tag, Loading, clickoutside } from "bkui-vue";
1
+ import { ref, onBeforeMount, defineComponent, watch, createBlock, openBlock, unref, withCtx, createElementBlock, Fragment, renderList, createElementVNode, createCommentVNode, toDisplayString, withModifiers, normalizeClass, nextTick, onMounted, withDirectives, createVNode, isRef, vModelText, createTextVNode, computed } from "vue";
2
+ import { Select, Tag, Popover, Loading, clickoutside } from "bkui-vue";
3
+ const getTenants = async (apiBaseUrl, tenantId) => {
4
+ if (!apiBaseUrl || !tenantId) {
5
+ console.warn("获取租户信息需要提供有效的API基础URL和租户ID");
6
+ return [];
7
+ }
8
+ try {
9
+ const url = `${apiBaseUrl}/api/v3/open-web/tenant/data-source-owner-tenants/`;
10
+ const response = await fetch(url, {
11
+ method: "GET",
12
+ headers: {
13
+ "x-bk-tenant-id": tenantId
14
+ },
15
+ credentials: "include"
16
+ });
17
+ if (!response.ok) {
18
+ throw new Error(`获取租户信息失败: ${response.status} ${response.statusText}`);
19
+ }
20
+ const data = await response.json();
21
+ return data.data || [];
22
+ } catch (error) {
23
+ console.error("获取租户信息失败:", error);
24
+ return [];
25
+ }
26
+ };
27
+ const searchUsers = async (apiBaseUrl, tenantId, keyword = "") => {
28
+ if (!keyword || !apiBaseUrl || !tenantId) {
29
+ console.warn("搜索用户需要提供有效的API基础URL、租户ID和关键词");
30
+ return [];
31
+ }
32
+ try {
33
+ const url = `${apiBaseUrl}/api/v3/open-web/tenant/users/-/search/?keyword=${encodeURIComponent(keyword)}`;
34
+ const response = await fetch(url, {
35
+ method: "GET",
36
+ headers: {
37
+ "x-bk-tenant-id": tenantId
38
+ },
39
+ credentials: "include"
40
+ });
41
+ if (!response.ok) {
42
+ throw new Error(`搜索用户失败: ${response.status} ${response.statusText}`);
43
+ }
44
+ const data = await response.json();
45
+ return data.data || [];
46
+ } catch (error) {
47
+ console.error("搜索用户失败:", error);
48
+ return [];
49
+ }
50
+ };
51
+ const lookupUsers = async (apiBaseUrl, tenantId, users = []) => {
52
+ if (users.length === 0 || !apiBaseUrl || !tenantId) {
53
+ console.warn("批量查找用户需要提供有效的API基础URL、租户ID和至少一个用户名");
54
+ return [];
55
+ }
56
+ try {
57
+ const url = `${apiBaseUrl}/api/v3/open-web/tenant/users/-/lookup/?lookups=${users.join(",")}&lookup_fields=bk_username`;
58
+ const response = await fetch(url, {
59
+ method: "GET",
60
+ headers: {
61
+ "x-bk-tenant-id": tenantId
62
+ },
63
+ credentials: "include"
64
+ });
65
+ if (!response.ok) {
66
+ throw new Error(`批量查找用户失败: ${response.status} ${response.statusText}`);
67
+ }
68
+ const data = await response.json();
69
+ return data.data || [];
70
+ } catch (error) {
71
+ console.error("批量查找用户失败:", error);
72
+ return [];
73
+ }
74
+ };
75
+ const formatUsers = (users) => {
76
+ if (!users || !Array.isArray(users)) return [];
77
+ return users.map((user) => ({
78
+ id: user.bk_username,
79
+ name: user.display_name,
80
+ tenantId: user.owner_tenant_id
81
+ }));
82
+ };
83
+ const useTenantData = (apiBaseUrl, tenantId) => {
84
+ const tenants = ref({});
85
+ const loading = ref(false);
86
+ const fetchTenants = async () => {
87
+ if (!apiBaseUrl || !tenantId) {
88
+ console.warn("获取租户需要提供有效的API基础URL和租户ID");
89
+ return;
90
+ }
91
+ loading.value = true;
92
+ try {
93
+ const result = await getTenants(apiBaseUrl, tenantId);
94
+ const tenantMap = {};
95
+ result.forEach((item) => {
96
+ if (item.id && item.name) {
97
+ tenantMap[item.id] = item.name;
98
+ }
99
+ });
100
+ tenants.value = tenantMap;
101
+ } catch (error) {
102
+ console.error("获取租户数据失败:", error);
103
+ } finally {
104
+ loading.value = false;
105
+ }
106
+ };
107
+ onBeforeMount(() => {
108
+ fetchTenants();
109
+ });
110
+ return {
111
+ tenants,
112
+ loading,
113
+ fetchTenants
114
+ };
115
+ };
116
+ const _hoisted_1$3 = { class: "user-option" };
117
+ const _hoisted_2$2 = { class: "user-name" };
118
+ const _hoisted_3$2 = {
119
+ key: 0,
120
+ class: "tenant-name"
121
+ };
122
+ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
123
+ ...{
124
+ name: "BkUserSelectorSingle"
125
+ },
126
+ __name: "single-selector",
127
+ props: {
128
+ modelValue: {},
129
+ placeholder: {},
130
+ apiBaseUrl: {},
131
+ tenantId: {},
132
+ tenants: {}
133
+ },
134
+ emits: ["update:modelValue", "change"],
135
+ setup(__props, { emit: __emit }) {
136
+ const props = __props;
137
+ const emit = __emit;
138
+ const { tenants } = useTenantData(props.apiBaseUrl || "", props.tenantId || "");
139
+ const options = ref([]);
140
+ const loading = ref(false);
141
+ const selectedUser = ref(props.modelValue || "");
142
+ onBeforeMount(async () => {
143
+ if (typeof props.modelValue === "string" && props.modelValue) {
144
+ try {
145
+ loading.value = true;
146
+ const result = await lookupUsers(props.apiBaseUrl || "", props.tenantId || "", [props.modelValue]);
147
+ options.value = formatUsers(result);
148
+ } catch (error) {
149
+ console.error("获取用户信息失败:", error);
150
+ } finally {
151
+ loading.value = false;
152
+ }
153
+ }
154
+ });
155
+ const fetchUsers = async (keyword = "") => {
156
+ if (!keyword || keyword.length < 2) return [];
157
+ loading.value = true;
158
+ try {
159
+ const result = await searchUsers(props.apiBaseUrl || "", props.tenantId || "", keyword);
160
+ const formattedResults = formatUsers(result);
161
+ options.value = formattedResults;
162
+ return formattedResults;
163
+ } catch (error) {
164
+ console.error("获取用户列表失败:", error);
165
+ return [];
166
+ } finally {
167
+ loading.value = false;
168
+ }
169
+ };
170
+ watch(selectedUser, (newVal) => {
171
+ emit("update:modelValue", newVal);
172
+ const selectedUserInfo = options.value.find((user) => user.id === newVal);
173
+ emit("change", selectedUserInfo || null);
174
+ });
175
+ return (_ctx, _cache) => {
176
+ return openBlock(), createBlock(unref(Select), {
177
+ modelValue: selectedUser.value,
178
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => selectedUser.value = $event),
179
+ placeholder: _ctx.placeholder,
180
+ "input-search": true,
181
+ "remote-method": fetchUsers,
182
+ filterable: ""
183
+ }, {
184
+ default: withCtx(() => [
185
+ (openBlock(true), createElementBlock(
186
+ Fragment,
187
+ null,
188
+ renderList(options.value, (item) => {
189
+ return openBlock(), createBlock(unref(Select).Option, {
190
+ key: item.id,
191
+ label: item.name,
192
+ value: item.id
193
+ }, {
194
+ default: withCtx(() => [
195
+ createElementVNode("div", _hoisted_1$3, [
196
+ createElementVNode(
197
+ "span",
198
+ _hoisted_2$2,
199
+ toDisplayString(item.name),
200
+ 1
201
+ /* TEXT */
202
+ ),
203
+ item.tenantId !== props.tenantId && item.tenantId && unref(tenants)[item.tenantId] ? (openBlock(), createElementBlock(
204
+ "span",
205
+ _hoisted_3$2,
206
+ "@" + toDisplayString(unref(tenants)[item.tenantId]),
207
+ 1
208
+ /* TEXT */
209
+ )) : createCommentVNode("v-if", true)
210
+ ])
211
+ ]),
212
+ _: 2
213
+ /* DYNAMIC */
214
+ }, 1032, ["label", "value"]);
215
+ }),
216
+ 128
217
+ /* KEYED_FRAGMENT */
218
+ ))
219
+ ]),
220
+ _: 1
221
+ /* STABLE */
222
+ }, 8, ["modelValue", "placeholder"]);
223
+ };
224
+ }
225
+ });
226
+ const _export_sfc = (sfc, props) => {
227
+ const target = sfc.__vccOpts || sfc;
228
+ for (const [key, val] of props) {
229
+ target[key] = val;
230
+ }
231
+ return target;
232
+ };
233
+ const SingleSelector = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-0ae61279"]]);
3
234
  /**!
4
235
  * Sortable 1.15.6
5
236
  * @author RubaXa <trash@rubaxa.org>
@@ -2157,716 +2388,711 @@ _extends(Remove, {
2157
2388
  });
2158
2389
  Sortable.mount(new AutoScrollPlugin());
2159
2390
  Sortable.mount(Remove, Revert);
2160
- const _sfc_main$1 = {
2161
- __name: "single",
2162
- props: {
2163
- modelValue: {
2164
- type: String,
2165
- default: ""
2166
- },
2167
- apiBaseUrl: {
2168
- type: String,
2169
- default: ""
2170
- },
2171
- tenantId: {
2172
- type: String,
2173
- default: ""
2391
+ const debounce = (fn, delay) => {
2392
+ let timer = null;
2393
+ return function(...args) {
2394
+ if (timer) {
2395
+ clearTimeout(timer);
2396
+ }
2397
+ timer = window.setTimeout(() => {
2398
+ fn(...args);
2399
+ timer = null;
2400
+ }, delay);
2401
+ };
2402
+ };
2403
+ const calculateVisibleTags = (container, items, containerWidth) => {
2404
+ if (!container || !items.length) return 0;
2405
+ let totalWidth = 0;
2406
+ let visibleCount = 0;
2407
+ for (let i = 0; i < items.length; i++) {
2408
+ const item = items[i];
2409
+ if (!item) continue;
2410
+ const style = window.getComputedStyle(item);
2411
+ const width = item.offsetWidth;
2412
+ const marginLeft = parseInt(style.marginLeft, 10) || 0;
2413
+ const marginRight = parseInt(style.marginRight, 10) || 0;
2414
+ totalWidth += width + marginLeft + marginRight;
2415
+ if (totalWidth > containerWidth) {
2416
+ break;
2417
+ }
2418
+ visibleCount++;
2419
+ }
2420
+ return Math.max(1, visibleCount);
2421
+ };
2422
+ const useUserSearch = (apiBaseUrl, tenantId) => {
2423
+ const searchResults = ref([]);
2424
+ const loading = ref(false);
2425
+ const searchQuery = ref("");
2426
+ const performSearch = async (keyword) => {
2427
+ if (!keyword || keyword.length < 2) {
2428
+ searchResults.value = [];
2429
+ return;
2430
+ }
2431
+ if (!apiBaseUrl || !tenantId) {
2432
+ console.warn("执行用户搜索需要提供有效的API基础URL和租户ID");
2433
+ return;
2434
+ }
2435
+ loading.value = true;
2436
+ try {
2437
+ const results = await searchUsers(apiBaseUrl, tenantId, keyword);
2438
+ searchResults.value = formatUsers(results);
2439
+ } catch (error) {
2440
+ console.error("用户搜索失败:", error);
2441
+ searchResults.value = [];
2442
+ } finally {
2443
+ loading.value = false;
2174
2444
  }
2445
+ };
2446
+ const debouncedSearch = debounce(performSearch, 300);
2447
+ const handleSearchInput = (value) => {
2448
+ searchQuery.value = value;
2449
+ debouncedSearch(value);
2450
+ };
2451
+ const clearSearch = () => {
2452
+ searchQuery.value = "";
2453
+ searchResults.value = [];
2454
+ };
2455
+ return {
2456
+ searchResults,
2457
+ loading,
2458
+ searchQuery,
2459
+ performSearch,
2460
+ handleSearchInput,
2461
+ clearSearch
2462
+ };
2463
+ };
2464
+ const _hoisted_1$2 = { class: "tag-content" };
2465
+ const _hoisted_2$1 = { class: "user-name" };
2466
+ const _hoisted_3$1 = {
2467
+ key: 0,
2468
+ class: "tenant-name"
2469
+ };
2470
+ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
2471
+ ...{
2472
+ name: "UserTag"
2175
2473
  },
2176
- emits: ["update:modelValue", "change"],
2474
+ __name: "user-tag",
2475
+ props: {
2476
+ user: {},
2477
+ tenants: {},
2478
+ currentTenantId: {},
2479
+ draggable: { type: Boolean },
2480
+ active: { type: Boolean },
2481
+ showTenant: { type: Boolean }
2482
+ },
2483
+ emits: ["click", "close"],
2177
2484
  setup(__props, { emit: __emit }) {
2178
- const props = __props;
2179
2485
  const emit = __emit;
2180
- const options = ref([]);
2181
- const loading = ref(false);
2182
- const selectedUser = ref(props.modelValue);
2183
- onBeforeMount(async () => {
2184
- if (props.modelValue) {
2185
- const url = `${props.apiBaseUrl}/api/v3/open-web/tenant/users/-/lookup/?lookups=${props.modelValue}&lookup_fields=bk_username`;
2186
- const response = await fetch(url, {
2187
- method: "GET",
2188
- headers: {
2189
- ...apiConfig.headers
2190
- },
2191
- credentials: "include"
2192
- // 包含cookie
2193
- });
2194
- const data = await response.json();
2195
- console.log("data", data);
2196
- options.value = data.data.map((item) => ({
2197
- id: item.bk_username,
2198
- name: item.display_name
2199
- }));
2200
- }
2201
- });
2202
- const apiConfig = {
2203
- baseUrl: `${props.apiBaseUrl}/api/v3/open-web/tenant/users/-/search/`,
2204
- headers: {
2205
- "x-bk-tenant-id": props.tenantId
2206
- }
2486
+ const handleClick = () => {
2487
+ emit("click");
2207
2488
  };
2208
- const fetchUsers = async (keyword = "") => {
2209
- var _a;
2210
- if (!keyword || keyword.length < 2) return [];
2211
- loading.value = true;
2212
- try {
2213
- const url = `${apiConfig.baseUrl}?keyword=${encodeURIComponent(keyword)}`;
2214
- const response = await fetch(url, {
2215
- method: "GET",
2216
- headers: {
2217
- ...apiConfig.headers
2218
- },
2219
- credentials: "include"
2220
- // 包含cookie
2221
- });
2222
- const data = await response.json();
2223
- console.log("data", data);
2224
- const result = ((_a = data.data) == null ? void 0 : _a.filter((item) => {
2225
- var _a2;
2226
- return !((_a2 = selectedUser.value) == null ? void 0 : _a2.includes(item.bk_username));
2227
- }).map((item) => ({
2228
- id: item.bk_username,
2229
- name: item.display_name
2230
- }))) || [];
2231
- options.value = result;
2232
- return result;
2233
- } catch (error) {
2234
- console.error("获取用户列表失败:", error);
2235
- return [];
2236
- } finally {
2237
- loading.value = false;
2238
- }
2489
+ const handleClose = () => {
2490
+ emit("close");
2239
2491
  };
2240
- watch(selectedUser, (newVal) => {
2241
- console.log("newVal", newVal);
2242
- emit("update:modelValue", newVal);
2243
- });
2244
2492
  return (_ctx, _cache) => {
2245
- return openBlock(), createBlock(unref(Select), {
2246
- filterable: "",
2247
- "input-search": true,
2248
- "remote-method": fetchUsers,
2249
- modelValue: selectedUser.value,
2250
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => selectedUser.value = $event)
2493
+ return openBlock(), createBlock(unref(Tag), {
2494
+ class: normalizeClass(["user-tag", { draggable: _ctx.draggable, active: _ctx.active }]),
2495
+ closable: "",
2496
+ onClick: withModifiers(handleClick, ["stop"]),
2497
+ onClose: handleClose
2251
2498
  }, {
2252
2499
  default: withCtx(() => [
2253
- (openBlock(true), createElementBlock(
2254
- Fragment,
2255
- null,
2256
- renderList(options.value, (item) => {
2257
- return openBlock(), createBlock(unref(Select).Option, {
2258
- key: item.id,
2259
- value: item.id,
2260
- label: item.name
2261
- }, null, 8, ["value", "label"]);
2262
- }),
2263
- 128
2264
- /* KEYED_FRAGMENT */
2265
- ))
2500
+ createElementVNode("div", _hoisted_1$2, [
2501
+ createElementVNode(
2502
+ "span",
2503
+ _hoisted_2$1,
2504
+ toDisplayString(_ctx.user.name),
2505
+ 1
2506
+ /* TEXT */
2507
+ ),
2508
+ _ctx.showTenant && _ctx.user.tenantId && _ctx.tenants[_ctx.user.tenantId] ? (openBlock(), createElementBlock(
2509
+ "span",
2510
+ _hoisted_3$1,
2511
+ " @" + toDisplayString(_ctx.tenants[_ctx.user.tenantId]),
2512
+ 1
2513
+ /* TEXT */
2514
+ )) : createCommentVNode("v-if", true)
2515
+ ])
2266
2516
  ]),
2267
2517
  _: 1
2268
2518
  /* STABLE */
2269
- }, 8, ["modelValue"]);
2519
+ }, 8, ["class"]);
2270
2520
  };
2271
2521
  }
2522
+ });
2523
+ const UserTag = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-cf26d9e2"]]);
2524
+ const _hoisted_1$1 = ["onClick"];
2525
+ const _hoisted_2 = ["placeholder"];
2526
+ const _hoisted_3 = ["placeholder"];
2527
+ const _hoisted_4 = {
2528
+ key: 0,
2529
+ class: "no-data"
2272
2530
  };
2273
- const _export_sfc = (sfc, props) => {
2274
- const target = sfc.__vccOpts || sfc;
2275
- for (const [key, val] of props) {
2276
- target[key] = val;
2277
- }
2278
- return target;
2279
- };
2280
- const _withScopeId = (n) => (pushScopeId("data-v-5e8a952d"), n = n(), popScopeId(), n);
2281
- const _hoisted_1 = { class: "popover-content" };
2282
- const _hoisted_2 = { key: 0 };
2283
- const _hoisted_3 = /* @__PURE__ */ _withScopeId(() => /* @__PURE__ */ createElementVNode(
2284
- "div",
2285
- { class: "no-data" },
2286
- [
2287
- /* @__PURE__ */ createElementVNode("span", null, "暂无数据")
2288
- ],
2289
- -1
2290
- /* HOISTED */
2291
- ));
2292
- const _hoisted_4 = [
2293
- _hoisted_3
2294
- ];
2295
2531
  const _hoisted_5 = ["onMousedown"];
2296
- const _hoisted_6 = ["src"];
2532
+ const _hoisted_6 = { class: "user-name" };
2297
2533
  const _hoisted_7 = {
2298
- key: 1,
2299
- class: "user-avatar-text"
2534
+ key: 0,
2535
+ class: "tenant-name"
2300
2536
  };
2301
- const _hoisted_8 = { class: "user-name" };
2302
- const _hoisted_9 = {
2303
- ref: "sortableContainer",
2304
- class: "tag-group"
2305
- };
2306
- const _hoisted_10 = ["onClick"];
2307
- const _hoisted_11 = { class: "tag-content" };
2308
- const _hoisted_12 = { class: "user-name" };
2309
- const _hoisted_13 = ["placeholder"];
2310
- const _hoisted_14 = ["placeholder"];
2311
- const _hoisted_15 = { class: "tag-content" };
2312
- const _hoisted_16 = { class: "user-name" };
2313
- const _hoisted_17 = ["placeholder"];
2314
- const _sfc_main = {
2315
- __name: "index",
2537
+ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
2538
+ ...{
2539
+ name: "BkUserSelectorMultiple"
2540
+ },
2541
+ __name: "multiple-selector",
2316
2542
  props: {
2317
- // 文本标签
2318
- label: {
2319
- type: String,
2320
- default: "人员选择"
2321
- },
2322
- // 是否必填
2323
- required: {
2324
- type: Boolean,
2325
- default: false
2543
+ /**
2544
+ * 绑定值
2545
+ */
2546
+ modelValue: {
2547
+ type: Array,
2548
+ default: () => []
2326
2549
  },
2327
- // 占位文字
2550
+ /**
2551
+ * 占位文本
2552
+ */
2328
2553
  placeholder: {
2329
2554
  type: String,
2330
2555
  default: "请输入人员名称搜索"
2331
2556
  },
2332
- // 默认选中的用户(单选时为字符串,多选时为数组)
2333
- modelValue: {
2334
- type: [String, Array],
2335
- default: ""
2336
- },
2337
- // 是否支持拖拽排序
2557
+ /**
2558
+ * 是否可拖拽
2559
+ */
2338
2560
  draggable: {
2339
2561
  type: Boolean,
2340
2562
  default: false
2341
2563
  },
2342
- // 是否多选
2343
- multiple: {
2344
- type: Boolean,
2345
- default: false
2346
- },
2347
- // 接口基础URL
2564
+ /**
2565
+ * API 基础 URL
2566
+ */
2348
2567
  apiBaseUrl: {
2349
2568
  type: String,
2350
2569
  default: ""
2351
2570
  },
2352
- // 租户ID
2571
+ /**
2572
+ * 租户 ID
2573
+ */
2353
2574
  tenantId: {
2354
2575
  type: String,
2355
2576
  default: ""
2577
+ },
2578
+ /**
2579
+ * 已选用户
2580
+ */
2581
+ selectedUsers: {
2582
+ type: Array,
2583
+ default: () => []
2356
2584
  }
2357
2585
  },
2358
- emits: ["update:modelValue", "change"],
2586
+ emits: ["update:selectedUsers", "add-user", "remove-user"],
2359
2587
  setup(__props, { emit: __emit }) {
2360
- const vClickoutside = clickoutside;
2361
- const vLoading = Loading;
2362
2588
  const props = __props;
2363
2589
  const emit = __emit;
2364
- const selectedUsers = ref(props.modelValue || []);
2365
- const searchQuery = ref("");
2366
- const loading = ref(false);
2367
- const showDropdown = ref(false);
2368
- const searchInput = ref(null);
2590
+ const { tenants } = useTenantData(props.apiBaseUrl, props.tenantId);
2591
+ const {
2592
+ searchResults,
2593
+ loading: searchLoading,
2594
+ searchQuery,
2595
+ handleSearchInput
2596
+ } = useUserSearch(props.apiBaseUrl, props.tenantId);
2369
2597
  const containerRef = ref(null);
2370
- const tagInlineContainerRef = ref(null);
2598
+ const tagsContainerRef = ref(null);
2599
+ const sortableContainerRef = ref(null);
2600
+ const collapsedContainerRef = ref(null);
2601
+ const inlineInputRef = ref(null);
2602
+ const lastInputRef = ref(null);
2603
+ const collapsedInputRef = ref(null);
2371
2604
  const isFocused = ref(false);
2372
- const visibleUsers = ref([]);
2373
- const hiddenCount = ref(0);
2374
- const userList = ref([]);
2375
- let searchTimer = null;
2605
+ const showDropdown = ref(false);
2376
2606
  const activeTagIndex = ref(-1);
2377
- const measureEl = ref(null);
2378
2607
  const sortableInstance = ref(null);
2379
- const apiConfig = {
2380
- baseUrl: `${props.apiBaseUrl}/api/v3/open-web/tenant/users/-/search/`,
2381
- headers: {
2382
- "x-bk-tenant-id": props.tenantId
2383
- }
2608
+ const visibleUsers = ref([]);
2609
+ const hiddenCount = ref(0);
2610
+ const initSortable = () => {
2611
+ if (!props.draggable || !sortableContainerRef.value) return;
2612
+ if (sortableInstance.value) {
2613
+ sortableInstance.value.destroy();
2614
+ }
2615
+ sortableInstance.value = new Sortable(sortableContainerRef.value, {
2616
+ animation: 150,
2617
+ draggable: ".tag-wrapper",
2618
+ onEnd: (evt) => {
2619
+ const { oldIndex: oldIndex2, newIndex: newIndex2 } = evt;
2620
+ if (oldIndex2 === newIndex2 || oldIndex2 === void 0 || newIndex2 === void 0) return;
2621
+ const updatedUsers = [...props.selectedUsers];
2622
+ const [movedUser] = updatedUsers.splice(oldIndex2, 1);
2623
+ updatedUsers.splice(newIndex2, 0, movedUser);
2624
+ emit("update:selectedUsers", updatedUsers);
2625
+ activeTagIndex.value = newIndex2;
2626
+ }
2627
+ });
2384
2628
  };
2385
- const fetchUsers = async (keyword = "") => {
2386
- var _a;
2387
- if (!keyword) return [];
2388
- loading.value = true;
2389
- try {
2390
- const url = `${apiConfig.baseUrl}?keyword=${encodeURIComponent(keyword)}`;
2391
- const response = await fetch(url, {
2392
- method: "GET",
2393
- headers: {
2394
- ...apiConfig.headers
2395
- },
2396
- credentials: "include"
2397
- // 包含cookie
2398
- });
2399
- const data = await response.json();
2400
- const selectedIds = selectedUsers.value.map((user) => user.id);
2401
- return ((_a = data.data) == null ? void 0 : _a.filter((item) => !selectedIds.includes(item.bk_username)).map((item) => ({
2402
- id: item.bk_username,
2403
- name: item.login_name,
2404
- avatar: item.avatar_url || ""
2405
- }))) || [];
2406
- } catch (error) {
2407
- console.error("获取用户列表失败:", error);
2408
- return [];
2409
- } finally {
2410
- loading.value = false;
2629
+ const calculateVisibleUsers = () => {
2630
+ if (!collapsedContainerRef.value || !props.selectedUsers.length) {
2631
+ visibleUsers.value = [];
2632
+ hiddenCount.value = 0;
2633
+ return;
2411
2634
  }
2635
+ const containerWidth = collapsedContainerRef.value.offsetWidth;
2636
+ const tagElements = collapsedContainerRef.value.querySelectorAll(".user-tag");
2637
+ const visibleCount = calculateVisibleTags(
2638
+ collapsedContainerRef.value,
2639
+ tagElements,
2640
+ containerWidth - 100
2641
+ // 为输入框和更多标签预留空间
2642
+ );
2643
+ visibleUsers.value = props.selectedUsers.slice(0, visibleCount);
2644
+ hiddenCount.value = Math.max(0, props.selectedUsers.length - visibleCount);
2412
2645
  };
2413
- watch(() => props.modelValue, (val) => {
2414
- selectedUsers.value = val || [];
2415
- });
2416
- watch(() => selectedUsers.value, (val) => {
2417
- emit("update:modelValue", val);
2418
- });
2419
- const getAvatarText = (name) => name ? name.charAt(0) : "";
2420
- const updateInputWidth = () => {
2421
- if (!searchInput.value || !measureEl.value) return;
2422
- measureEl.value.textContent = searchQuery.value || searchInput.value.placeholder || "";
2423
- const width = Math.max(20, measureEl.value.offsetWidth + 10);
2424
- searchInput.value.style.width = `${width}px`;
2646
+ const handleFocus = () => {
2647
+ isFocused.value = true;
2648
+ showDropdown.value = false;
2649
+ activeTagIndex.value = -1;
2650
+ nextTick(() => {
2651
+ if (lastInputRef.value) {
2652
+ lastInputRef.value.focus();
2653
+ }
2654
+ });
2425
2655
  };
2426
- const handleInput = () => {
2427
- if (searchTimer) {
2428
- clearTimeout(searchTimer);
2429
- }
2430
- loading.value = true;
2431
- showDropdown.value = true;
2432
- searchTimer = setTimeout(async () => {
2433
- if (searchQuery.value.length >= 2) {
2434
- userList.value = await fetchUsers(searchQuery.value);
2435
- } else {
2436
- userList.value = [];
2656
+ const handleClickOutside = () => {
2657
+ isFocused.value = false;
2658
+ showDropdown.value = false;
2659
+ activeTagIndex.value = -1;
2660
+ searchQuery.value = "";
2661
+ nextTick(() => {
2662
+ calculateVisibleUsers();
2663
+ });
2664
+ };
2665
+ const handleContainerClick = () => {
2666
+ activeTagIndex.value = -1;
2667
+ nextTick(() => {
2668
+ if (lastInputRef.value) {
2669
+ lastInputRef.value.focus();
2437
2670
  }
2438
- loading.value = false;
2439
- }, 300);
2440
- updateInputWidth();
2671
+ });
2441
2672
  };
2442
2673
  const handleTagClick = (index2) => {
2443
2674
  activeTagIndex.value = index2;
2444
- setTimeout(() => {
2445
- var _a;
2446
- (_a = searchInput.value) == null ? void 0 : _a.focus();
2447
- }, 0);
2675
+ nextTick(() => {
2676
+ if (activeTagIndex.value === props.selectedUsers.length - 1) {
2677
+ if (lastInputRef.value) {
2678
+ lastInputRef.value.focus();
2679
+ }
2680
+ } else if (inlineInputRef.value) {
2681
+ inlineInputRef.value.focus();
2682
+ }
2683
+ });
2448
2684
  };
2449
- const handleFocus = () => {
2685
+ const handleInputFocus = () => {
2450
2686
  if (!isFocused.value) {
2451
2687
  isFocused.value = true;
2452
- activeTagIndex.value = selectedUsers.value.length - 1;
2453
2688
  }
2454
- showDropdown.value = true;
2455
- setTimeout(() => {
2456
- var _a;
2457
- (_a = searchInput.value) == null ? void 0 : _a.focus();
2458
- }, 0);
2689
+ if (searchQuery.value.length >= 2) {
2690
+ showDropdown.value = true;
2691
+ }
2459
2692
  };
2460
- const addUser = (user) => {
2461
- if (!selectedUsers.value.some((item) => item.id === user.id)) {
2693
+ const handleInput = () => {
2694
+ handleSearchInput(searchQuery.value);
2695
+ showDropdown.value = searchQuery.value.length >= 2;
2696
+ };
2697
+ const handlePaste = (event) => {
2698
+ var _a;
2699
+ const pastedText = ((_a = event.clipboardData) == null ? void 0 : _a.getData("text")) || "";
2700
+ if (pastedText.trim()) {
2701
+ searchQuery.value = pastedText.trim();
2702
+ handleInput();
2703
+ }
2704
+ };
2705
+ const handleKeyDown = (event) => {
2706
+ if (event.key === "Backspace" && !searchQuery.value && props.selectedUsers.length > 0) {
2462
2707
  if (activeTagIndex.value >= 0) {
2463
- const updatedUsers = [...selectedUsers.value];
2464
- updatedUsers.splice(activeTagIndex.value + 1, 0, user);
2465
- selectedUsers.value = updatedUsers;
2466
- activeTagIndex.value += 1;
2708
+ const userToRemove = props.selectedUsers[activeTagIndex.value];
2709
+ removeUser(userToRemove);
2710
+ if (activeTagIndex.value > 0) {
2711
+ activeTagIndex.value = activeTagIndex.value - 1;
2712
+ } else {
2713
+ activeTagIndex.value = -1;
2714
+ }
2467
2715
  } else {
2468
- selectedUsers.value = [...selectedUsers.value, user];
2469
- activeTagIndex.value = selectedUsers.value.length - 1;
2716
+ const lastUser = props.selectedUsers[props.selectedUsers.length - 1];
2717
+ removeUser(lastUser);
2470
2718
  }
2471
- emit("change", selectedUsers.value);
2719
+ }
2720
+ };
2721
+ const addUser = (user) => {
2722
+ if (!user || !user.id) return;
2723
+ if (!props.selectedUsers.some((item) => item.id === user.id)) {
2724
+ const updatedUsers = [...props.selectedUsers, user];
2725
+ emit("update:selectedUsers", updatedUsers);
2726
+ emit("add-user", user);
2472
2727
  }
2473
2728
  searchQuery.value = "";
2474
- userList.value = [];
2475
- setTimeout(() => {
2476
- var _a;
2477
- (_a = searchInput.value) == null ? void 0 : _a.focus();
2478
- }, 0);
2729
+ showDropdown.value = false;
2479
2730
  };
2480
2731
  const removeUser = (user) => {
2481
- selectedUsers.value = selectedUsers.value.filter((item) => item.id !== user.id);
2482
- emit("change", selectedUsers.value);
2483
- };
2484
- const calculateVisibleTags = async () => {
2485
- if (!props.multiple) {
2486
- return;
2487
- }
2488
- await nextTick();
2489
- if (selectedUsers.value.length === 0) {
2490
- visibleUsers.value = [];
2491
- hiddenCount.value = 0;
2492
- return;
2732
+ if (!user || !user.id) return;
2733
+ const updatedUsers = props.selectedUsers.filter((item) => item.id !== user.id);
2734
+ emit("update:selectedUsers", updatedUsers);
2735
+ emit("remove-user", user);
2736
+ if (activeTagIndex.value >= updatedUsers.length) {
2737
+ activeTagIndex.value = updatedUsers.length - 1;
2493
2738
  }
2494
- if (isFocused.value) {
2495
- visibleUsers.value = [...selectedUsers.value];
2496
- hiddenCount.value = 0;
2497
- return;
2739
+ if (!isFocused.value) {
2740
+ nextTick(() => {
2741
+ calculateVisibleUsers();
2742
+ });
2498
2743
  }
2499
- const container = tagInlineContainerRef.value;
2500
- const originalStyle = {
2501
- flexWrap: container.style.flexWrap,
2502
- height: container.style.height,
2503
- whiteSpace: container.style.whiteSpace,
2504
- overflow: container.style.overflow
2505
- };
2506
- container.style.flexWrap = "wrap";
2507
- container.style.height = "auto";
2508
- container.style.whiteSpace = "normal";
2509
- container.style.overflow = "visible";
2510
- const tagElements = container.querySelectorAll(".tag-item");
2511
- if (tagElements.length === 0) {
2512
- Object.assign(container.style, originalStyle);
2513
- visibleUsers.value = [];
2514
- hiddenCount.value = 0;
2515
- return;
2744
+ };
2745
+ watch(() => props.selectedUsers, () => {
2746
+ visibleUsers.value = [...props.selectedUsers];
2747
+ if (!isFocused.value) {
2748
+ nextTick(() => {
2749
+ calculateVisibleUsers();
2750
+ });
2516
2751
  }
2517
- const firstRowTop = tagElements[0].offsetTop;
2518
- const visibleIndexes = [];
2519
- for (let i = 0; i < tagElements.length; i++) {
2520
- if (tagElements[i].offsetTop === firstRowTop) {
2521
- visibleIndexes.push(i);
2522
- } else {
2523
- break;
2524
- }
2752
+ }, { deep: true });
2753
+ watch(isFocused, (newVal) => {
2754
+ if (newVal) {
2755
+ nextTick(() => {
2756
+ initSortable();
2757
+ });
2525
2758
  }
2526
- let visibleCount = visibleIndexes.length;
2527
- if (visibleCount < selectedUsers.value.length) {
2528
- const moreTagElement = document.createElement("span");
2529
- moreTagElement.className = "tag-item tag-more";
2530
- moreTagElement.textContent = `+${selectedUsers.value.length - visibleCount}`;
2531
- container.appendChild(moreTagElement);
2532
- if (moreTagElement.offsetTop > firstRowTop) {
2533
- visibleCount = Math.max(0, visibleCount - 1);
2534
- }
2535
- container.removeChild(moreTagElement);
2759
+ });
2760
+ const handleResize = () => {
2761
+ if (!isFocused.value) {
2762
+ calculateVisibleUsers();
2536
2763
  }
2537
- Object.assign(container.style, originalStyle);
2538
- visibleUsers.value = selectedUsers.value.slice(0, visibleCount);
2539
- hiddenCount.value = selectedUsers.value.length - visibleCount;
2540
2764
  };
2541
- watch(() => selectedUsers.value.length, () => {
2542
- calculateVisibleTags();
2543
- });
2544
2765
  onMounted(() => {
2545
- window.addEventListener("resize", calculateVisibleTags);
2546
- calculateVisibleTags();
2547
- updateInputWidth();
2548
2766
  initSortable();
2767
+ calculateVisibleUsers();
2768
+ window.addEventListener("resize", handleResize);
2769
+ nextTick(() => {
2770
+ calculateVisibleUsers();
2771
+ });
2549
2772
  });
2550
- const handleBlur = () => {
2551
- isFocused.value = false;
2552
- activeTagIndex.value = -1;
2553
- showDropdown.value = false;
2554
- searchQuery.value = "";
2555
- calculateVisibleTags();
2773
+ return (_ctx, _cache) => {
2774
+ return withDirectives((openBlock(), createElementBlock("div", {
2775
+ ref_key: "containerRef",
2776
+ ref: containerRef,
2777
+ class: "multiple-selector"
2778
+ }, [
2779
+ createCommentVNode(" 聚焦状态 - 可编辑模式 "),
2780
+ isFocused.value ? (openBlock(), createElementBlock(
2781
+ "div",
2782
+ {
2783
+ key: 0,
2784
+ ref_key: "tagsContainerRef",
2785
+ ref: tagsContainerRef,
2786
+ class: "tags-container focused",
2787
+ onClick: withModifiers(handleContainerClick, ["stop"])
2788
+ },
2789
+ [
2790
+ createCommentVNode(" 用户标签列表 "),
2791
+ createElementVNode(
2792
+ "div",
2793
+ {
2794
+ ref_key: "sortableContainerRef",
2795
+ ref: sortableContainerRef,
2796
+ class: "tag-list"
2797
+ },
2798
+ [
2799
+ (openBlock(true), createElementBlock(
2800
+ Fragment,
2801
+ null,
2802
+ renderList(__props.selectedUsers, (user, index2) => {
2803
+ return openBlock(), createElementBlock("div", {
2804
+ key: user.id,
2805
+ class: "tag-wrapper",
2806
+ onClick: withModifiers(($event) => handleTagClick(index2), ["stop"])
2807
+ }, [
2808
+ createVNode(UserTag, {
2809
+ user,
2810
+ tenants: unref(tenants),
2811
+ "current-tenant-id": __props.tenantId,
2812
+ draggable: __props.draggable,
2813
+ active: index2 === activeTagIndex.value,
2814
+ "show-tenant": true,
2815
+ onClick: ($event) => handleTagClick(index2),
2816
+ onClose: ($event) => removeUser(user)
2817
+ }, null, 8, ["user", "tenants", "current-tenant-id", "draggable", "active", "onClick", "onClose"]),
2818
+ createCommentVNode(" 在当前激活标签后插入输入框 "),
2819
+ index2 === activeTagIndex.value && activeTagIndex.value !== __props.selectedUsers.length - 1 ? withDirectives((openBlock(), createElementBlock(
2820
+ "input",
2821
+ {
2822
+ key: 0,
2823
+ ref_for: true,
2824
+ ref_key: "inlineInputRef",
2825
+ ref: inlineInputRef,
2826
+ class: "search-input inline",
2827
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => isRef(searchQuery) ? searchQuery.value = $event : null),
2828
+ onFocus: handleInputFocus,
2829
+ onInput: handleInput,
2830
+ onKeydown: handleKeyDown,
2831
+ onPaste: handlePaste
2832
+ },
2833
+ null,
2834
+ 544
2835
+ /* NEED_HYDRATION, NEED_PATCH */
2836
+ )), [
2837
+ [vModelText, unref(searchQuery)]
2838
+ ]) : createCommentVNode("v-if", true)
2839
+ ], 8, _hoisted_1$1);
2840
+ }),
2841
+ 128
2842
+ /* KEYED_FRAGMENT */
2843
+ )),
2844
+ createCommentVNode(" 最后一个输入框 "),
2845
+ activeTagIndex.value === -1 || activeTagIndex.value === __props.selectedUsers.length - 1 ? withDirectives((openBlock(), createElementBlock("input", {
2846
+ key: 0,
2847
+ ref_key: "lastInputRef",
2848
+ ref: lastInputRef,
2849
+ class: "search-input last",
2850
+ "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => isRef(searchQuery) ? searchQuery.value = $event : null),
2851
+ placeholder: !__props.selectedUsers.length ? __props.placeholder : "",
2852
+ onFocus: handleInputFocus,
2853
+ onInput: handleInput,
2854
+ onKeydown: handleKeyDown,
2855
+ onPaste: handlePaste
2856
+ }, null, 40, _hoisted_2)), [
2857
+ [vModelText, unref(searchQuery)]
2858
+ ]) : createCommentVNode("v-if", true)
2859
+ ],
2860
+ 512
2861
+ /* NEED_PATCH */
2862
+ )
2863
+ ],
2864
+ 512
2865
+ /* NEED_PATCH */
2866
+ )) : (openBlock(), createElementBlock(
2867
+ Fragment,
2868
+ { key: 1 },
2869
+ [
2870
+ createCommentVNode(" 未聚焦状态 - 只读展示模式 "),
2871
+ createElementVNode(
2872
+ "div",
2873
+ {
2874
+ ref_key: "collapsedContainerRef",
2875
+ ref: collapsedContainerRef,
2876
+ class: "tags-container collapsed",
2877
+ onClick: withModifiers(handleFocus, ["stop"])
2878
+ },
2879
+ [
2880
+ (openBlock(true), createElementBlock(
2881
+ Fragment,
2882
+ null,
2883
+ renderList(visibleUsers.value, (user) => {
2884
+ return openBlock(), createBlock(UserTag, {
2885
+ key: user.id,
2886
+ user,
2887
+ tenants: unref(tenants),
2888
+ "current-tenant-id": __props.tenantId,
2889
+ "show-tenant": true,
2890
+ onClick: handleFocus,
2891
+ onClose: ($event) => removeUser(user)
2892
+ }, null, 8, ["user", "tenants", "current-tenant-id", "onClose"]);
2893
+ }),
2894
+ 128
2895
+ /* KEYED_FRAGMENT */
2896
+ )),
2897
+ createCommentVNode(" 显示折叠标签数量 "),
2898
+ hiddenCount.value > 0 ? (openBlock(), createBlock(unref(Tag), {
2899
+ key: 0,
2900
+ class: "tag-more",
2901
+ onClick: withModifiers(handleFocus, ["stop"])
2902
+ }, {
2903
+ default: withCtx(() => [
2904
+ createTextVNode(
2905
+ " +" + toDisplayString(hiddenCount.value),
2906
+ 1
2907
+ /* TEXT */
2908
+ )
2909
+ ]),
2910
+ _: 1
2911
+ /* STABLE */
2912
+ })) : createCommentVNode("v-if", true),
2913
+ createCommentVNode(" 搜索输入框 "),
2914
+ withDirectives(createElementVNode("input", {
2915
+ ref_key: "collapsedInputRef",
2916
+ ref: collapsedInputRef,
2917
+ class: "search-input collapsed",
2918
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => isRef(searchQuery) ? searchQuery.value = $event : null),
2919
+ placeholder: !__props.selectedUsers.length ? __props.placeholder : "",
2920
+ onFocus: handleFocus
2921
+ }, null, 40, _hoisted_3), [
2922
+ [vModelText, unref(searchQuery)]
2923
+ ])
2924
+ ],
2925
+ 512
2926
+ /* NEED_PATCH */
2927
+ )
2928
+ ],
2929
+ 2112
2930
+ /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */
2931
+ )),
2932
+ createCommentVNode(" 下拉选项列表 "),
2933
+ createVNode(unref(Popover), {
2934
+ "ext-cls": "bk-user-selector-popover",
2935
+ arrow: false,
2936
+ disabled: !showDropdown.value,
2937
+ "is-show": showDropdown.value,
2938
+ width: containerRef.value ? containerRef.value.offsetWidth : "auto",
2939
+ boundary: "document.body",
2940
+ placement: "bottom-start",
2941
+ theme: "light",
2942
+ trigger: "manual"
2943
+ }, {
2944
+ content: withCtx(() => [
2945
+ createVNode(unref(Loading), {
2946
+ class: "dropdown-content",
2947
+ loading: unref(searchLoading),
2948
+ size: "small",
2949
+ mode: "spin"
2950
+ }, {
2951
+ default: withCtx(() => [
2952
+ unref(searchResults).length === 0 ? (openBlock(), createElementBlock("div", _hoisted_4, [
2953
+ createElementVNode(
2954
+ "span",
2955
+ null,
2956
+ toDisplayString(unref(searchQuery).length > 1 ? "未找到匹配用户" : "请输入关键词搜索"),
2957
+ 1
2958
+ /* TEXT */
2959
+ )
2960
+ ])) : (openBlock(true), createElementBlock(
2961
+ Fragment,
2962
+ { key: 1 },
2963
+ renderList(unref(searchResults), (user) => {
2964
+ return openBlock(), createElementBlock("div", {
2965
+ class: "user-option",
2966
+ key: user.id,
2967
+ onMousedown: withModifiers(($event) => addUser(user), ["prevent"])
2968
+ }, [
2969
+ createElementVNode(
2970
+ "span",
2971
+ _hoisted_6,
2972
+ toDisplayString(user.name),
2973
+ 1
2974
+ /* TEXT */
2975
+ ),
2976
+ user.tenantId !== __props.tenantId && user.tenantId && unref(tenants)[user.tenantId] ? (openBlock(), createElementBlock(
2977
+ "span",
2978
+ _hoisted_7,
2979
+ "@" + toDisplayString(unref(tenants)[user.tenantId]),
2980
+ 1
2981
+ /* TEXT */
2982
+ )) : createCommentVNode("v-if", true)
2983
+ ], 40, _hoisted_5);
2984
+ }),
2985
+ 128
2986
+ /* KEYED_FRAGMENT */
2987
+ ))
2988
+ ]),
2989
+ _: 1
2990
+ /* STABLE */
2991
+ }, 8, ["loading"])
2992
+ ]),
2993
+ _: 1
2994
+ /* STABLE */
2995
+ }, 8, ["disabled", "is-show", "width"])
2996
+ ])), [
2997
+ [unref(clickoutside), handleClickOutside]
2998
+ ]);
2556
2999
  };
2557
- const dragOptions = computed(() => ({
2558
- animation: 150,
2559
- ghostClass: "ghost-tag",
2560
- chosenClass: "chosen-tag",
2561
- dragClass: "drag-tag",
2562
- handle: ".tag-item",
2563
- // 根据draggable属性决定是否启用排序
2564
- sort: props.draggable
2565
- }));
2566
- const initSortable = () => {
2567
- nextTick(() => {
2568
- const sortableContainerEl = document.querySelector(".tag-group");
2569
- console.log(sortableContainerEl, "sortableContainerEl");
2570
- if (sortableContainerEl && props.draggable) {
2571
- if (sortableInstance.value) {
2572
- sortableInstance.value.destroy();
3000
+ }
3001
+ });
3002
+ const MultipleSelector = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-6a1779a9"]]);
3003
+ const _hoisted_1 = { class: "bk-user-selector" };
3004
+ const _sfc_main = /* @__PURE__ */ defineComponent({
3005
+ ...{
3006
+ name: "BkUserSelector"
3007
+ },
3008
+ __name: "user-selector",
3009
+ props: {
3010
+ label: { default: "人员选择" },
3011
+ required: { type: Boolean, default: false },
3012
+ placeholder: { default: "请输入人员名称搜索" },
3013
+ modelValue: { default: "" },
3014
+ draggable: { type: Boolean, default: false },
3015
+ multiple: { type: Boolean, default: false },
3016
+ apiBaseUrl: { default: "" },
3017
+ tenantId: { default: "" }
3018
+ },
3019
+ emits: ["update:modelValue", "change"],
3020
+ setup(__props, { emit: __emit }) {
3021
+ const props = __props;
3022
+ const emit = __emit;
3023
+ const { tenants } = useTenantData(props.apiBaseUrl, props.tenantId);
3024
+ const selectedUsers = ref([]);
3025
+ const selectedUser = ref(props.multiple ? "" : props.modelValue);
3026
+ const selectedUserIds = computed(() => {
3027
+ return props.multiple ? selectedUsers.value.map((user) => user.id) : [];
3028
+ });
3029
+ const initSelectedUsers = async () => {
3030
+ if (props.multiple) {
3031
+ const ids = Array.isArray(props.modelValue) ? props.modelValue : [];
3032
+ if (ids.length > 0) {
3033
+ try {
3034
+ const result = await lookupUsers(props.apiBaseUrl, props.tenantId, ids);
3035
+ selectedUsers.value = formatUsers(result);
3036
+ } catch (error) {
3037
+ console.error("获取选中用户信息失败:", error);
2573
3038
  }
2574
- sortableInstance.value = Sortable.create(sortableContainerEl, {
2575
- ...dragOptions.value,
2576
- onEnd: (evt) => {
2577
- const newOrder = [...selectedUsers.value];
2578
- const { oldIndex: oldIndex2, newIndex: newIndex2 } = evt;
2579
- if (oldIndex2 !== newIndex2) {
2580
- const movedItem = newOrder.splice(oldIndex2, 1)[0];
2581
- newOrder.splice(newIndex2, 0, movedItem);
2582
- selectedUsers.value = newOrder;
2583
- handleDragChange();
2584
- }
2585
- }
2586
- });
2587
3039
  }
2588
- });
3040
+ } else {
3041
+ selectedUser.value = props.modelValue;
3042
+ }
2589
3043
  };
2590
- watch([
2591
- () => selectedUsers.value,
2592
- () => props.draggable,
2593
- () => isFocused.value
2594
- ], () => {
2595
- initSortable();
2596
- }, { deep: true });
2597
- const handleDragChange = () => {
2598
- emit("change", selectedUsers.value);
3044
+ const handleUpdateUser = (user) => {
3045
+ emit("change", user);
2599
3046
  };
2600
- watch(searchQuery, () => {
2601
- updateInputWidth();
2602
- });
2603
- const handleUpdateSelected = (user) => {
2604
- selectedUsers.value = [user];
3047
+ const handleUpdateSelectedUsers = (users) => {
3048
+ selectedUsers.value = users;
3049
+ emit("update:modelValue", users.map((user) => user.id));
3050
+ emit("change", users);
2605
3051
  };
3052
+ watch(selectedUser, (newVal) => {
3053
+ if (!props.multiple) {
3054
+ emit("update:modelValue", newVal);
3055
+ }
3056
+ });
3057
+ onBeforeMount(() => {
3058
+ initSelectedUsers();
3059
+ });
2606
3060
  return (_ctx, _cache) => {
2607
- return withDirectives((openBlock(), createElementBlock("section", null, [
3061
+ return openBlock(), createElementBlock("section", _hoisted_1, [
2608
3062
  createCommentVNode(" 单选模式 "),
2609
- !__props.multiple ? (openBlock(), createBlock(_sfc_main$1, {
3063
+ !_ctx.multiple ? (openBlock(), createBlock(SingleSelector, {
2610
3064
  key: 0,
2611
- modelValue: selectedUsers.value,
2612
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => selectedUsers.value = $event),
2613
- placeholder: __props.placeholder,
2614
- "api-base-url": __props.apiBaseUrl,
2615
- "tenant-id": __props.tenantId,
2616
- "onUpdate:selected": handleUpdateSelected
2617
- }, null, 8, ["modelValue", "placeholder", "api-base-url", "tenant-id"])) : (openBlock(), createElementBlock(
3065
+ modelValue: selectedUser.value,
3066
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => selectedUser.value = $event),
3067
+ "api-base-url": _ctx.apiBaseUrl,
3068
+ placeholder: _ctx.placeholder,
3069
+ "tenant-id": _ctx.tenantId,
3070
+ tenants: unref(tenants),
3071
+ onChange: handleUpdateUser
3072
+ }, null, 8, ["modelValue", "api-base-url", "placeholder", "tenant-id", "tenants"])) : (openBlock(), createElementBlock(
2618
3073
  Fragment,
2619
3074
  { key: 1 },
2620
3075
  [
2621
- createCommentVNode(" 使用 popover 包裹内容 "),
2622
- createVNode(unref(Popover), {
2623
- placement: "bottom-start",
2624
- theme: "light",
2625
- trigger: "manual",
2626
- arrow: false,
2627
- "is-show": showDropdown.value,
2628
- width: containerRef.value ? containerRef.value.offsetWidth : "auto",
2629
- disabled: !showDropdown.value
2630
- }, {
2631
- content: withCtx(() => [
2632
- withDirectives((openBlock(), createElementBlock("div", _hoisted_1, [
2633
- userList.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_2, _hoisted_4)) : (openBlock(true), createElementBlock(
2634
- Fragment,
2635
- { key: 1 },
2636
- renderList(userList.value, (item) => {
2637
- return openBlock(), createElementBlock("div", {
2638
- key: item.id,
2639
- class: "user-option",
2640
- onMousedown: withModifiers(($event) => addUser(item), ["prevent"])
2641
- }, [
2642
- item.avatar ? (openBlock(), createElementBlock("img", {
2643
- key: 0,
2644
- class: "user-avatar",
2645
- src: item.avatar,
2646
- alt: "用户头像"
2647
- }, null, 8, _hoisted_6)) : (openBlock(), createElementBlock(
2648
- "span",
2649
- _hoisted_7,
2650
- toDisplayString(getAvatarText(item.name)),
2651
- 1
2652
- /* TEXT */
2653
- )),
2654
- createElementVNode(
2655
- "span",
2656
- _hoisted_8,
2657
- toDisplayString(item.name),
2658
- 1
2659
- /* TEXT */
2660
- )
2661
- ], 40, _hoisted_5);
2662
- }),
2663
- 128
2664
- /* KEYED_FRAGMENT */
2665
- ))
2666
- ])), [
2667
- [unref(vLoading), { mode: "spin", size: "small", loading: loading.value }]
2668
- ])
2669
- ]),
2670
- default: withCtx(() => [
2671
- createElementVNode(
2672
- "div",
2673
- {
2674
- ref_key: "containerRef",
2675
- ref: containerRef,
2676
- class: normalizeClass(["tag-input-container", { "is-focused": isFocused.value, "is-single": !__props.multiple }])
2677
- },
2678
- [
2679
- createCommentVNode(" 多选模式 "),
2680
- createCommentVNode(" 聚焦状态 - 使用单一draggable "),
2681
- isFocused.value ? (openBlock(), createElementBlock(
2682
- Fragment,
2683
- { key: 0 },
2684
- [
2685
- createCommentVNode(" 多选模式 "),
2686
- createElementVNode(
2687
- "div",
2688
- _hoisted_9,
2689
- [
2690
- (openBlock(true), createElementBlock(
2691
- Fragment,
2692
- null,
2693
- renderList(selectedUsers.value, (element, index2) => {
2694
- return openBlock(), createElementBlock("div", {
2695
- key: element.id,
2696
- class: "tag-wrapper",
2697
- onClick: withModifiers(($event) => handleTagClick(index2), ["stop"])
2698
- }, [
2699
- createVNode(unref(Tag), {
2700
- class: normalizeClass(["tag-item", { "draggable": __props.draggable, "active": index2 === activeTagIndex.value }]),
2701
- size: "small",
2702
- closable: "",
2703
- onClose: ($event) => removeUser(element),
2704
- onClick: withModifiers(($event) => handleTagClick(index2), ["stop"])
2705
- }, {
2706
- default: withCtx(() => [
2707
- createElementVNode("div", _hoisted_11, [
2708
- createElementVNode(
2709
- "span",
2710
- _hoisted_12,
2711
- toDisplayString(element.name),
2712
- 1
2713
- /* TEXT */
2714
- )
2715
- ])
2716
- ]),
2717
- _: 2
2718
- /* DYNAMIC */
2719
- }, 1032, ["class", "onClose", "onClick"]),
2720
- createCommentVNode(" 在当前激活的标签后插入输入框 "),
2721
- createElementVNode(
2722
- "span",
2723
- {
2724
- ref_for: true,
2725
- ref_key: "measureEl",
2726
- ref: measureEl,
2727
- class: "measure-text",
2728
- "aria-hidden": "true"
2729
- },
2730
- null,
2731
- 512
2732
- /* NEED_PATCH */
2733
- ),
2734
- index2 === activeTagIndex.value && activeTagIndex.value !== selectedUsers.value.length - 1 ? withDirectives((openBlock(), createElementBlock("input", {
2735
- key: 0,
2736
- ref_for: true,
2737
- ref_key: "searchInput",
2738
- ref: searchInput,
2739
- "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => searchQuery.value = $event),
2740
- placeholder: selectedUsers.value.length ? "" : __props.placeholder,
2741
- class: "tag-input inline-input",
2742
- onInput: handleInput,
2743
- onFocus: handleFocus
2744
- }, null, 40, _hoisted_13)), [
2745
- [vModelText, searchQuery.value]
2746
- ]) : createCommentVNode("v-if", true)
2747
- ], 8, _hoisted_10);
2748
- }),
2749
- 128
2750
- /* KEYED_FRAGMENT */
2751
- ))
2752
- ],
2753
- 512
2754
- /* NEED_PATCH */
2755
- ),
2756
- createCommentVNode(" 如果激活索引是最后一个或没有激活的标签,将输入框放在最后 "),
2757
- activeTagIndex.value === -1 || activeTagIndex.value === selectedUsers.value.length - 1 ? withDirectives((openBlock(), createElementBlock("input", {
2758
- key: 0,
2759
- ref_key: "searchInput",
2760
- ref: searchInput,
2761
- "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => searchQuery.value = $event),
2762
- placeholder: selectedUsers.value.length ? "" : __props.placeholder,
2763
- class: "tag-input single-input",
2764
- onInput: handleInput,
2765
- onFocus: handleFocus
2766
- }, null, 40, _hoisted_14)), [
2767
- [vModelText, searchQuery.value]
2768
- ]) : createCommentVNode("v-if", true)
2769
- ],
2770
- 64
2771
- /* STABLE_FRAGMENT */
2772
- )) : (openBlock(), createElementBlock(
2773
- Fragment,
2774
- { key: 1 },
2775
- [
2776
- createCommentVNode(" 失焦状态 "),
2777
- createCommentVNode(" 多选模式 "),
2778
- createElementVNode(
2779
- "div",
2780
- {
2781
- ref_key: "tagInlineContainerRef",
2782
- ref: tagInlineContainerRef,
2783
- class: "tag-inline-container",
2784
- onClick: withModifiers(handleFocus, ["stop"])
2785
- },
2786
- [
2787
- (openBlock(true), createElementBlock(
2788
- Fragment,
2789
- null,
2790
- renderList(visibleUsers.value, (user) => {
2791
- return openBlock(), createBlock(unref(Tag), {
2792
- key: user.id,
2793
- class: "tag-item",
2794
- closable: "",
2795
- onClose: ($event) => removeUser(user),
2796
- onClick: withModifiers(handleFocus, ["stop"])
2797
- }, {
2798
- default: withCtx(() => [
2799
- createElementVNode("div", _hoisted_15, [
2800
- createElementVNode(
2801
- "span",
2802
- _hoisted_16,
2803
- toDisplayString(user.name),
2804
- 1
2805
- /* TEXT */
2806
- )
2807
- ])
2808
- ]),
2809
- _: 2
2810
- /* DYNAMIC */
2811
- }, 1032, ["onClose"]);
2812
- }),
2813
- 128
2814
- /* KEYED_FRAGMENT */
2815
- )),
2816
- createCommentVNode(" 显示折叠标签数量 "),
2817
- hiddenCount.value > 0 ? (openBlock(), createBlock(unref(Tag), {
2818
- key: 0,
2819
- class: "tag-item tag-more",
2820
- onClick: withModifiers(handleFocus, ["stop"])
2821
- }, {
2822
- default: withCtx(() => [
2823
- createTextVNode(
2824
- " +" + toDisplayString(hiddenCount.value),
2825
- 1
2826
- /* TEXT */
2827
- )
2828
- ]),
2829
- _: 1
2830
- /* STABLE */
2831
- })) : createCommentVNode("v-if", true),
2832
- createCommentVNode(" 修改这里的输入框样式 "),
2833
- withDirectives(createElementVNode("input", {
2834
- ref_key: "searchInput",
2835
- ref: searchInput,
2836
- "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => searchQuery.value = $event),
2837
- placeholder: selectedUsers.value.length ? "" : __props.placeholder,
2838
- class: normalizeClass(["tag-input full-width", { "placeholder-style": !selectedUsers.value.length }]),
2839
- onFocus: handleFocus
2840
- }, null, 42, _hoisted_17), [
2841
- [vModelText, searchQuery.value]
2842
- ])
2843
- ],
2844
- 512
2845
- /* NEED_PATCH */
2846
- )
2847
- ],
2848
- 64
2849
- /* STABLE_FRAGMENT */
2850
- ))
2851
- ],
2852
- 2
2853
- /* CLASS */
2854
- )
2855
- ]),
2856
- _: 1
2857
- /* STABLE */
2858
- }, 8, ["is-show", "width", "disabled"])
3076
+ createCommentVNode(" 多选模式 "),
3077
+ createVNode(MultipleSelector, {
3078
+ modelValue: selectedUserIds.value,
3079
+ "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => selectedUserIds.value = $event),
3080
+ "api-base-url": _ctx.apiBaseUrl,
3081
+ placeholder: _ctx.placeholder,
3082
+ "tenant-id": _ctx.tenantId,
3083
+ draggable: _ctx.draggable,
3084
+ "selected-users": selectedUsers.value,
3085
+ "onUpdate:selectedUsers": handleUpdateSelectedUsers
3086
+ }, null, 8, ["modelValue", "api-base-url", "placeholder", "tenant-id", "draggable", "selected-users"])
2859
3087
  ],
2860
3088
  2112
2861
3089
  /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */
2862
3090
  ))
2863
- ])), [
2864
- [unref(vClickoutside), handleBlur]
2865
3091
  ]);
2866
3092
  };
2867
3093
  }
2868
- };
2869
- const BkUserSelector = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-5e8a952d"]]);
3094
+ });
3095
+ const BkUserSelector = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-144c2518"]]);
2870
3096
  export {
2871
3097
  BkUserSelector,
2872
3098
  BkUserSelector as default