711 lines
20 KiB
Vue
711 lines
20 KiB
Vue
<template>
|
||
<div
|
||
style="display: flex;align-items: center;justify-content: center;width: 100%;max-width: 1400px;">
|
||
|
||
<!-- <img @click="goHome" class="logo-clickable" style="width: 200px;height: 60px;padding-right: 20px;"
|
||
src="https://www.opencomputing.cn/idfile?path=logo_ncmatch.png" alt=""> -->
|
||
<div
|
||
style="min-width:800px;display: flex;align-items: center;justify-content: space-between;">
|
||
|
||
<!-- 中间搜索区域 -->
|
||
<div class="search-section" style="position: relative;margin: 0 20px;">
|
||
<div class="search-bar" :class="{ 'has-results': showSearchResults && searchResults.length > 0 }">
|
||
<el-select class="mySelect" v-model="publish_type" placeholder="请选择" @change="handleTypeChange"
|
||
style="width: 75px;">
|
||
<el-option label="商品" value="1">
|
||
</el-option>
|
||
<el-option label="需求" value="2"></el-option>
|
||
</el-select>
|
||
<input v-model="keyword" type="text" class="search-input" placeholder="搜你想搜..." @input="handleInputChange"
|
||
@focus="handleInputFocus" @blur="handleInputBlur">
|
||
|
||
<button class="search-btn" @click="handleSearch">搜索</button>
|
||
</div>
|
||
<!-- 实时搜索结果 -->
|
||
<div v-if="showSearchResults && searchResults.length > 0" class="search-results" @click.stop>
|
||
<div v-for="result in searchResults" :key="result.id" class="search-result-item"
|
||
@click.stop.prevent="handleSearch(result)"
|
||
@mousedown.stop.prevent
|
||
@mouseup.stop.prevent
|
||
style="cursor: pointer; padding: 10px">
|
||
<span class="result-title" v-html="highlightKeyword(result)"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 热搜关键词 -->
|
||
<div v-if="false" class="hot-search">
|
||
|
||
<a v-for="keyword in hotSearchKeywords" :key="keyword" href="#" class="hot-keyword"
|
||
@click="handleHotKeywordClick(keyword)">
|
||
{{ keyword }}
|
||
</a>
|
||
</div>
|
||
|
||
</div>
|
||
<!-- position: absolute;right: -150px;top: 0px; -->
|
||
<span
|
||
v-if="false"
|
||
style="height: 44px;line-height: 44px;border-radius: 8px;border:1px solid #275AFF; color: #275AFF;font-size: 18px;font-weight: 500;padding: 0 20px;z-index: 10;display: flex;align-items: center;gap: 10px;">
|
||
<img style="width: 24px;height: 24px;" src="../mainPage/img/robot.svg" alt="">
|
||
NC AI</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import eventBus from '@/utils/eventBus'
|
||
import { reqSearch } from '@/api/ncmatch'
|
||
export default {
|
||
name: 'search',
|
||
data() {
|
||
return {
|
||
keyword: '',
|
||
publish_type: '1',
|
||
showSearchResults: false,
|
||
searchResults: [],
|
||
searchTimeout: null,
|
||
// 移除搜索历史记录
|
||
// 移除当前搜索状态
|
||
hotSearchKeywords: [
|
||
'昆仑芯-P800',
|
||
'天垓-150',
|
||
'4090',
|
||
'云服务器-GPU',
|
||
'人脸识别',
|
||
'SDWAN',
|
||
'互联网专线',
|
||
'DCI',
|
||
'AI专线',
|
||
'对象存储',
|
||
'智慧医疗',
|
||
'智慧客服',
|
||
'A800',
|
||
'A100',
|
||
'910B'
|
||
],
|
||
hotMenuList: [
|
||
{
|
||
id: "hot",
|
||
name: "热门推荐",
|
||
icon: require("../../newImg/niu.png"),
|
||
activeIcon: require("../../newImg/niuActive.png")
|
||
},
|
||
{
|
||
id: "hlzx",
|
||
name: "互联网专线",
|
||
icon: require("../../newImg/niu.png"),
|
||
activeIcon: require("../../newImg/niuActive.png"),
|
||
},
|
||
{
|
||
id: "SDWAN",
|
||
name: "SDWAN",
|
||
icon: require("../../newImg/niu.png"),
|
||
activeIcon: require("../../newImg/niuActive.png"),
|
||
},
|
||
{
|
||
id: "DCI",
|
||
name: "DCI",
|
||
icon: require("../../newImg/niu.png"),
|
||
activeIcon: require("../../newImg/niuActive.png"),
|
||
},
|
||
{
|
||
id: "AI",
|
||
name: "AI专线",
|
||
icon: require("../../newImg/niu.png"),
|
||
activeIcon: require("../../newImg/niuActive.png"),
|
||
}
|
||
],
|
||
}
|
||
},
|
||
// 移除计算属性
|
||
// 移除watch监听器
|
||
methods: {
|
||
goHome(event) {
|
||
|
||
|
||
if (this.$route.path != '/ncmatchHome/index') {
|
||
console.log('准备跳转到:', '/ncmatchHome/index');
|
||
this.$router.push('/ncmatchHome/index').then(() => {
|
||
console.log('跳转成功');
|
||
}).catch(err => {
|
||
console.error('跳转失败:', err);
|
||
});
|
||
} else {
|
||
console.log('已经在首页,无需跳转');
|
||
}
|
||
},
|
||
goSearch() {
|
||
// 通过路由传参控制搜索行为
|
||
const currentRoute = this.$route;
|
||
const currentDisplayPage = currentRoute.query.display_page;
|
||
const targetQuery = {
|
||
keyword: this.keyword,
|
||
publish_type: this.publish_type,
|
||
display_page: 'list', // 正式搜索使用 list
|
||
current_page: 1,
|
||
page_size: 8
|
||
};
|
||
|
||
console.log('准备跳转到搜索结果页面:', { currentRoute, targetQuery });
|
||
|
||
// 通过 display_page 参数判断是否在搜索结果页面
|
||
if (currentDisplayPage !== 'list') {
|
||
// 不在搜索结果页面,需要跳转
|
||
console.log('执行路由跳转');
|
||
this.$router.push({
|
||
path: '/ncmatchHome/search',
|
||
query: targetQuery
|
||
}).catch(err => {
|
||
// 忽略重复导航错误
|
||
if (err.name !== 'NavigationDuplicated') {
|
||
console.error('路由导航错误:', err);
|
||
} else {
|
||
console.log('路由重复导航,忽略错误');
|
||
}
|
||
});
|
||
} else {
|
||
// 如果已经在搜索结果页面,重新调用接口获取最新数据
|
||
console.log('已在搜索结果页面,重新调用接口获取最新数据');
|
||
this.performFormalSearch(this.keyword, this.publish_type);
|
||
}
|
||
},
|
||
// 处理输入变化
|
||
handleInputChange() {
|
||
// 清除之前的定时器
|
||
if (this.searchTimeout) {
|
||
clearTimeout(this.searchTimeout)
|
||
}
|
||
|
||
// 如果输入框为空,隐藏搜索结果
|
||
if (!this.keyword.trim()) {
|
||
this.showSearchResults = false
|
||
this.searchResults = []
|
||
return
|
||
}
|
||
|
||
// 设置防抖,300ms后执行搜索
|
||
this.searchTimeout = setTimeout(() => {
|
||
this.performRandomSearch()
|
||
}, 300)
|
||
},
|
||
|
||
// 执行搜索
|
||
performRandomSearch() {
|
||
if (!this.keyword.trim()) {
|
||
this.showSearchResults = false
|
||
this.searchResults = []
|
||
return
|
||
}
|
||
|
||
console.log('执行联想搜索:', { keyword: this.keyword, type: this.publish_type });
|
||
|
||
// 调用统一的搜索方法
|
||
this.performSearch(this.keyword, this.publish_type);
|
||
},
|
||
|
||
// 统一的搜索方法
|
||
performSearch(keyword, type) {
|
||
console.log('执行联想搜索:', { keyword, type });
|
||
|
||
reqSearch({
|
||
url_link: window.location.href,
|
||
keyword: keyword,
|
||
publish_type: type,
|
||
display_page: 'overview', // 联想搜索使用 overview
|
||
current_page: 1,
|
||
page_size: 15
|
||
}).then(res => {
|
||
this.searchResults = res.data.result
|
||
console.log('联想搜索结果:', res)
|
||
this.showSearchResults = true
|
||
|
||
// 联想搜索不需要更新URL参数,只显示结果
|
||
console.log('联想搜索完成,显示结果');
|
||
|
||
}).catch(error => {
|
||
console.error('联想搜索失败:', error);
|
||
this.$message.error('搜索失败,请重试');
|
||
});
|
||
},
|
||
|
||
// 正式搜索方法(用于在搜索路由下重新调用接口)
|
||
performFormalSearch(keyword, type) {
|
||
console.log('=== 执行正式搜索开始 ===');
|
||
console.log('搜索参数:', { keyword, type });
|
||
console.log('当前路由:', this.$route.path);
|
||
console.log('事件总线实例:', eventBus);
|
||
|
||
reqSearch({
|
||
url_link: window.location.href,
|
||
keyword: keyword,
|
||
publish_type: type,
|
||
display_page: 'list', // 正式搜索使用 list
|
||
current_page: 1,
|
||
page_size: 8
|
||
}).then(res => {
|
||
console.log('正式搜索结果:', res);
|
||
|
||
// 准备发送事件数据
|
||
const eventData = {
|
||
type: type,
|
||
keyword: keyword,
|
||
data: res.data
|
||
};
|
||
console.log('准备发送事件数据:', eventData);
|
||
|
||
// 通过事件总线触发搜索事件,让兄弟组件处理结果
|
||
try {
|
||
eventBus.$emit('search', eventData);
|
||
console.log('事件总线事件发送成功');
|
||
} catch (error) {
|
||
console.error('事件总线事件发送失败:', error);
|
||
}
|
||
|
||
console.log('正式搜索完成,已触发搜索事件');
|
||
|
||
}).catch(error => {
|
||
console.error('正式搜索失败:', error);
|
||
this.$message.error('搜索失败,请重试');
|
||
});
|
||
},
|
||
|
||
// 更新URL参数
|
||
updateURLParams(keyword, type) {
|
||
const query = { ...this.$route.query };
|
||
|
||
if (keyword) {
|
||
query.keyword = keyword;
|
||
} else {
|
||
delete query.keyword;
|
||
}
|
||
|
||
if (type) {
|
||
query.publish_type = type;
|
||
} else {
|
||
delete query.publish_type;
|
||
}
|
||
|
||
console.log('更新URL参数:', {
|
||
oldQuery: this.$route.query,
|
||
newQuery: query
|
||
});
|
||
|
||
// 直接更新URL参数,不检查是否变化
|
||
this.$router.replace({
|
||
path: this.$route.path,
|
||
query: query
|
||
}).catch(err => {
|
||
// 忽略重复导航错误
|
||
if (err.name !== 'NavigationDuplicated') {
|
||
console.error('更新URL参数失败:', err);
|
||
} else {
|
||
console.log('URL参数更新重复,忽略错误');
|
||
}
|
||
});
|
||
},
|
||
|
||
// 提取纯文本内容
|
||
extractPlainText(htmlText) {
|
||
// 创建临时DOM元素来提取纯文本
|
||
const tempDiv = document.createElement('div');
|
||
tempDiv.innerHTML = htmlText;
|
||
return tempDiv.textContent || tempDiv.innerText || '';
|
||
},
|
||
|
||
// 处理类型选择变化
|
||
handleTypeChange(value) {
|
||
console.log('选择的类型:', value === '1' ? '商品' : '需求', value)
|
||
this.publish_type = value
|
||
|
||
// 如果当前有搜索结果,重新搜索
|
||
if (this.keyword.trim() && this.showSearchResults) {
|
||
this.performSearch(this.keyword, value);
|
||
}
|
||
},
|
||
|
||
// 处理搜索
|
||
handleSearch(result) {
|
||
// 立即阻止事件传播,确保方法能被正确执行
|
||
if (event) {
|
||
event.stopPropagation();
|
||
event.preventDefault();
|
||
}
|
||
|
||
console.log('=== handleSearch 开始执行 ===');
|
||
console.log('接收到的参数:', result);
|
||
console.log('当前路由路径:', this.$route.path);
|
||
console.log('当前查询参数:', this.$route.query);
|
||
|
||
// 如果传入了result参数(来自联想结果),使用其product_name作为keyword
|
||
if (result && result.product_name) {
|
||
console.log('使用联想结果作为搜索关键词');
|
||
const plainText = this.extractPlainText(result.product_name);
|
||
this.keyword = plainText;
|
||
console.log('提取的纯文本:', plainText);
|
||
|
||
// 立即隐藏搜索结果,避免被全局点击事件干扰
|
||
this.showSearchResults = false;
|
||
this.searchResults = [];
|
||
} else {
|
||
console.log('使用输入框的关键词进行搜索');
|
||
// 如果没有传入result,检查输入框是否有内容
|
||
if (!this.keyword.trim()) {
|
||
this.$message.warning('请输入搜索关键词');
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 执行搜索逻辑
|
||
console.log('准备执行搜索,关键词:', this.keyword, '类型:', this.publish_type);
|
||
|
||
// 通过路由传参控制搜索行为
|
||
const currentRoute = this.$route;
|
||
const currentDisplayPage = currentRoute.query.display_page;
|
||
|
||
if (currentDisplayPage !== 'list') {
|
||
// 不在搜索结果页面,需要跳转
|
||
console.log('跳转到搜索结果页面');
|
||
this.goSearch()
|
||
} else {
|
||
// 已经在搜索结果页面,重新调用接口获取最新数据
|
||
console.log('已在搜索结果页面,重新调用接口获取最新数据');
|
||
// 更新URL参数
|
||
this.updateURLParams(this.keyword, this.publish_type);
|
||
|
||
// 重新调用搜索接口,获取最新数据
|
||
this.performFormalSearch(this.keyword, this.publish_type);
|
||
}
|
||
|
||
console.log('=== handleSearch 执行完成 ===');
|
||
},
|
||
|
||
// 处理热门关键词点击
|
||
handleHotKeywordClick(keyword) {
|
||
this.keyword = keyword
|
||
this.handleSearch()
|
||
},
|
||
|
||
// 处理输入框聚焦
|
||
handleInputFocus() {
|
||
// 聚焦时不立即显示联想框,只有当有搜索结果时才显示
|
||
if (this.keyword.trim() && this.searchResults.length > 0) {
|
||
this.showSearchResults = true;
|
||
}
|
||
},
|
||
|
||
// 处理输入框失焦
|
||
handleInputBlur() {
|
||
// 延迟隐藏搜索结果,给用户点击联想结果的机会
|
||
setTimeout(() => {
|
||
if (!this.$el.contains(document.activeElement)) {
|
||
this.hideSearchResults()
|
||
}
|
||
}, 100); // 延迟100ms
|
||
},
|
||
|
||
// 手动隐藏搜索结果
|
||
hideSearchResults() {
|
||
this.showSearchResults = false
|
||
this.searchResults = []
|
||
},
|
||
|
||
// 处理全局点击事件
|
||
handleGlobalClick(event) {
|
||
// 延迟处理,避免干扰其他点击事件
|
||
setTimeout(() => {
|
||
// 检查点击是否在搜索框内部或联想结果内部
|
||
if (this.$el && this.$el.contains(event.target)) {
|
||
console.log('点击在搜索组件内部,不隐藏联想结果');
|
||
return;
|
||
}
|
||
|
||
// 如果联想结果正在显示,则隐藏
|
||
if (this.showSearchResults) {
|
||
console.log('点击在搜索组件外部,隐藏联想结果');
|
||
this.hideSearchResults();
|
||
}
|
||
}, 100); // 增加延迟到100ms,给其他事件处理器留出更多时间
|
||
},
|
||
|
||
// 高亮显示搜索关键词
|
||
highlightKeyword(item) {
|
||
console.log(item)
|
||
|
||
if (!this.keyword || !this.keyword.trim()) {
|
||
return item.product_name || item;
|
||
}
|
||
|
||
const keyword = this.keyword.trim();
|
||
// 使用 product_name 字段作为显示文本
|
||
const text = item.product_name || item;
|
||
|
||
console.log('高亮处理:', { keyword, text, item, textType: typeof text });
|
||
|
||
// 如果关键词为空,直接返回原文本
|
||
if (!keyword) {
|
||
return text;
|
||
}
|
||
|
||
// 如果文本为空或不是字符串,返回原文本
|
||
if (!text || typeof text !== 'string') {
|
||
console.warn('文本不是字符串类型:', text);
|
||
return text;
|
||
}
|
||
|
||
// 创建正则表达式,支持多个关键词(用空格分隔)
|
||
const keywords = keyword.split(/\s+/).filter(k => k.length > 0);
|
||
let highlightedText = text;
|
||
|
||
// 对每个关键词进行高亮处理
|
||
keywords.forEach(key => {
|
||
if (key.length > 0) {
|
||
const regex = new RegExp(`(${this.escapeRegExp(key)})`, 'gi');
|
||
highlightedText = highlightedText.replace(regex, '<span class="highlighted-keyword">$1</span>');
|
||
}
|
||
});
|
||
|
||
console.log('高亮结果:', highlightedText);
|
||
return highlightedText;
|
||
},
|
||
|
||
// 转义正则表达式特殊字符
|
||
escapeRegExp(string) {
|
||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||
},
|
||
|
||
// 处理路由变化
|
||
handleRouteChange(to, from) {
|
||
console.log('处理路由变化:', { to, from, currentKeyword: this.keyword });
|
||
|
||
const keyword = to.query.keyword;
|
||
const publishType = to.query.publish_type;
|
||
|
||
if (keyword) {
|
||
// 直接更新搜索状态,不检查是否相同
|
||
console.log('更新搜索状态:', { keyword, publishType });
|
||
this.keyword = keyword;
|
||
this.publish_type = publishType || '1'; // 如果没有指定类型,默认为商品
|
||
|
||
// 确保组件状态正确初始化
|
||
this.showSearchResults = false;
|
||
this.searchResults = [];
|
||
|
||
// 重新绑定事件监听器,确保点击事件正常工作
|
||
this.$nextTick(() => {
|
||
this.reinitializeEventListeners();
|
||
});
|
||
|
||
// 不在这里执行搜索,只更新状态
|
||
// 搜索由用户点击搜索按钮或选择联想结果时触发
|
||
console.log('搜索状态已更新,等待用户操作');
|
||
} else {
|
||
// 如果没有搜索参数,清空状态
|
||
if (this.keyword || this.publish_type !== '1') {
|
||
console.log('清空搜索状态');
|
||
this.keyword = '';
|
||
this.publish_type = '1';
|
||
this.hideSearchResults();
|
||
}
|
||
}
|
||
},
|
||
|
||
// 重新初始化事件监听器
|
||
reinitializeEventListeners() {
|
||
console.log('重新初始化事件监听器');
|
||
|
||
// 移除旧的监听器
|
||
document.removeEventListener('click', this.handleGlobalClick);
|
||
|
||
// 重新添加新的监听器
|
||
document.addEventListener('click', this.handleGlobalClick);
|
||
|
||
// 确保组件状态正确
|
||
this.showSearchResults = false;
|
||
this.searchResults = [];
|
||
|
||
console.log('事件监听器重新初始化完成');
|
||
}
|
||
},
|
||
|
||
// 组件挂载后添加全局点击事件监听
|
||
mounted() {
|
||
console.log('=== 组件挂载开始 ===');
|
||
console.log('当前路由:', this.$route.path);
|
||
console.log('当前查询参数:', this.$route.query);
|
||
|
||
document.addEventListener('click', this.handleGlobalClick)
|
||
console.log('全局点击事件监听器已添加');
|
||
|
||
// 检查URL参数,如果有搜索参数则自动执行搜索
|
||
this.handleRouteChange(this.$route, null);
|
||
|
||
// 添加路由监听,确保子路由变化时能正确处理
|
||
this.$watch('$route', (to, from) => {
|
||
console.log('路由变化监听:', { to, from });
|
||
this.handleRouteChange(to, from);
|
||
}, { immediate: true, deep: true });
|
||
|
||
console.log('=== 组件挂载完成 ===');
|
||
},
|
||
|
||
// 组件销毁时清理定时器和事件监听器
|
||
beforeDestroy() {
|
||
if (this.searchTimeout) {
|
||
clearTimeout(this.searchTimeout)
|
||
}
|
||
document.removeEventListener('click', this.handleGlobalClick)
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.search-section {
|
||
flex: 1;
|
||
min-width: 850px;
|
||
// padding-bottom: 10px;
|
||
.search-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
background: white;
|
||
border: 2px solid #3f68d8;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
position: relative;
|
||
transition: border-radius 0.2s ease;
|
||
|
||
// 当显示搜索结果时,隐藏下边框并调整圆角
|
||
&.has-results {
|
||
border-bottom-color: transparent;
|
||
border-radius: 4px 4px 0 0;
|
||
}
|
||
|
||
.search-input {
|
||
flex: 1;
|
||
padding: 12px 15px;
|
||
border: none;
|
||
outline: none;
|
||
font-size: 14px;
|
||
padding-left: 0;
|
||
}
|
||
|
||
.search-btn {
|
||
background: #0864dd;
|
||
color: white;
|
||
border: none;
|
||
padding: 12px 20px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
|
||
&:hover {
|
||
background: #173ad4;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 搜索结果样式
|
||
.search-results {
|
||
position: absolute;
|
||
top: 42px;
|
||
left: 0;
|
||
right: 0;
|
||
background: white;
|
||
border: 2px solid #3f68d8;
|
||
border-top: none;
|
||
border-radius: 0 0 4px 4px;
|
||
z-index: 99999;
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
|
||
.search-result-item {
|
||
padding: 12px 15px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
&:hover {
|
||
background-color: #f5f7fa;
|
||
|
||
* {
|
||
color: #0864dd!important;
|
||
|
||
|
||
}
|
||
}
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.result-title {
|
||
font-size: 14px;
|
||
color: #333;
|
||
}
|
||
|
||
.result-type {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
padding: 2px 8px;
|
||
background: #f0f2f5;
|
||
border-radius: 10px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.hot-search {
|
||
margin-top: 8px;
|
||
display: flex;
|
||
gap: 15px;
|
||
flex-wrap: wrap;
|
||
|
||
|
||
|
||
.search-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.hot-keyword {
|
||
&:nth-child(-n+3) {
|
||
color: #e1251b;
|
||
}
|
||
|
||
color: #666;
|
||
text-decoration: none;
|
||
font-size: 12px;
|
||
|
||
&:hover {
|
||
color: #e1251b;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
::v-deep .mySelect {
|
||
input {
|
||
border: none;
|
||
}
|
||
}
|
||
|
||
::v-deep .highlighted-keyword {
|
||
font-weight: bold;
|
||
color: #0864dd;
|
||
}
|
||
|
||
.logo-clickable {
|
||
cursor: pointer;
|
||
transition: opacity 0.2s ease;
|
||
|
||
&:hover {
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.search-section {
|
||
width: 100%;
|
||
}
|
||
}
|
||
</style>
|