973 lines
30 KiB
Vue
973 lines
30 KiB
Vue
<template>
|
||
<!-- 主页布局 -->
|
||
<a-layout theme="light" style="height: 100%; overflow: hidden">
|
||
<!-- 左侧边栏 -->
|
||
<a-layout-sider theme="light" v-model:collapsed="collapsed" :trigger="null" collapsible>
|
||
<!-- 左侧边栏的顶部header -->
|
||
<a-layout-header class="logo" style=" padding: 0; height: 50px; line-height: 50px; text-align: center" mode="inline"> <!--background: #646ee3;-->
|
||
<a href="/" target="_self">
|
||
<img v-if="collapsed == false" src="/images/logo-piao.png" alt="智汇票务" style="height: 36px" />
|
||
<img v-if="collapsed == true" src="/images/logo-piao-one.png" alt="智汇票务" style="height: 36px" />
|
||
</a>
|
||
</a-layout-header>
|
||
<!-- 左侧边栏的菜单栏 -->
|
||
<a-menu v-model:selectedKeys="menuState.selectedMenuKeys" :open-keys="menuState.openedFaterMenuKeys" theme="light"
|
||
class="side-left-menu" mode="inline" style="height: 100%; overflow: scroll; padding-top: 4px"
|
||
@openChange="expandMenu">
|
||
|
||
<a-menu-item key="index" @click="clickMenuItem('HomePage', '/home', '主页', 'IndexPage', 'index', '工作台', 'index')">
|
||
<template #icon>
|
||
<ZhihuiIcon icon="HomeOutlined" />
|
||
</template>
|
||
<span>票务总台</span>
|
||
</a-menu-item>
|
||
|
||
<!-- 循环生成菜单:菜单的key使用sidebarRouters中的path,父菜单的key带“/”,子菜单的key不带“/” -->
|
||
<a-sub-menu v-for="router in sidebarRouters" :key="router.path" style="padding-top:0px;">
|
||
<template #icon>
|
||
<ZhihuiIcon :icon="router.meta.icon" />
|
||
</template>
|
||
<template #title>
|
||
<span style="display: flex; align-items: center; justify-content: flex-start; width: 100%;">
|
||
<span v-html="router.meta.link ? `<a href='${router.meta.link}' target='_blank'>${router.meta.title}</a>` : router.meta.title"></span>
|
||
<a-badge v-if="menuBadges[router.path] && menuBadges[router.path] > 0" :count="menuBadges[router.path]" size="small" />
|
||
</span>
|
||
</template>
|
||
<div v-for="children in router.children" :key="children.path" class="side-left-menu-sub">
|
||
<!-- 二级父菜单(有子菜单) -->
|
||
<a-sub-menu v-if="children.children && children.children.length > 0" :key="children.path">
|
||
<template #icon>
|
||
<ZhihuiIcon :icon="children.meta.icon" />
|
||
</template>
|
||
<template #title>
|
||
<span style="display: flex; align-items: center; justify-content: flex-start; width: 100%;">
|
||
<span>{{ children.meta.title }}</span>
|
||
<a-badge v-if="menuBadges[children.path] && menuBadges[children.path] > 0" :count="menuBadges[children.path]" size="small" />
|
||
</span>
|
||
</template>
|
||
<!-- 三级菜单 -->
|
||
<a-menu-item
|
||
v-for="threeLevelChildren in children.children"
|
||
:key="threeLevelChildren.path"
|
||
@click="clickThreeLevelMenuItem(router.path, children.name, children.path, children.meta.title, threeLevelChildren.name, threeLevelChildren.path, threeLevelChildren.meta.title, threeLevelChildren.component)">
|
||
<template #icon>
|
||
<ZhihuiIcon :icon="threeLevelChildren.meta.icon" />
|
||
</template>
|
||
<!-- 使用条件判断来决定显示普通文本还是带badge的标题 -->
|
||
<template v-if="menuBadges[threeLevelChildren.path] && menuBadges[threeLevelChildren.path] > 0">
|
||
<span style="display: flex; align-items: center; justify-content: flex-start; width: 100%;">
|
||
<span>{{ threeLevelChildren.meta.title }}</span>
|
||
<a-badge :count="menuBadges[threeLevelChildren.path]" size="small" />
|
||
</span>
|
||
</template>
|
||
<template v-else>
|
||
<span>{{ threeLevelChildren.meta.title }}</span>
|
||
</template>
|
||
</a-menu-item>
|
||
</a-sub-menu>
|
||
<!-- 二级菜单(无子菜单) -->
|
||
<a-menu-item v-else :key="children.path" @click="clickMenuItem(router.name, router.path, router.meta.title, children.name, children.path, children.meta.title, children.component)">
|
||
<template #icon>
|
||
<ZhihuiIcon :icon="children.meta.icon" />
|
||
</template>
|
||
<!-- 使用条件判断来决定显示普通文本还是带badge的标题 -->
|
||
<template v-if="menuBadges[children.path] && menuBadges[children.path] > 0">
|
||
<span style="display: flex; align-items: center; justify-content: flex-start; width: 100%;">
|
||
<span>{{ children.meta.title }}</span>
|
||
<a-badge :count="menuBadges[children.path]" size="small" />
|
||
</span>
|
||
</template>
|
||
<template v-else>
|
||
<span>{{ children.meta.title }}</span>
|
||
</template>
|
||
</a-menu-item>
|
||
</div>
|
||
</a-sub-menu>
|
||
</a-menu>
|
||
<MusicPlayer :collapsed="collapsed" />
|
||
</a-layout-sider>
|
||
<!-- 右侧布局 -->
|
||
<a-layout>
|
||
<!-- 右侧布局的顶部header -->
|
||
<a-layout-header class="navbar" style="padding: 0; height: 50px; line-height: 50px" mode="inline">
|
||
<a-row class="navbar-row">
|
||
<a-col class="navbar-col-menu">
|
||
<MenuUnfoldOutlined v-if="collapsed" @click="() => (collapsed = !collapsed)"
|
||
style="color: rgb(253, 253, 253); padding-left: 20px" />
|
||
<MenuFoldOutlined v-else @click="() => (collapsed = !collapsed)"
|
||
style="color: rgb(253, 253, 253); padding-left: 20px" />
|
||
<span class="navbar-title">{{ headerInfo.currentFatherMenuTitle }} / {{ headerInfo.currentMenuTitle }}</span>
|
||
</a-col>
|
||
|
||
<!-- 右侧功能区容器 -->
|
||
<div class="navbar-right-group">
|
||
<!-- 倒计时显示 -->
|
||
<a-col class="navbar-col-countdown">
|
||
<div style="display: flex; align-items: center; justify-content: flex-start; height: 50px; color: #fff;">
|
||
<a-badge :status="refreshStatus" />
|
||
<span v-if="refreshInterval !== 0" :class="['countdown-number', { 'countdown-urgent': remainingSeconds <= 3 }]">
|
||
<LoadingOutlined v-if="refreshStatus === 'processing'" />
|
||
<span v-else>{{ remainingSeconds }}秒</span>
|
||
</span>
|
||
<span v-else class="countdown-number">
|
||
非自动刷新
|
||
</span>
|
||
</div>
|
||
</a-col>
|
||
|
||
<a-col class="navbar-col-actions">
|
||
<a-space :size="16" class="navbar-buttons">
|
||
<a-button type="primary" @click="toggleAirModal" v-if="isAdmin" class="navbar-btn">
|
||
航空公司
|
||
<span v-if="airVisible"><UpOutlined /></span>
|
||
<span v-else><DownOutlined /></span>
|
||
</a-button>
|
||
<a href="http://2233.zhonghangtianxia.com/AirRule/tuigaiqian.aspx?Airlines=GS" target="_blank"
|
||
class="ant-btn ant-btn-primary navbar-btn">退改政策</a>
|
||
<a href="/setting/platform" class="ant-btn ant-btn-primary navbar-btn">平台消息</a>
|
||
</a-space>
|
||
</a-col>
|
||
|
||
<a-col class="navbar-col-user">
|
||
<!-- 用户名称 -->
|
||
<a-dropdown v-model:visible="userDropdownVisible" placement="bottomRight" class="user-dropdown">
|
||
<a style="color: #fffeee" class="user-dropdown-link">
|
||
<img :src="avatarSrc" class="avatarIcon" @error="onAvatarError" />
|
||
<span class="user-name">{{ vueStore.getters.nickName }}</span>
|
||
<ZhihuiIcon icon= "UpOutlined" v-if="userDropdownVisible"/>
|
||
<ZhihuiIcon icon= "DownOutlined" v-else/>
|
||
</a>
|
||
<template #overlay>
|
||
<a-menu>
|
||
<a-menu-item>
|
||
<ZhihuiIcon icon="UserOutlined" style="padding-right: 10px" />
|
||
<a @click="enterPersonalCenter" style="padding-right: 10px">个人中心</a>
|
||
</a-menu-item>
|
||
<template v-if="isAdmin">
|
||
<a-menu-item>
|
||
<ZhihuiIcon icon="SettingOutlined" style="padding-right: 10px" />
|
||
<a @click="changeTheme" style="padding-right: 10px">配置中心</a>
|
||
</a-menu-item>
|
||
</template>
|
||
<a-menu-item>
|
||
<ZhihuiIcon icon="LogoutOutlined" style="padding-right: 10px" />
|
||
<a @click="logout" style="padding-right: 10px">退出登录</a>
|
||
</a-menu-item>
|
||
</a-menu>
|
||
</template>
|
||
</a-dropdown>
|
||
</a-col>
|
||
</div>
|
||
<a-modal class="air-modal" v-model:visible="airVisible" width="840px" title="国内航空公司列表"
|
||
:footer="null" :mask="true" :closable="false">
|
||
<div class="air-container">
|
||
<div class="air-list">
|
||
<a-row :gutter="[8, 1]">
|
||
<a-col :xs="12" :sm="8" :md="6" :lg="6" v-for="(airline, index) in airList" :key="airline.id" class="airline-col">
|
||
<a :href="airline.url" target="_blank" class="airline-link">
|
||
<span class="airline-info">{{ airline.name }}-{{ airline.code }}</span>
|
||
<img :src="`${airline.logo}`" alt="Logo" class="airline-logo" />
|
||
</a>
|
||
</a-col>
|
||
</a-row>
|
||
</div>
|
||
<a-spin size="middle" v-if="airLoading"></a-spin>
|
||
</div>
|
||
</a-modal>
|
||
</a-row>
|
||
</a-layout-header>
|
||
<!-- 右侧布局的主显示区 -->
|
||
<div class="main-area">
|
||
<a-layout-content class="content-area">
|
||
<div class="card-container">
|
||
<a-config-provider :locale="zhCN">
|
||
<router-view></router-view>
|
||
</a-config-provider>
|
||
</div>
|
||
</a-layout-content>
|
||
<AIChat />
|
||
</div>
|
||
</a-layout>
|
||
</a-layout>
|
||
<SettingDrawer ref="settingDrawer" />
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, reactive, onMounted, onUnmounted, watch } from 'vue';
|
||
import { useRouter, useRoute } from 'vue-router';
|
||
import { useStore, mapGetters } from 'vuex';
|
||
import {
|
||
MenuFoldOutlined,
|
||
MenuUnfoldOutlined,
|
||
UpOutlined,
|
||
DownOutlined,
|
||
LoadingOutlined,
|
||
} from '@ant-design/icons-vue';
|
||
import { ZhihuiIcon } from '@/utils/ZhihuiIcon.js';
|
||
import zhCN from 'ant-design-vue/es/locale/zh_CN';
|
||
import SettingDrawer from './SettingDrawer.vue';
|
||
import MusicPlayer from '@/components/MusicPlayer.vue';
|
||
import AIChat from '@/components/AIChat.vue';
|
||
import { message } from 'ant-design-vue';
|
||
import store from '@/store';
|
||
import dayjs from 'dayjs';
|
||
import 'dayjs/locale/zh-cn';
|
||
import { airlineApi } from '@/api/piao/airline';
|
||
import { PAGE_SIZE_OPTIONS } from '@/api/piao/constant';
|
||
import { messageApi } from '@/api/piao/message';
|
||
import zhihuiBus from '@/utils/zhihuiBus'; // 引入事件总线
|
||
import zhihuiAudio from '@/utils/zhihuiAudio'; // 引入音频播放器
|
||
|
||
dayjs.locale('zh-cn');
|
||
|
||
const userDropdownVisible = ref(false);
|
||
const avatarError = ref(false);
|
||
const avatarSrc = computed(() => {
|
||
if (avatarError.value) return "/images/defaultavatar.png";
|
||
return store.getters.avatar ? import.meta.env.VITE_API_DOMAIN + store.getters.avatar : "/images/defaultavatar.png";
|
||
});
|
||
const onAvatarError = () => { avatarError.value = true; };
|
||
|
||
const vueStore = useStore();
|
||
const vueRouter = useRouter();
|
||
const vueRoute = useRoute();
|
||
|
||
const isAdmin = computed(() => vueStore.state.user.roles?.includes('admin'));
|
||
// 左侧边栏是否折叠:移动端默认折叠
|
||
const isMobile = ref(window.innerWidth <= 950);
|
||
const collapsed = ref(isMobile.value);
|
||
|
||
// ========== 刷新相关 ==========
|
||
const refreshStatus = ref('success');
|
||
const remainingSeconds = ref(30);
|
||
const refreshInterval = ref(30000); // 默认30秒
|
||
|
||
let refreshTimer = null;
|
||
let countdownTimer = null;
|
||
let lastFetchTime = Date.now();
|
||
|
||
// ========== 提醒相关 ==========
|
||
const alertMode = ref(0); // 默认不提醒
|
||
let previousTotalBadges = 0; // 记录上一次的badge总数,用于检测是否有新增
|
||
|
||
const menuBadges = ref({
|
||
//一级显示
|
||
'/order': 0, // 订单列表显示5
|
||
// 订单系统下的二级菜单
|
||
'autoorder': 0, // 订单列表显示5
|
||
'autorefund': 0, // 我的进行中显示2
|
||
'changelist': 0, // 待出票显示3
|
||
'orderlist': 0, // 紧急出票显示1
|
||
'ordermine': 0, // 退票列表显示4
|
||
'orderurgent': 0, // 改期订单显示2
|
||
'orderwaiting': 0, // 自动出票显示7
|
||
'refundlist': 0 // 自动退票显示8
|
||
});
|
||
|
||
// 处理提醒模式变化
|
||
const handleAlertModeChange = (event) => {
|
||
if (event && event.detail) {
|
||
const newMode = event.detail.mode;
|
||
if (newMode !== undefined) {
|
||
alertMode.value = newMode;
|
||
zhihuiAudio.setAlertMode(newMode);
|
||
}
|
||
}
|
||
};
|
||
|
||
// 初始化音频播放器
|
||
const initAudioPlayer = () => {
|
||
// 预加载音频文件
|
||
zhihuiAudio.preloadAudio(1, '/audio/alert1.mp3');
|
||
zhihuiAudio.preloadAudio(2, '/audio/alert2.mp3');
|
||
|
||
// 预加载自定义音频文件
|
||
zhihuiAudio.preloadCustomAudio('alert-tui', '/audio/alert-tui.mp3');
|
||
zhihuiAudio.preloadCustomAudio('alert-gai', '/audio/alert-gai.mp3');
|
||
|
||
// 从 localStorage 加载保存的提醒模式
|
||
const savedAlertMode = localStorage.getItem('alertMode');
|
||
if (savedAlertMode !== null) {
|
||
const mode = parseInt(savedAlertMode);
|
||
alertMode.value = mode;
|
||
zhihuiAudio.setAlertMode(mode);
|
||
}
|
||
};
|
||
|
||
const fetchBadgeData = async () => {
|
||
refreshStatus.value = 'processing';
|
||
try {
|
||
// 从 localStorage 获取时间范围,默认10分钟
|
||
const savedTimeRange = localStorage.getItem('timeRange');
|
||
const timeRange = savedTimeRange ? parseInt(savedTimeRange) : 10;
|
||
|
||
const {data} = await messageApi.getMenuBadges({
|
||
...menuBadges.value,
|
||
timeRange: timeRange
|
||
});
|
||
|
||
if (data) {
|
||
// 合并数据,只更新存在的字段
|
||
menuBadges.value = {
|
||
...menuBadges.value,
|
||
...data
|
||
};
|
||
|
||
// 获取最近指定时间范围的新订单数量
|
||
const orderNewCount = data.orderNewCount || 0;
|
||
const refundNewCount = data.refundNewCount || 0;
|
||
const changeNewCount = data.changeNewCount || 0;
|
||
|
||
// 判断是否需要播放音乐:
|
||
// 1. 第一次加载(previousTotalBadges === 0)且有新订单
|
||
// 2. 或者最近指定时间范围有新订单
|
||
if ((previousTotalBadges === 0 && Object.values(data).reduce((sum, value) => sum + value, 0) > 0) ||
|
||
orderNewCount > 0 || refundNewCount > 0 || changeNewCount > 0) {
|
||
// 根据不同类型的新订单播放不同的音乐
|
||
if (orderNewCount > 0) {
|
||
zhihuiAudio.playAlert();
|
||
} else if (refundNewCount > 0) {
|
||
zhihuiAudio.playCustomAudio('alert-tui');
|
||
} else if (changeNewCount > 0) {
|
||
zhihuiAudio.playCustomAudio('alert-gai');
|
||
}
|
||
}
|
||
|
||
// 更新 previousTotalBadges 为当前总数
|
||
previousTotalBadges = Object.values(menuBadges.value).reduce((sum, value) => sum + value, 0);
|
||
}
|
||
refreshStatus.value = 'success';
|
||
console.log('Badge数据已更新:', menuBadges.value);
|
||
|
||
// 触发全局刷新事件,通知所有监听该事件的组件刷新
|
||
zhihuiBus.emit('global-refresh', { timestamp: Date.now() });
|
||
|
||
} catch (error) {
|
||
console.error('获取Badge数据失败:', error);
|
||
refreshStatus.value = 'warning';
|
||
} finally {
|
||
lastFetchTime = Date.now();
|
||
calculateRemainingSeconds();
|
||
}
|
||
};
|
||
|
||
// ========== 倒计时相关 ==========
|
||
const calculateRemainingSeconds = () => {
|
||
// 如果刷新间隔为0(不刷新),直接返回0并停止倒计时
|
||
if (refreshInterval.value === 0) {
|
||
remainingSeconds.value = 0;
|
||
return 0;
|
||
}
|
||
|
||
const elapsed = Date.now() - lastFetchTime;
|
||
const remaining = Math.max(0, Math.ceil((refreshInterval.value - elapsed) / 1000));
|
||
remainingSeconds.value = remaining;
|
||
return remaining;
|
||
};
|
||
|
||
const startCountdown = () => {
|
||
// 如果刷新间隔为0(不刷新),不启动倒计时
|
||
if (refreshInterval.value === 0) {
|
||
remainingSeconds.value = 0;
|
||
return;
|
||
}
|
||
|
||
if (countdownTimer) clearInterval(countdownTimer);
|
||
|
||
calculateRemainingSeconds();
|
||
|
||
countdownTimer = setInterval(() => {
|
||
const remaining = calculateRemainingSeconds();
|
||
if (remaining <= 0) {
|
||
fetchBadgeData();
|
||
}
|
||
}, 1000);
|
||
};
|
||
|
||
const stopCountdown = () => {
|
||
if (countdownTimer) {
|
||
clearInterval(countdownTimer);
|
||
countdownTimer = null;
|
||
}
|
||
};
|
||
|
||
// ========== 自动刷新 ==========
|
||
const startAutoRefresh = () => {
|
||
// 如果刷新间隔为0(不刷新),不启动定时器
|
||
if (refreshInterval.value === 0) {
|
||
return;
|
||
}
|
||
|
||
if (refreshTimer) clearInterval(refreshTimer);
|
||
|
||
refreshTimer = setInterval(() => {
|
||
fetchBadgeData();
|
||
}, refreshInterval.value);
|
||
};
|
||
|
||
const stopAutoRefresh = () => {
|
||
if (refreshTimer) {
|
||
clearInterval(refreshTimer);
|
||
refreshTimer = null;
|
||
}
|
||
};
|
||
|
||
// ========== 监听配置中心的间隔变化 ==========
|
||
const handleRefreshIntervalChange = (event) => {
|
||
if (event && event.detail) {
|
||
const newInterval = event.detail.interval;
|
||
if (newInterval !== undefined && !isNaN(newInterval)) {
|
||
refreshInterval.value = parseInt(newInterval);
|
||
|
||
// 保存到 localStorage
|
||
localStorage.setItem('preferredRefreshInterval', refreshInterval.value.toString());
|
||
|
||
// 先停止所有定时器
|
||
stopAutoRefresh();
|
||
stopCountdown();
|
||
|
||
// 如果新间隔不为0,重新启动定时器
|
||
if (refreshInterval.value !== 0) {
|
||
// 重置最后获取时间,避免立即触发
|
||
lastFetchTime = Date.now();
|
||
startAutoRefresh();
|
||
startCountdown();
|
||
} else {
|
||
// 设置为不刷新时,清除倒计时显示
|
||
remainingSeconds.value = 0;
|
||
refreshStatus.value = 'success';
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
// ========== 初始化 ==========
|
||
const initializeRefresh = () => {
|
||
// 从localStorage加载用户偏好的刷新间隔
|
||
const savedInterval = localStorage.getItem('preferredRefreshInterval');
|
||
if (savedInterval !== null) {
|
||
refreshInterval.value = parseInt(savedInterval);
|
||
}
|
||
|
||
// 立即获取一次数据
|
||
fetchBadgeData();
|
||
|
||
// 只有在刷新间隔不为0时才启动定时器
|
||
if (refreshInterval.value !== 0) {
|
||
startAutoRefresh();
|
||
startCountdown();
|
||
} else {
|
||
// 如果是不刷新模式,倒计时显示为0
|
||
remainingSeconds.value = 0;
|
||
}
|
||
};
|
||
|
||
// 菜单相关状态数据
|
||
const menuState = reactive({
|
||
// 所有一级菜单节点
|
||
rootSubmenuKeys: [],
|
||
// 展开的父菜单节点
|
||
openedFaterMenuKeys: [],
|
||
// 当前选中的菜单项
|
||
selectedMenuKeys: []
|
||
});
|
||
|
||
// 通过从后端获取的用户可以访问的菜单信息生产前端菜单列表
|
||
const sidebarRouters = computed(mapGetters(['sidebarRouters']).sidebarRouters.bind({ $store: vueStore }));
|
||
// console.log('sidebarRouters=', JSON.stringify(sidebarRouters.value));
|
||
// 设置一级父菜单
|
||
if (sidebarRouters.value && sidebarRouters.value.length > 0) {
|
||
sidebarRouters.value.forEach((element) => {
|
||
// console.log(element.path);
|
||
menuState.rootSubmenuKeys.push(element.path);
|
||
});
|
||
}
|
||
// 将工作台菜单添加到一级菜单节点数组中
|
||
menuState.rootSubmenuKeys.push('index');
|
||
|
||
const headerInfo = reactive({ currentFatherMenuTitle: '主页', currentMenuTitle: '票务总台' });
|
||
// alert(JSON.stringify(vueRoute));
|
||
// 刷新页面后重新选中对应的菜单
|
||
if (vueRoute.path) {
|
||
let arr = vueRoute.path.split('/');
|
||
// 如果有父菜单,获取父菜单key
|
||
if (arr.length > 2) {
|
||
menuState.openedFaterMenuKeys = [];
|
||
menuState.openedFaterMenuKeys.push('/' + arr[1]);
|
||
menuState.selectedMenuKeys = [];
|
||
menuState.selectedMenuKeys.push(arr[2]);
|
||
} else {
|
||
// 如果没有父菜单
|
||
if ('/home' === vueRoute.path) {
|
||
// 如果是主页路由,则选中工作台菜单
|
||
menuState.selectedMenuKeys.push('index');
|
||
} else {
|
||
menuState.openedFaterMenuKeys = [];
|
||
menuState.selectedMenuKeys = [];
|
||
menuState.selectedMenuKeys.push(arr[1]);
|
||
}
|
||
}
|
||
|
||
// 刷新页面后,重新设置顶部的菜单名称
|
||
if (sidebarRouters.value && sidebarRouters.value.length > 0) {
|
||
sidebarRouters.value.forEach((element) => {
|
||
if (element.path === '/' + arr[1]) {
|
||
headerInfo.currentFatherMenuTitle = element.meta.title;
|
||
if (element.children) {
|
||
element.children.forEach((child) => {
|
||
if (child.path === arr[2]) {
|
||
headerInfo.currentMenuTitle = child.meta.title;
|
||
}
|
||
});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// 展开菜单时,收起其他已展开的菜单
|
||
const expandMenu = (openedFaterMenuKeys) => {
|
||
const latestOpenKey = openedFaterMenuKeys.find((key) => menuState.openedFaterMenuKeys.indexOf(key) === -1);
|
||
if (menuState.rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
|
||
menuState.openedFaterMenuKeys = openedFaterMenuKeys;
|
||
} else {
|
||
menuState.openedFaterMenuKeys = latestOpenKey ? [latestOpenKey] : [];
|
||
}
|
||
};
|
||
|
||
// 点击菜单项:跳转到对应的路由
|
||
const clickMenuItem = (fatherMenuName, fatherMenuPath, fatherTitle, menuName, menuPath, menuTitle, menuComponent) => {
|
||
if (menuPath === 'index') {
|
||
//如果点击的是工作台菜单,则关闭其他已展开的父菜单
|
||
menuState.openedFaterMenuKeys = [];
|
||
}
|
||
headerInfo.currentFatherMenuTitle = fatherTitle;
|
||
headerInfo.currentMenuTitle = menuTitle;
|
||
|
||
let routePathStr = fatherMenuPath + '/' + menuPath;
|
||
console.log('点击菜单后请求路由=' + routePathStr);
|
||
vueRouter.push({ path: routePathStr });
|
||
};
|
||
// 点击菜单项:跳转到对应的路由
|
||
const clickThreeLevelMenuItem = (greatFatherMenuPath, fatherMenuName, fatherMenuPath, fatherTitle, menuName, menuPath, menuTitle, menuComponent) => {
|
||
if (menuPath === 'index') {
|
||
//如果点击的是工作台菜单,则关闭其他已展开的父菜单
|
||
menuState.openedFaterMenuKeys = [];
|
||
}
|
||
headerInfo.currentFatherMenuTitle = fatherTitle;
|
||
headerInfo.currentMenuTitle = menuTitle;
|
||
|
||
let routePathStr = greatFatherMenuPath + '/' + fatherMenuPath + '/' + menuPath;
|
||
console.log('点击菜单后请求路由=' + routePathStr);
|
||
vueRouter.push({ path: routePathStr });
|
||
};
|
||
|
||
// 退出登录
|
||
const logout = () => {
|
||
vueStore.dispatch('LogOut_Action')
|
||
.then(() => {
|
||
// 登陆成功,跳转到主页
|
||
vueRouter.push({ path: '/' }).catch(() => {});
|
||
}).catch(() => {});
|
||
};
|
||
|
||
const router = useRouter();
|
||
// 进入个人中心
|
||
const enterPersonalCenter = () => {
|
||
router.push('/user/profile');
|
||
};
|
||
const settingDrawer = ref();
|
||
const changeTheme = () => {
|
||
if (settingDrawer.value) {
|
||
settingDrawer.value.showDrawer();
|
||
}
|
||
};
|
||
|
||
const airLoading = ref(false);
|
||
const airVisible = ref(false);
|
||
const airList = ref([]);
|
||
const airQuery = {
|
||
pageNum: 1,
|
||
pageSize: PAGE_SIZE_OPTIONS[8]
|
||
};
|
||
const toggleAirModal = async () => {
|
||
airVisible.value = !airVisible.value;
|
||
if (airVisible.value) {
|
||
try {
|
||
airLoading.value = true;
|
||
const airData = await airlineApi.queryPage(airQuery);
|
||
airList.value = airData.data.list;
|
||
console.log(JSON.stringify(airList.value));
|
||
} finally {
|
||
airLoading.value = false;
|
||
}
|
||
}
|
||
};
|
||
|
||
// 监听路由变化,同步菜单选中状态
|
||
watch(() => vueRoute.path, (newPath) => {
|
||
if (newPath) {
|
||
let arr = newPath.split('/');
|
||
if (arr.length > 2) {
|
||
menuState.openedFaterMenuKeys = ['/' + arr[1]];
|
||
menuState.selectedMenuKeys = [arr[2]];
|
||
} else {
|
||
if ('/home' === newPath) {
|
||
menuState.selectedMenuKeys = ['index'];
|
||
} else {
|
||
menuState.openedFaterMenuKeys = [];
|
||
menuState.selectedMenuKeys = arr.length > 1 ? [arr[1]] : [];
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// ========== 生命周期 ==========
|
||
onMounted(() => {
|
||
initializeRefresh();
|
||
initAudioPlayer(); // 初始化音频播放器
|
||
|
||
// 添加事件监听
|
||
window.addEventListener('refresh-interval-change', handleRefreshIntervalChange);
|
||
window.addEventListener('alert-mode-change', handleAlertModeChange);
|
||
window.addEventListener('resize', handleResize);
|
||
});
|
||
|
||
function handleResize() {
|
||
const wasMobile = isMobile.value;
|
||
isMobile.value = window.innerWidth <= 950;
|
||
if (isMobile.value) {
|
||
collapsed.value = true;
|
||
} else if (wasMobile) {
|
||
// 从移动端变为桌面端,自动展开菜单
|
||
collapsed.value = false;
|
||
}
|
||
}
|
||
|
||
onUnmounted(() => {
|
||
stopAutoRefresh();
|
||
stopCountdown();
|
||
|
||
// 移除事件监听
|
||
window.removeEventListener('refresh-interval-change', handleRefreshIntervalChange);
|
||
window.removeEventListener('alert-mode-change', handleAlertModeChange);
|
||
window.removeEventListener('resize', handleResize);
|
||
|
||
// 停止音频播放
|
||
zhihuiAudio.stop();
|
||
});
|
||
</script>
|
||
|
||
<style lang="less">
|
||
// 对antd的tabs和menu部分样式进行了重写
|
||
@import '@/assets/styles/homePage/homePageStyles.less';
|
||
// 引入默认的全局样式
|
||
@import '@/assets/styles/globalStyles.less';
|
||
@import '@/assets/styles/skins.less';
|
||
|
||
.navbar-col-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
flex: 0 0 auto;
|
||
padding-right: 120px;
|
||
}
|
||
|
||
@media (max-width: 850px) {
|
||
.navbar-col-actions {
|
||
display: none ;
|
||
overflow: hidden !important;
|
||
}
|
||
}
|
||
|
||
|
||
.air-modal .ant-modal-title {
|
||
text-align: center;
|
||
font-size: 20px;
|
||
}
|
||
|
||
.air-modal .air-list span {
|
||
padding: 3px;
|
||
margin: 1px;
|
||
vertical-align: middle;
|
||
line-height: 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
.air-modal .air-list span:hover {
|
||
/* border: solid 1px #62a3ef;*/
|
||
}
|
||
|
||
.airline-col {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.airline-link {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
width: 100%;
|
||
padding: 5px 8px;
|
||
border: 1px solid transparent;
|
||
border-radius: 4px;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.airline-link:hover {
|
||
border-color: #62a3ef;
|
||
background-color: #fff;
|
||
}
|
||
|
||
.airline-info {
|
||
margin-right: 8px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.airline-logo {
|
||
height: 25px;
|
||
width: 80px;
|
||
max-width: 70px;
|
||
margin: 3px;
|
||
object-fit: contain;
|
||
}
|
||
|
||
.avatarIcon {
|
||
height: 33px;
|
||
width: 33px;
|
||
border-radius: 50%;
|
||
background-color: #a8e4e4;
|
||
opacity: 0.9;
|
||
border: 1px solid #abcbe7;margin-right:5px;
|
||
}
|
||
|
||
|
||
.audio-player {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
progress[value] {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 100%;
|
||
height: 20px;
|
||
background-color: #ddd;
|
||
}
|
||
|
||
progress[value]::-webkit-progress-bar {
|
||
background-color: #ddd;
|
||
}
|
||
|
||
progress[value]::-webkit-progress-value {
|
||
background-color: blue;
|
||
}
|
||
|
||
// 倒计时数字样式
|
||
.countdown-number {
|
||
margin-left: 4px;
|
||
padding: 2px 8px; /* 上下2px,左右8px,保持高度适中 */
|
||
//border-radius: 12px; /* 圆角 */
|
||
font-size: 13px; /* 字体大小 */
|
||
line-height: 1; /* 行高设为1,防止撑高 */
|
||
display: inline-block; /* 内联块元素 */
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
// 3秒内紧急倒计时样式
|
||
.countdown-urgent {
|
||
background: rgb(246 90 95 );
|
||
color: #fff;
|
||
font-size: 15px;
|
||
font-weight: bold;
|
||
animation: urgent-pulse 0.5s infinite;
|
||
padding: 4px 14px;
|
||
line-height: 1;
|
||
display: inline-block;
|
||
border-radius: 12px;
|
||
box-shadow: 0 0 8px rgba(255, 77, 79, 0.6);
|
||
}
|
||
|
||
@keyframes urgent-pulse {
|
||
0% {
|
||
transform: scale(1);
|
||
opacity: 1;
|
||
box-shadow: 0 0 8px rgba(255, 77, 79, 0.6);
|
||
}
|
||
50% {
|
||
transform: scale(1.15);
|
||
opacity: 0.85;
|
||
box-shadow: 0 0 16px rgba(255, 77, 79, 0.9);
|
||
}
|
||
100% {
|
||
transform: scale(1);
|
||
opacity: 1;
|
||
box-shadow: 0 0 8px rgba(255, 77, 79, 0.6);
|
||
}
|
||
}
|
||
.ant-menu-title-content {
|
||
.ant-badge {
|
||
margin-left: 5px !important;
|
||
}
|
||
}
|
||
|
||
.main-area {
|
||
display: flex;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
min-height: 0;
|
||
}
|
||
.content-area {
|
||
flex: 1;
|
||
padding: 0px;
|
||
min-height: 280px;
|
||
overflow: scroll;
|
||
background: white;
|
||
}
|
||
.content-area::-webkit-scrollbar {
|
||
width: 6px;
|
||
height: 6px;
|
||
}
|
||
.content-area::-webkit-scrollbar-thumb {
|
||
background: #fbfbfb;
|
||
border-radius: 3px;
|
||
}
|
||
.content-area::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
.content-area::-webkit-scrollbar-corner {
|
||
background: transparent;
|
||
}
|
||
.card-container {
|
||
padding: 10px;
|
||
}
|
||
// 响应式导航栏样式
|
||
.navbar-row {
|
||
width: 100%;
|
||
height: 100%;
|
||
margin: 0 !important;
|
||
display: flex !important;
|
||
justify-content: space-between !important;
|
||
align-items: center !important;
|
||
flex-direction: row !important;
|
||
}
|
||
|
||
.navbar-title {
|
||
color: rgb(253, 253, 253);
|
||
padding-left: 20px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
flex: 0 0 auto !important;
|
||
}
|
||
|
||
.navbar-col-menu {
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
flex: 0 0 auto !important;
|
||
}
|
||
|
||
.navbar-right-group {
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
flex-direction: row !important;
|
||
justify-content: flex-end !important;
|
||
flex: 0 0 auto !important;
|
||
flex-wrap: nowrap !important;
|
||
}
|
||
|
||
.navbar-col-countdown {
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
flex: 0 0 auto !important;
|
||
padding-right: 20px !important;
|
||
}
|
||
|
||
|
||
|
||
.navbar-col-user {
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
flex: 0 0 auto !important;
|
||
padding-right: 15px;
|
||
}
|
||
|
||
.user-dropdown {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.user-dropdown-link {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.user-name {
|
||
max-width: 80px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
|
||
|
||
// 小屏幕适配
|
||
@media (max-width: 1200px) {
|
||
.navbar-title {
|
||
font-size: 14px;
|
||
padding-left: 10px;
|
||
}
|
||
|
||
.navbar-buttons .ant-btn {
|
||
padding-left: 8px !important;
|
||
padding-right: 8px !important;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 992px) {
|
||
.navbar-title {
|
||
font-size: 13px;
|
||
padding-left: 8px;
|
||
}
|
||
|
||
.navbar-buttons .ant-btn-sm {
|
||
padding: 0 4px;
|
||
font-size: 12px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.navbar-title {
|
||
font-size: 12px;
|
||
padding-left: 5px;
|
||
}
|
||
|
||
.navbar-buttons .ant-btn {
|
||
margin-left: 2px !important;
|
||
margin-right: 2px !important;
|
||
}
|
||
|
||
.countdown-number {
|
||
font-size: 11px;
|
||
padding: 2px 4px;
|
||
}
|
||
|
||
.user-name {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 576px) {
|
||
.navbar-col-countdown {
|
||
padding-right: 0 !important;
|
||
}
|
||
|
||
.navbar-buttons .ant-btn {
|
||
margin-left: 1px !important;
|
||
margin-right: 1px !important;
|
||
font-size: 11px;
|
||
}
|
||
}
|
||
</style> |