1
0

SessionPopup.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. <template>
  2. <!-- 会话引用选择弹层:由父组件用 # 触发,多选上限 5;已选状态与高亮项都由父组件传入 -->
  3. <div ref="rootEl" class="session-popup" @click.stop @pointerdown.stop>
  4. <div class="session-popup-header">
  5. <span>选择会话</span>
  6. <span class="session-count">{{ selectedCount }}/5</span>
  7. </div>
  8. <div class="session-list">
  9. <div
  10. v-for="(task, index) in list"
  11. :key="task.id"
  12. class="session-item"
  13. :class="{
  14. active: index === activeIndex,
  15. selected: isSelected(task.id),
  16. disabled: (selectedCount >= 5 && !isSelected(task.id)) || task.id === currentTaskId,
  17. }"
  18. @click="emit('select', task)"
  19. >
  20. <div class="session-checkbox" :class="{ checked: isSelected(task.id) }">
  21. <span v-if="isSelected(task.id)" class="check-mark">✓</span>
  22. </div>
  23. <div class="session-item-icon"></div>
  24. <span class="session-item-name">{{ task.name }}</span>
  25. </div>
  26. <div v-if="list.length === 0" class="session-empty">暂无匹配会话</div>
  27. </div>
  28. <div class="session-popup-footer">
  29. <span v-if="selectedCount >= 5" class="max-hint">最多选择5个会话</span>
  30. <span v-else>点击选择会话(最多5个)</span>
  31. </div>
  32. </div>
  33. </template>
  34. <script setup lang="ts">
  35. import { computed, ref } from 'vue';
  36. import { onClickOutside } from '@vueuse/core';
  37. interface SessionOption {
  38. id: string;
  39. name: string;
  40. }
  41. const props = defineProps<{
  42. list: SessionOption[];
  43. activeIndex: number;
  44. selectedIds: string[];
  45. currentTaskId: string;
  46. }>();
  47. const emit = defineEmits<{ (e: 'select', task: SessionOption): void; (e: 'close'): void }>();
  48. const selectedCount = computed(() => props.selectedIds.length);
  49. const isSelected = (id: string) => props.selectedIds.includes(id);
  50. // 组件自己持有根元素,外部点击由这里判定后回调父组件
  51. const rootEl = ref<HTMLElement>();
  52. onClickOutside(rootEl, () => emit('close'));
  53. </script>
  54. <style scoped lang="less">
  55. .session-popup {
  56. position: absolute;
  57. bottom: 100%;
  58. left: 0;
  59. right: 0;
  60. margin-bottom: 8px;
  61. background: #0a2a3f;
  62. border: 1px solid #1a4a6f;
  63. border-radius: 8px;
  64. max-height: 300px;
  65. display: flex;
  66. flex-direction: column;
  67. z-index: 1000;
  68. box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
  69. .session-popup-header {
  70. display: flex;
  71. justify-content: space-between;
  72. align-items: center;
  73. padding: 10px 14px 8px;
  74. font-size: 13px;
  75. color: #e0e6ed;
  76. border-bottom: 1px solid rgba(63, 80, 106, 0.3);
  77. flex-shrink: 0;
  78. .session-count {
  79. font-size: 12px;
  80. color: #3a8fd4;
  81. font-weight: 500;
  82. }
  83. }
  84. .session-list {
  85. flex: 1;
  86. overflow-y: auto;
  87. padding: 6px;
  88. .session-item {
  89. display: flex;
  90. align-items: center;
  91. gap: 10px;
  92. padding: 8px 10px;
  93. cursor: pointer;
  94. border-radius: 6px;
  95. transition: background 0.2s;
  96. &:hover {
  97. background: rgba(10, 132, 255, 0.15);
  98. }
  99. &.active {
  100. background: rgba(10, 132, 255, 0.2);
  101. }
  102. &.selected {
  103. background: rgba(10, 132, 255, 0.12);
  104. }
  105. &.disabled {
  106. opacity: 0.4;
  107. cursor: not-allowed;
  108. &:hover {
  109. background: transparent;
  110. }
  111. }
  112. .session-checkbox {
  113. width: 16px;
  114. height: 16px;
  115. border: 1.5px solid rgba(255, 255, 255, 0.25);
  116. border-radius: 3px;
  117. flex-shrink: 0;
  118. display: flex;
  119. align-items: center;
  120. justify-content: center;
  121. transition: all 0.2s;
  122. &.checked {
  123. background: #3a8fd4;
  124. border-color: #3a8fd4;
  125. }
  126. .check-mark {
  127. font-size: 11px;
  128. color: #fff;
  129. line-height: 1;
  130. }
  131. }
  132. .session-item-icon {
  133. width: 18px;
  134. height: 18px;
  135. background-image: var(--img-chat-popup-session-icon);
  136. background-repeat: no-repeat;
  137. background-size: 100% 100%;
  138. flex-shrink: 0;
  139. }
  140. .session-item-name {
  141. font-size: 13px;
  142. color: #e0e6ed;
  143. overflow: hidden;
  144. text-overflow: ellipsis;
  145. white-space: nowrap;
  146. }
  147. }
  148. .session-empty {
  149. text-align: center;
  150. color: #8b949e;
  151. font-size: 13px;
  152. padding: 20px 0;
  153. }
  154. }
  155. .session-popup-footer {
  156. padding: 8px 14px;
  157. font-size: 12px;
  158. color: #8b949e;
  159. text-align: center;
  160. border-top: 1px solid rgba(63, 80, 106, 0.3);
  161. flex-shrink: 0;
  162. .max-hint {
  163. color: #faad14;
  164. }
  165. }
  166. }
  167. </style>