Files
piaowu/ruoyi-uia/src/views/HomePage.vue
T
2026-06-14 18:42:09 +08:00

973 lines
30 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>