687 lines
18 KiB
Vue
687 lines
18 KiB
Vue
<template>
|
||
<!-- 左侧分类导航 -->
|
||
<aside class="category-sidebar">
|
||
<ul class="category-list">
|
||
<li class="category-item special-item">
|
||
<img src="../img/hot.svg" alt="热门推荐">
|
||
热门推荐 / 活动促销
|
||
</li>
|
||
<li v-for="category in categories" :key="category.id" class="category-item"
|
||
@mouseenter="showProductList(category)" @mouseleave="hideProductList"
|
||
:class="{ 'category-item-active': currentCategory && currentCategory.id === category.id }">
|
||
<span class="category-icon">
|
||
<img :src="category.icon" :alt="category.first_level_name">
|
||
</span>
|
||
<span class="category-name">{{ category.first_level_name }}</span>
|
||
<span class="category-divider">|</span>
|
||
<div class="menu-item">
|
||
<span v-for="(secondary, index) in category.secondaryClassification" :key="secondary.id">
|
||
{{ secondary.second_level_name }}{{ index < category.secondaryClassification.length - 1 ? ' / ' : '' }}
|
||
</span>
|
||
</div>
|
||
<span class="category-arrow">›</span>
|
||
</li>
|
||
</ul>
|
||
|
||
<transition name="slide-fade">
|
||
<div v-loading="loading" element-loading-text="加载中..." element-loading-spinner="el-icon-loading"
|
||
element-loading-background="rgba(255, 255, 255, 0.8)" class="rightBox" v-if="currentCategory"
|
||
@mouseenter="keepProductList" @mouseleave="hideProductList">
|
||
<div class="rightBox-content">
|
||
<!-- 二级菜单标题 -->
|
||
<div class="secondary-menu">
|
||
<div v-for="secondary in currentCategory.secondaryClassification" :key="secondary.id" class="secondary-item"
|
||
:class="{
|
||
active: selectedSecondary === secondary,
|
||
'has-children': secondary.thirdClassification && secondary.thirdClassification.length > 0
|
||
}"
|
||
@mouseenter="selectSecondary(secondary)"
|
||
@click="handleSecondaryClick(secondary)">
|
||
{{ secondary.second_level_name }}
|
||
<span v-if="secondary.thirdClassification && secondary.thirdClassification.length > 0" class="item-arrow">›</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 三级和四级菜单内容 -->
|
||
<div class="menu-content">
|
||
<!-- 如果有选中的二级菜单且有三级菜单,显示京东风格的分类区域 -->
|
||
<div
|
||
v-if="selectedSecondary && selectedSecondary.thirdClassification && selectedSecondary.thirdClassification.length > 0"
|
||
class="jd-style-menu">
|
||
<div v-for="third in selectedSecondary.thirdClassification" :key="third.id" class="category-section">
|
||
<!-- 只有当有四级菜单时才显示三级菜单标题 -->
|
||
<div v-if="third.product_list && third.product_list.length > 0" class="section-header">
|
||
<span class="section-title">{{ third.third_level_name }}</span>
|
||
<span class="section-arrow">›</span>
|
||
</div>
|
||
<div class="section-content">
|
||
<!-- 如果有四级菜单(product_list),直接显示所有四级菜单项 -->
|
||
<div v-if="third.product_list && third.product_list.length > 0" class="product-grid">
|
||
<div @click="goSearch(product)" v-for="(product, index) in third.product_list" :key="product.id"
|
||
class="product-tag">
|
||
{{ product.first_level_name }}
|
||
</div>
|
||
</div>
|
||
<!-- 如果没有四级菜单,将三级菜单项视为四级菜单项显示 -->
|
||
<div v-else class="product-grid">
|
||
<div @click="openTalk" class="product-tag special-tag">
|
||
{{ third.third_level_name }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 如果没有三级菜单,显示二级菜单项 -->
|
||
<div v-else-if="selectedSecondary" class="jd-style-menu">
|
||
<div class="category-section">
|
||
<div class="section-header">
|
||
<span class="section-title">{{ selectedSecondary.second_level_name }}</span>
|
||
<span class="section-arrow">›</span>
|
||
</div>
|
||
<div class="section-content">
|
||
<div class="product-grid">
|
||
<div class="product-tag special-tag" @click="goSearch(selectedSecondary)">
|
||
{{ selectedSecondary.second_level_name }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 默认:显示所有二级菜单项 -->
|
||
<div v-else class="jd-style-menu">
|
||
<div v-for="secondary in currentCategory.secondaryClassification" :key="secondary.id"
|
||
class="category-section">
|
||
<div class="section-header">
|
||
<span class="section-title">{{ secondary.second_level_name }}</span>
|
||
<span class="section-arrow">›</span>
|
||
</div>
|
||
<div class="section-content">
|
||
<div class="product-grid">
|
||
<div class="product-tag" @click="goSearch(secondary)">
|
||
{{ secondary.second_level_name }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</transition>
|
||
<Talk></Talk>
|
||
</aside>
|
||
</template>
|
||
|
||
<script>
|
||
import { reqNcMatchMenu, reqSearch } from '@/api/ncmatch';
|
||
import { buildDynamicStructure } from './buildNcmatchTree';
|
||
import Talk from '@/views/homePage/dialog/talk/index.vue';
|
||
import eventBus from '@/utils/eventBus'
|
||
|
||
export default {
|
||
name: 'menuAside',
|
||
components: {
|
||
Talk
|
||
},
|
||
data() {
|
||
return {
|
||
currentCategory: null,
|
||
selectedSecondary: null,
|
||
hideTimer: null,
|
||
categories: [],
|
||
loading: false,
|
||
keyword: '',
|
||
publish_type: '',
|
||
activeSection: null
|
||
}
|
||
},
|
||
created() {
|
||
this.getCategories();
|
||
},
|
||
methods: {
|
||
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('搜索失败,请重试');
|
||
});
|
||
},
|
||
goSearch(product) {
|
||
console.log("product", product);
|
||
|
||
// 添加点击动画效果
|
||
const element = event.target;
|
||
element.classList.add('click-animation');
|
||
setTimeout(() => {
|
||
element.classList.remove('click-animation');
|
||
}, 300);
|
||
|
||
if (product.source == 'search') {
|
||
// 统一与 search 组件的行为:跳转到 /ncmatchHome/search 并触发正式搜索
|
||
const keywordFromItem = product && (product.first_level_name || product.product_name || product.second_level_name)
|
||
this.keyword = keywordFromItem || this.keyword || ''
|
||
this.publish_type = this.publish_type || '1'
|
||
this.$router.push({
|
||
path: '/ncmatchHome/search',
|
||
query: {
|
||
keyword: this.keyword,
|
||
publish_type: "1",
|
||
display_page: 'list',
|
||
current_page: 1,
|
||
page_size: 8
|
||
}
|
||
}).catch(err => {
|
||
if (err && err.name !== 'NavigationDuplicated') {
|
||
// ignore duplicate
|
||
// console.error(err)
|
||
}
|
||
})
|
||
this.performFormalSearch(this.keyword, this.publish_type);
|
||
} else {
|
||
this.openTalk()
|
||
}
|
||
},
|
||
|
||
async openTalk() {
|
||
await this.hideProductList('quick')
|
||
this.$store.commit('setShowTalk', true);
|
||
},
|
||
getCategories() {
|
||
this.loading = true;
|
||
reqNcMatchMenu({ url_link: window.location.href }).then(res => {
|
||
this.loading = false;
|
||
if (res.status) {
|
||
this.categories = buildDynamicStructure(res.data)
|
||
console.log("测试", this.categories);
|
||
}
|
||
})
|
||
},
|
||
showProductList(category) {
|
||
// 清除之前的定时器
|
||
if (this.hideTimer) {
|
||
clearTimeout(this.hideTimer);
|
||
this.hideTimer = null;
|
||
}
|
||
this.currentCategory = category;
|
||
// 自动选中第一个二级菜单
|
||
if (category.secondaryClassification && category.secondaryClassification.length > 0) {
|
||
this.selectedSecondary = category.secondaryClassification[0];
|
||
} else {
|
||
this.selectedSecondary = null;
|
||
}
|
||
},
|
||
selectSecondary(secondary) {
|
||
this.selectedSecondary = secondary;
|
||
},
|
||
|
||
handleSecondaryClick(secondary) {
|
||
this.selectSecondary(secondary);
|
||
// 添加点击反馈
|
||
if (event) {
|
||
const element = event.target;
|
||
element.classList.add('click-feedback');
|
||
setTimeout(() => {
|
||
element.classList.remove('click-feedback');
|
||
}, 300);
|
||
}
|
||
},
|
||
|
||
keepProductList() {
|
||
// 清除隐藏定时器,保持显示
|
||
if (this.hideTimer) {
|
||
clearTimeout(this.hideTimer);
|
||
this.hideTimer = null;
|
||
}
|
||
},
|
||
hideProductList(type) {
|
||
if (type === 'quick') {
|
||
this.currentCategory = null;
|
||
this.selectedSecondary = null;
|
||
this.hideTimer = null;
|
||
return
|
||
}
|
||
// 延迟隐藏,给用户时间移动到rightBox
|
||
this.hideTimer = setTimeout(() => {
|
||
this.currentCategory = null;
|
||
this.selectedSecondary = null;
|
||
this.hideTimer = null;
|
||
}, 200);
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.category-sidebar {
|
||
position: relative;
|
||
background-color: #f8fbfe;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||
height: 100%;
|
||
border-radius: 10px;
|
||
padding: 15px 5px;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.category-list {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
width: 100%;
|
||
|
||
.category-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 12px;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
border-radius: 6px;
|
||
margin-bottom: 8px;
|
||
position: relative;
|
||
|
||
&:hover {
|
||
color: #2c96fc !important;
|
||
background: #e6f3ff !important;
|
||
transform: translateX(5px);
|
||
box-shadow: 0 2px 8px rgba(44, 150, 252, 0.2);
|
||
}
|
||
|
||
&.category-item-active {
|
||
color: #2c96fc !important;
|
||
background: #e6f3ff !important;
|
||
border-right: 3px solid #2c96fc;
|
||
}
|
||
|
||
&.special-item {
|
||
color: #E02020;
|
||
font-weight: 600;
|
||
|
||
img {
|
||
margin-right: 8px;
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
}
|
||
|
||
.category-icon {
|
||
width: 24px;
|
||
height: 24px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
|
||
img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: contain;
|
||
transition: transform 0.3s ease;
|
||
}
|
||
}
|
||
|
||
&:hover .category-icon img {
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.category-name {
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.category-divider {
|
||
color: #e0e0e0;
|
||
margin: 0 4px;
|
||
}
|
||
|
||
.menu-item {
|
||
flex: 1;
|
||
font-size: 14px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
min-width: 0;
|
||
color: #666;
|
||
}
|
||
|
||
.category-arrow {
|
||
color: #ccc;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
transition: transform 0.3s ease, color 0.3s ease;
|
||
}
|
||
|
||
&:hover .category-arrow {
|
||
color: #2c96fc;
|
||
transform: translateX(3px);
|
||
}
|
||
}
|
||
}
|
||
|
||
.rightBox {
|
||
position: absolute;
|
||
left: 100%;
|
||
top: 0;
|
||
width: 900px;
|
||
background: white;
|
||
border-radius: 8px;
|
||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
||
z-index: 1000;
|
||
padding: 20px;
|
||
min-height: 400px;
|
||
margin-left: 8px;
|
||
overflow: visible;
|
||
border: 1px solid #e8e8e8;
|
||
animation: fadeInScale 0.3s ease;
|
||
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: -10px;
|
||
top: 0;
|
||
width: 10px;
|
||
height: 100%;
|
||
background: transparent;
|
||
}
|
||
|
||
.rightBox-content {
|
||
height: 100%;
|
||
|
||
.secondary-menu {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 15px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
|
||
.secondary-item {
|
||
padding: 8px 16px;
|
||
background: #f5f7fa;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
font-size: 14px;
|
||
color: #333;
|
||
font-weight: 500;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: -100%;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
|
||
transition: left 0.5s;
|
||
}
|
||
|
||
&:hover::before {
|
||
left: 100%;
|
||
}
|
||
|
||
&:hover,
|
||
&.active {
|
||
background: linear-gradient(to right, #275AFF, #2EBDFA);
|
||
color: #fff;
|
||
box-shadow: 0 4px 12px rgba(39, 90, 255, 0.3);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
&.has-children::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: -2px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 0;
|
||
height: 2px;
|
||
background: #275AFF;
|
||
transition: width 0.3s ease;
|
||
}
|
||
|
||
&.active.has-children::after {
|
||
width: 80%;
|
||
}
|
||
|
||
&.click-feedback {
|
||
animation: subtle-pulse 0.3s ease;
|
||
}
|
||
|
||
.item-arrow {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
&:hover .item-arrow,
|
||
&.active .item-arrow {
|
||
transform: translateX(3px);
|
||
}
|
||
}
|
||
}
|
||
|
||
.menu-content {
|
||
min-height: 280px;
|
||
max-height: 500px;
|
||
overflow-y: auto;
|
||
padding-right: 5px;
|
||
|
||
.jd-style-menu {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
|
||
.category-section {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
margin-bottom: 15px;
|
||
|
||
.section-header {
|
||
display: flex;
|
||
align-items: center;
|
||
min-width: 120px;
|
||
margin-right: 20px;
|
||
|
||
.section-title {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-right: 6px;
|
||
}
|
||
|
||
.section-arrow {
|
||
color: #999;
|
||
font-size: 12px;
|
||
}
|
||
}
|
||
|
||
.section-content {
|
||
flex: 1;
|
||
|
||
.product-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
|
||
.product-tag {
|
||
padding: 8px 16px;
|
||
font-size: 14px;
|
||
color: #666;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
border-radius: 20px;
|
||
background: #f8f9fa;
|
||
position: relative;
|
||
overflow: hidden;
|
||
border: 1px solid transparent;
|
||
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: -100%;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent);
|
||
transition: left 0.5s;
|
||
}
|
||
|
||
&:hover::before {
|
||
left: 100%;
|
||
}
|
||
|
||
&:hover {
|
||
color: #275AFF;
|
||
background: #f0f7ff;
|
||
border-color: #275AFF;
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 8px rgba(39, 90, 255, 0.2);
|
||
}
|
||
|
||
&.click-animation {
|
||
animation: subtle-bounce 0.3s ease;
|
||
}
|
||
}
|
||
|
||
.special-tag {
|
||
color: #275AFF !important;
|
||
border: 1px solid #275AFF !important;
|
||
background: #f0f7ff !important;
|
||
font-weight: 500 !important;
|
||
|
||
&:hover {
|
||
color: #275AFF !important;
|
||
background: #e0efff !important;
|
||
box-shadow: 0 4px 8px rgba(39, 90, 255, 0.2) !important;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 动画定义 */
|
||
@keyframes fadeInScale {
|
||
0% {
|
||
opacity: 0;
|
||
transform: scale(0.95) translateX(10px);
|
||
}
|
||
100% {
|
||
opacity: 1;
|
||
transform: scale(1) translateX(0);
|
||
}
|
||
}
|
||
|
||
@keyframes subtle-pulse {
|
||
0% {
|
||
transform: scale(1);
|
||
}
|
||
50% {
|
||
transform: scale(1.02);
|
||
}
|
||
100% {
|
||
transform: scale(1);
|
||
}
|
||
}
|
||
|
||
@keyframes subtle-bounce {
|
||
0%, 20%, 60%, 100% {
|
||
transform: translateY(0);
|
||
}
|
||
40% {
|
||
transform: translateY(-3px);
|
||
}
|
||
80% {
|
||
transform: translateY(-1px);
|
||
}
|
||
}
|
||
|
||
/* 过渡动画样式 */
|
||
.slide-fade-enter-active {
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.slide-fade-leave-active {
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.slide-fade-enter {
|
||
opacity: 0;
|
||
transform: translateX(10px);
|
||
}
|
||
|
||
.slide-fade-leave-to {
|
||
opacity: 0;
|
||
transform: translateX(10px);
|
||
}
|
||
|
||
.slide-fade-enter-to,
|
||
.slide-fade-leave {
|
||
opacity: 1;
|
||
transform: translateX(0);
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 1200px) {
|
||
.rightBox {
|
||
width: 800px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 992px) {
|
||
.rightBox {
|
||
width: 700px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.category-sidebar {
|
||
display: none;
|
||
}
|
||
}
|
||
</style>
|