gaoluyang
2026-06-29 27cd042df9aca0383a49f3514bc21958dd890912
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
<script setup lang="ts">
import type { AiChatMessageApi } from '#/api/ai/chat/message';
 
import { ref } from 'vue';
 
import { useVbenDrawer } from '../../../../../../packages/effects/common-ui/src';
import { IconifyIcon } from '../../../../../../packages/icons/src';
 
defineProps<{
  webSearchPages?: AiChatMessageApi.WebSearchPage[];
}>();
 
const isExpanded = ref(false); // 默认收起
const selectedResult = ref<AiChatMessageApi.WebSearchPage | null>(null); // 选中的搜索结果
const iconLoadError = ref<Record<number, boolean>>({}); // 记录图标加载失败
 
const [Drawer, drawerApi] = useVbenDrawer({
  title: '联网搜索详情',
  closable: true,
  footer: true,
  onCancel() {
    drawerApi.close();
  },
  onConfirm() {
    if (selectedResult.value?.url) {
      window.open(selectedResult.value.url, '_blank');
    }
    drawerApi.close();
  },
});
 
/** 切换展开/收起 */
function toggleExpanded() {
  isExpanded.value = !isExpanded.value;
}
 
/** 点击搜索结果 */
function handleClick(result: AiChatMessageApi.WebSearchPage) {
  selectedResult.value = result;
  drawerApi.open();
}
 
/** 图标加载失败处理 */
function handleIconError(index: number) {
  iconLoadError.value[index] = true;
}
</script>
 
<template>
  <div v-if="webSearchPages && webSearchPages.length > 0" class="mt-2.5">
    <!-- 标题栏:可点击展开/收起 -->
    <div
      class="mb-2 flex cursor-pointer items-center justify-between text-sm text-gray-600 transition-colors hover:text-blue-500"
      @click="toggleExpanded"
    >
      <div class="flex items-center gap-1.5">
        <IconifyIcon icon="lucide:search" :size="14" />
        <span>联网搜索结果 ({{ webSearchPages.length }} 条)</span>
      </div>
      <IconifyIcon
        :icon="isExpanded ? 'lucide:chevron-up' : 'lucide:chevron-down'"
        class="text-xs transition-transform duration-200"
        :size="12"
      />
    </div>
 
    <!-- 可展开的搜索结果列表 -->
    <div
      v-show="isExpanded"
      class="flex flex-col gap-2 transition-all duration-200 ease-in-out"
    >
      <div
        v-for="(page, index) in webSearchPages"
        :key="index"
        class="cursor-pointer rounded-md bg-white p-2.5 transition-all hover:bg-blue-50"
        @click="handleClick(page)"
      >
        <div class="flex items-start gap-2">
          <!-- 网站图标 -->
          <div class="mt-0.5 h-4 w-4 flex-shrink-0">
            <img
              v-if="page.icon && !iconLoadError[index]"
              :src="page.icon"
              :alt="page.name"
              class="h-full w-full rounded-sm object-contain"
              @error="handleIconError(index)"
            />
            <IconifyIcon
              v-else
              icon="lucide:link"
              class="h-full w-full text-gray-600"
            />
          </div>
          <!-- 内容区域 -->
          <div class="min-w-0 flex-1">
            <!-- 网站名称 -->
            <div class="mb-1 truncate text-xs text-gray-400">
              {{ page.name }}
            </div>
            <!-- 主标题 -->
            <div
              class="mb-1 line-clamp-2 text-sm font-medium leading-snug text-blue-600"
            >
              {{ page.title }}
            </div>
            <!-- 描述 -->
            <div class="mb-1 line-clamp-2 text-xs leading-snug text-gray-600">
              {{ page.snippet }}
            </div>
            <!-- URL -->
            <div class="truncate text-xs text-green-700">
              {{ page.url }}
            </div>
          </div>
        </div>
      </div>
    </div>
 
    <!-- 联网搜索详情 Drawer -->
    <Drawer class="w-[600px]" cancel-text="关闭" confirm-text="访问原文">
      <div v-if="selectedResult">
        <!-- 标题区域 -->
        <div class="mb-4 flex items-start gap-3">
          <div class="mt-0.5 h-6 w-6 flex-shrink-0">
            <img
              v-if="selectedResult.icon"
              :src="selectedResult.icon"
              :alt="selectedResult.name"
              class="h-full w-full rounded-sm object-contain"
            />
            <IconifyIcon
              v-else
              icon="lucide:link"
              class="h-full w-full text-gray-600"
            />
          </div>
          <div class="min-w-0 flex-1">
            <div class="mb-2 text-lg font-bold text-gray-900">
              {{ selectedResult.title }}
            </div>
            <div class="mb-1 text-sm text-gray-500">
              {{ selectedResult.name }}
            </div>
            <div class="break-all text-sm text-green-700">
              {{ selectedResult.url }}
            </div>
          </div>
        </div>
        <!-- 内容区域 -->
        <div class="space-y-4">
          <!-- 简短描述 -->
          <div>
            <div class="mb-2 text-sm font-semibold text-gray-900">简短描述</div>
            <div
              class="rounded-lg bg-gray-50 p-3 text-sm leading-relaxed text-gray-700"
            >
              {{ selectedResult.snippet }}
            </div>
          </div>
          <!-- 内容摘要 -->
          <div v-if="selectedResult.summary">
            <div class="mb-2 text-sm font-semibold text-gray-900">内容摘要</div>
            <div
              class="max-h-[50vh] overflow-y-auto whitespace-pre-wrap rounded-lg bg-gray-50 p-3 text-sm leading-relaxed text-gray-900"
            >
              {{ selectedResult.summary }}
            </div>
          </div>
        </div>
      </div>
    </Drawer>
  </div>
</template>