| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946 |
- <template>
- <header
- class="fixed w-full top-0 z-100 bg-white border-b border-[#f0f2f5] shadow-sm"
- >
- <div
- class="max-w-[1200px] mx-auto flex items-center justify-between px-5 h-15"
- >
- <div class="flex items-center gap-2 cursor-pointer" @click="goHome">
- <img :src="logo" alt="logo" class="w-9 h-9 rounded-md" />
- <span
- style="text-wrap-mode: nowrap"
- class="text-[#02409b] text-[20px] font-bold hidden lg:flex"
- >KERUI DEEPOIL</span
- >
- <span class="text-[#828182] text-[14px]" style="text-wrap-mode: nowrap"
- >智慧经营平台</span
- >
- </div>
- <nav class="hidden lg:flex flex-1 mx-4 ml-80 text-sm">
- <ul class="flex items-center gap-6 text-[#303133] text-md">
- <!-- 首页 -->
- <li>
- <a
- class="cursor-pointer px-3 py-1.5 rounded-md transition-all duration-300"
- :class="
- router.currentRoute.value.path === '/'
- ? 'nav-item-active'
- : 'nav-item-default'
- "
- style="text-wrap-mode: nowrap"
- @click="router.push('/')"
- >
- 首页
- </a>
- </li>
- <!-- 流程门户 -->
- <li>
- <a
- style="text-wrap-mode: nowrap"
- class="cursor-pointer px-3 py-1.5 rounded-md transition-all duration-300"
- :class="
- router.currentRoute.value.path.startsWith('/flow')
- ? 'nav-item-active'
- : 'nav-item-default'
- "
- @click="goFlow"
- >
- 流程门户
- </a>
- </li>
- <!-- 驾驶舱门户 -->
- <li v-hasPermi="['portal:dashboard:view']">
- <a
- class="cursor-pointer px-3 py-1.5 rounded-md transition-all duration-300"
- :class="
- router.currentRoute.value.path.startsWith('/drive')
- ? 'nav-item-active'
- : 'nav-item-default'
- "
- @click="goDrive"
- style="text-wrap-mode: nowrap"
- >
- 驾驶舱门户
- </a>
- </li>
- <!-- 报表门户 (假设路径为 /report,请根据实际路由调整) -->
- <li>
- <a
- class="cursor-pointer px-3 py-1.5 rounded-md transition-all duration-300"
- :class="
- router.currentRoute.value.path.startsWith('/report')
- ? 'nav-item-active'
- : 'nav-item-default'
- "
- style="text-wrap-mode: nowrap"
- >
- 报表门户
- </a>
- </li>
- </ul>
- </nav>
- <div
- style="border-left: 1px solid #a5bbdb"
- class="hidden lg:flex items-center gap-3 h-[40%] pl-4"
- >
- <!-- 消息中心 -->
- <el-dropdown trigger="click" placement="bottom-end">
- <div class="flex items-center gap-2 cursor-pointer pr-6 pt-1">
- <el-badge
- :value="unreadMessageCount + oaUnreadCount"
- class="item"
- v-if="hasUnreadMessages || oaHasUnreadCount"
- >
- <Icon
- icon="mdi:bell"
- class="w-5 h-5 text-[#507698] hover:text-[#409EFF]"
- />
- </el-badge>
- <Icon
- v-else
- icon="mdi:bell"
- class="w-5 h-5 text-[#507698] hover:text-[#409EFF]"
- />
- </div>
- <template #dropdown>
- <el-dropdown-menu class="notification-dropdown">
- <div class="notification-tabs pl-2">
- <el-tabs v-model="activeTab" class="demo-tabs">
- <el-tab-pane label="OA" name="tasks">
- <template #label>
- <span class="custom-tabs-label">
- <span>OA</span>
- <el-badge
- :value="oaUnreadCount"
- class="item ml-1"
- v-if="oaHasUnreadCount"
- ></el-badge>
- </span>
- </template>
- <div class="tab-content">
- <div>
- <span
- v-if="oaHasUnreadCount"
- class="cursor-pointer text-blue-500"
- @click="oaMarkAllAsRead"
- >全部标为已读</span
- >
- </div>
- <!-- OA消息 -->
- <div
- class="task-item"
- v-for="(task, index) in oaMessagesList"
- :key="index"
- >
- <div class="task-info">
- <p class="task-title">
- <span
- v-if="task.status === '0'"
- class="inline-block h-2 w-2 bg-[#f56c6c] rounded-full"
- ></span>
- {{ task.title }}
- </p>
- <p class="message-desc">
- <span>{{ task.oaCreateTime }}</span>
- </p>
- </div>
- </div>
- <div v-if="!oaMessagesList.length" class="no-tasks">
- 暂无新消息
- </div>
- </div>
- </el-tab-pane>
- <el-tab-pane label="CRM" name="messages">
- <template #label>
- <span class="custom-tabs-label">
- <span>CRM</span>
- <el-badge
- :value="unreadMessageCount"
- class="item ml-1"
- v-if="hasUnreadMessages"
- ></el-badge>
- </span>
- </template>
- <div class="tab-content">
- <!-- 消息中心内容 -->
- <div>
- <span
- v-if="hasUnreadMessages"
- class="cursor-pointer text-blue-500"
- @click="markAllAsRead"
- >全部标为已读</span
- >
- </div>
- <div
- class="message-item"
- v-for="(item, index) in messages"
- :key="index"
- >
- <div class="message-icon"></div>
- <div class="message-text">
- <!-- 未读就显示小红点 -->
- <p class="message-title flex items-center gap-5">
- <span
- v-if="item.status === '0'"
- class="w-2 h-2 bg-[#f56c6c] rounded-full"
- ></span
- >{{ item.contentMajor }}
- </p>
- <p class="message-desc">
- {{ timestampToDateTime(item.createTime) }}
- </p>
- </div>
- </div>
- <div v-if="!messages.length" class="no-messages">
- 暂无新消息
- </div>
- </div>
- </el-tab-pane>
- </el-tabs>
- </div>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- <template v-if="isLoggedIn">
- <el-dropdown @command="onUserCommand" trigger="click">
- <span class="flex items-center gap-2 cursor-pointer pr-2">
- <div class="avatar-wrapper">
- <img
- :src="userAvatar || person"
- alt="avatar"
- class="w-8 h-8 rounded-full avatar-image"
- />
- </div>
- <span
- class="text-sm text-[#303133]"
- style="text-wrap-mode: nowrap"
- >{{ userName }}</span
- >
- </span>
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item command="profile">
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="18"
- height="18"
- viewBox="0 0 16 16"
- >
- <g fill="none">
- <path
- fill="url(#SVG3BqCJdyi)"
- d="M11.5 8A1.5 1.5 0 0 1 13 9.5v.5c0 1.971-1.86 4-5 4s-5-2.029-5-4v-.5A1.5 1.5 0 0 1 4.5 8z"
- />
- <path
- fill="url(#SVGfKhxtenh)"
- d="M11.5 8A1.5 1.5 0 0 1 13 9.5v.5c0 1.971-1.86 4-5 4s-5-2.029-5-4v-.5A1.5 1.5 0 0 1 4.5 8z"
- />
- <path
- fill="url(#SVGJYCMTblH)"
- d="M8 1.5A2.75 2.75 0 1 1 8 7a2.75 2.75 0 0 1 0-5.5"
- />
- <defs>
- <linearGradient
- id="SVG3BqCJdyi"
- x1="5.378"
- x2="7.616"
- y1="8.798"
- y2="14.754"
- gradientUnits="userSpaceOnUse"
- >
- <stop offset=".125" stop-color="#9c6cfe" />
- <stop offset="1" stop-color="#7a41dc" />
- </linearGradient>
- <linearGradient
- id="SVGfKhxtenh"
- x1="8"
- x2="11.164"
- y1="7.286"
- y2="17.139"
- gradientUnits="userSpaceOnUse"
- >
- <stop stop-color="#885edb" stop-opacity="0" />
- <stop offset="1" stop-color="#e362f8" />
- </linearGradient>
- <linearGradient
- id="SVGJYCMTblH"
- x1="6.558"
- x2="9.361"
- y1="2.231"
- y2="6.707"
- gradientUnits="userSpaceOnUse"
- >
- <stop offset=".125" stop-color="#9c6cfe" />
- <stop offset="1" stop-color="#7a41dc" />
- </linearGradient>
- </defs>
- </g>
- </svg>
- <span class="pl-2">个人中心</span>
- </el-dropdown-item>
- <el-dropdown-item command="logout">
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="18"
- height="18"
- viewBox="0 0 24 24"
- >
- <g fill="none">
- <path
- fill="url(#SVG0pAmxd9w)"
- d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2"
- />
- <path
- fill="url(#SVGFnXqmeDt)"
- d="m15.53 8.47l-.084-.073a.75.75 0 0 0-.882-.007l-.094.08L12 10.939l-2.47-2.47l-.084-.072a.75.75 0 0 0-.882-.007l-.094.08l-.073.084a.75.75 0 0 0-.007.882l.08.094L10.939 12l-2.47 2.47l-.072.084a.75.75 0 0 0-.007.882l.08.094l.084.073a.75.75 0 0 0 .882.007l.094-.08L12 13.061l2.47 2.47l.084.072a.75.75 0 0 0 .882.007l.094-.08l.073-.084a.75.75 0 0 0 .007-.882l-.08-.094L13.061 12l2.47-2.47l.072-.084a.75.75 0 0 0 .007-.882z"
- />
- <defs>
- <linearGradient
- id="SVG0pAmxd9w"
- x1="5.125"
- x2="18.25"
- y1="3.25"
- y2="22.625"
- gradientUnits="userSpaceOnUse"
- >
- <stop stop-color="#f83f54" />
- <stop offset="1" stop-color="#ca2134" />
- </linearGradient>
- <linearGradient
- id="SVGFnXqmeDt"
- x1="8.685"
- x2="12.591"
- y1="12.332"
- y2="16.392"
- gradientUnits="userSpaceOnUse"
- >
- <stop stop-color="#fdfdfd" />
- <stop offset="1" stop-color="#fecbe6" />
- </linearGradient>
- </defs>
- </g>
- </svg>
- <span class="pl-2">退出登录</span>
- </el-dropdown-item>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- </template>
- <template v-else>
- <div
- style="text-wrap: nowrap"
- class="text-md flex items-center justify-center cursor-pointer h-full py-4"
- @click="login"
- >
- 登 录
- </div>
- </template>
- </div>
- <div class="lg:hidden">
- <el-dropdown trigger="click" placement="bottom-end">
- <div class="flex items-center gap-2 cursor-pointer pr-6 pt-1">
- <el-badge
- :value="unreadMessageCount + oaUnreadCount"
- class="item"
- v-if="hasUnreadMessages || oaHasUnreadCount"
- >
- <Icon
- icon="mdi:bell"
- class="w-5 h-5 text-gray-600 hover:text-[#409EFF]"
- />
- </el-badge>
- <Icon
- v-else
- icon="mdi:bell"
- class="w-5 h-5 text-gray-600 hover:text-[#409EFF]"
- />
- </div>
- <template #dropdown>
- <el-dropdown-menu class="notification-dropdown">
- <div class="notification-tabs pl-2">
- <el-tabs v-model="activeTab" class="demo-tabs">
- <el-tab-pane label="OA" name="tasks">
- <template #label>
- <span class="custom-tabs-label">
- <span>OA</span>
- <el-badge
- :value="oaUnreadCount"
- class="item ml-1"
- v-if="oaHasUnreadCount"
- ></el-badge>
- </span>
- </template>
- <div class="tab-content">
- <div>
- <span
- v-if="oaHasUnreadCount"
- class="cursor-pointer text-blue-500"
- @click="oaMarkAllAsRead"
- >全部标为已读</span
- >
- </div>
- <!-- OA消息 -->
- <div
- class="task-item"
- v-for="(task, index) in oaMessagesList"
- :key="index"
- >
- <div class="task-info">
- <p class="task-title">
- <span
- v-if="task.status === '0'"
- class="inline-block h-2 w-2 bg-[#f56c6c] rounded-full"
- ></span>
- {{ task.title }}
- </p>
- <p class="message-desc">
- <span>{{ task.oaCreateTime }}</span>
- </p>
- </div>
- </div>
- <div v-if="!oaMessagesList.length" class="no-tasks">
- 暂无新消息
- </div>
- </div>
- </el-tab-pane>
- <el-tab-pane label="CRM" name="messages">
- <template #label>
- <span class="custom-tabs-label">
- <span>CRM</span>
- <el-badge
- :value="unreadMessageCount"
- class="item ml-1"
- v-if="hasUnreadMessages"
- ></el-badge>
- </span>
- </template>
- <div class="tab-content">
- <!-- 消息中心内容 -->
- <div>
- <span
- v-if="hasUnreadMessages"
- class="cursor-pointer text-blue-500"
- @click="markAllAsRead"
- >全部标为已读</span
- >
- </div>
- <div
- class="message-item"
- v-for="(item, index) in messages"
- :key="index"
- >
- <div class="message-icon"></div>
- <div class="message-text">
- <!-- 未读就显示小红点 -->
- <p class="message-title flex items-center gap-5">
- <span
- v-if="item.status === '0'"
- class="w-2 h-2 bg-[#f56c6c] rounded-full"
- ></span
- >{{ item.contentMajor }}
- </p>
- <p class="message-desc">
- {{ timestampToDateTime(item.createTime) }}
- </p>
- </div>
- </div>
- <div v-if="!messages.length" class="no-messages">
- 暂无新消息
- </div>
- </div>
- </el-tab-pane>
- </el-tabs>
- </div>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- <el-button link @click="drawer = true">
- <i class="el-icon" />
- <Icon icon="fa:bars" class="icon" />
- </el-button>
- </div>
- </div>
- <el-drawer
- v-model="drawer"
- placement="right"
- size="80%"
- :with-header="false"
- >
- <div class="p-4 space-y-3">
- <ul class="flex flex-col gap-3 text-[#303133]">
- <li>
- <a
- class="block px-3 py-2 rounded-md transition-all duration-300"
- :class="
- router.currentRoute.value.path === '/'
- ? 'nav-item-active'
- : 'nav-item-default'
- "
- @click="
- router.push('/');
- drawer = false;
- "
- >
- 首页
- </a>
- </li>
- <li>
- <a
- class="block px-3 py-2 rounded-md transition-all duration-300"
- :class="
- router.currentRoute.value.path.startsWith('/flow')
- ? 'nav-item-active'
- : 'nav-item-default'
- "
- @click="goFlow"
- >
- 流程门户
- </a>
- </li>
- <li v-hasPermi="['portal:dashboard:view']">
- <a
- class="block px-3 py-2 rounded-md transition-all duration-300"
- :class="
- router.currentRoute.value.path.startsWith('/drive')
- ? 'nav-item-active'
- : 'nav-item-default'
- "
- @click="goDrive"
- >
- 驾驶舱门户
- </a>
- </li>
- <li>
- <a
- class="block px-3 py-2 rounded-md transition-all duration-300"
- :class="
- router.currentRoute.value.path.startsWith('/report')
- ? 'nav-item-active'
- : 'nav-item-default'
- "
- >
- 报表门户
- </a>
- </li>
- </ul>
- <div class="flex items-center gap-3 mt-3">
- <template v-if="isLoggedIn">
- <el-dropdown @command="onUserCommand" trigger="click">
- <span class="flex items-center gap-2 cursor-pointer pr-2">
- <div class="avatar-wrapper">
- <img
- :src="userAvatar || person"
- alt="avatar"
- class="w-8 h-8 rounded-full avatar-image"
- />
- </div>
- <span class="text-sm text-[#303133]">{{ userName }}</span>
- </span>
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item command="profile">
- <span class="pl-2">个人中心</span>
- </el-dropdown-item>
- <el-dropdown-item command="logout">
- <span class="pl-2">退出登录</span>
- </el-dropdown-item>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- </template>
- <el-button
- v-else
- type="primary"
- class="flex-1 bg-[#0050b3]!"
- @click="login"
- >登录</el-button
- >
- </div>
- </div>
- </el-drawer>
- </header>
- </template>
- <script setup lang="ts">
- import { Icon } from "@iconify/vue";
- import { ref, computed, onMounted, onBeforeUnmount } from "vue";
- import { useRouter } from "vue-router";
- import logo from "@/assets/images/logo.png";
- import person from "@/assets/images/person.png";
- import { useUserStoreWithOut } from "@/stores/useUserStore";
- const userStore = useUserStoreWithOut();
- import {
- getNotifyMessages,
- getNotifyMessageList,
- markMessageAsRead,
- getUnreadNotifyMessageCount,
- getOANotifyMessages,
- getOANotifyMessageList,
- markOAMessageAsRead,
- } from "@api/user";
- import {
- getAccessToken,
- getRefreshToken,
- removeToken,
- setToken,
- } from "@utils/auth";
- import { deleteUserCache } from "@hooks/useCache";
- import { manualLogoutKey, reloginCancelKey } from "@/config/axios/service";
- // 新增消息中心状态
- const activeTab = ref("tasks");
- const messages = ref([]);
- const isLoggedIn = computed(
- () => !!userStore.isSetUser || !!userStore.user?.id,
- );
- const userAvatar = computed(() => userStore.user?.avatar || "");
- const userName = computed(() => userStore.user?.nickname || "");
- // 是否有未读消息
- const hasUnreadMessages = computed(() => {
- return messages.value.some((msg) => msg.status === "0");
- });
- // oa是否有未读
- const oaHasUnreadCount = computed(() => {
- return oaMessagesList.value.some((msg) => msg.status === "0");
- });
- // 未读消息数量
- const unreadMessageCount = computed(() => {
- return messages.value.filter((msg) => msg.status === "0").length;
- });
- // oa未读消息数量
- const oaUnreadCount = computed(() => {
- return oaMessagesList.value.filter((msg) => msg.status === "0").length;
- });
- // oa未读
- const oaMessagesList = ref([]);
- const unreadCount = ref(0); // 未读消息数量
- const getUnreadCount = async () => {
- if (!getAccessToken()) {
- unreadCount.value = 0;
- return;
- }
- const data = await getUnreadNotifyMessageCount();
- unreadCount.value = data;
- };
- let messageTimer: ReturnType<typeof setInterval> | undefined;
- let unreadTimer: ReturnType<typeof setInterval> | undefined;
- onMounted(async () => {
- if (isLoggedIn.value) {
- getUnreadCount();
- await getNotifyMessages(userStore.getUser.username);
- const messageList = await getNotifyMessageList(userStore.getUser.username);
- messages.value = messageList.filter((msg: any) => msg.status === "0");
- // oa消息
- await getOANotifyMessages(userStore.getUser.username);
- const oaMessageList = await getOANotifyMessageList(
- userStore.getUser.username,
- );
- oaMessagesList.value = oaMessageList.filter((msg) => msg.status === "0");
- }
- messageTimer = setInterval(
- async () => {
- if (isLoggedIn.value) {
- await getNotifyMessages(userStore.getUser.username);
- const messageList = await getNotifyMessageList(
- userStore.getUser.username,
- );
- messages.value = messageList.filter((msg: any) => msg.status === "0");
- // oa消息
- await getOANotifyMessages(userStore.getUser.username);
- const oaMessageList = await getOANotifyMessageList(
- userStore.getUser.username,
- );
- oaMessagesList.value = oaMessageList.filter(
- (msg: any) => msg.status === "0",
- );
- }
- },
- 1000 * 60 * 5,
- );
- unreadTimer = setInterval(
- () => {
- if (userStore.getIsSetUser && getAccessToken()) {
- console.log("轮询刷新小红点");
- getUnreadCount();
- } else {
- unreadCount.value = 0;
- }
- },
- 1000 * 60 * 1,
- );
- });
- onBeforeUnmount(() => {
- if (messageTimer) {
- clearInterval(messageTimer);
- }
- if (unreadTimer) {
- clearInterval(unreadTimer);
- }
- });
- function timestampToDateTime(timestamp) {
- // 兼容 10位(秒) / 13位(毫秒)
- const len = String(timestamp).length;
- const date = new Date(Number(timestamp) * (len === 10 ? 1000 : 1));
- // 年
- const year = date.getFullYear();
- // 月(0~11 → +1)
- const month = String(date.getMonth() + 1).padStart(2, "0");
- // 日
- const day = String(date.getDate()).padStart(2, "0");
- // 时
- const hours = String(date.getHours()).padStart(2, "0");
- // 分
- const minutes = String(date.getMinutes()).padStart(2, "0");
- // 秒
- const seconds = String(date.getSeconds()).padStart(2, "0");
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
- }
- const router = useRouter();
- const drawer = ref(false);
- // 全部标为已读
- const markAllAsRead = async () => {
- await markMessageAsRead(userStore.getUser.username);
- // 刷新消息列表
- const messageList = await getNotifyMessageList(userStore.getUser.username);
- messages.value = messageList.filter((msg: any) => msg.status === "0");
- };
- // oa全部标为已读
- const oaMarkAllAsRead = async () => {
- await markOAMessageAsRead(userStore.getUser.username);
- // 刷新消息列表
- const messageList = await getOANotifyMessageList(userStore.getUser.username);
- oaMessagesList.value = messageList.filter((msg: any) => msg.status === "0");
- };
- const goHome = () => {
- router.push({ path: "/" });
- };
- const login = () => {
- router.push({
- path: "/login",
- });
- };
- const goFlow = () => {
- router.push({ path: "/flow" });
- };
- const goDrive = () => {
- router.push({ path: "/drive" });
- };
- const onUserCommand = async (command: string) => {
- if (command === "logout") {
- // await userStore.loginOut();
- deleteUserCache(); // 删除用户缓存
- sessionStorage.setItem(manualLogoutKey, "true");
- sessionStorage.removeItem(reloginCancelKey);
- removeToken();
- window.location.href = "/login";
- }
- };
- </script>
- <style scoped>
- .avatar-wrapper {
- position: relative;
- overflow: hidden;
- border-radius: 50%;
- }
- .avatar-wrapper::before {
- content: "";
- position: absolute;
- top: 0;
- left: -100%;
- width: 50%;
- height: 100%;
- background: linear-gradient(
- 90deg,
- rgba(255, 255, 255, 0) 0%,
- rgba(255, 255, 255, 0.8) 50%,
- rgba(255, 255, 255, 0) 100%
- );
- transform: skewX(-25deg);
- transition: none;
- z-index: 1;
- opacity: 0;
- }
- .avatar-wrapper:hover::before {
- animation: shine 0.5s ease-out;
- }
- @keyframes shine {
- 0% {
- left: -100%;
- opacity: 0;
- }
- 10% {
- opacity: 1;
- }
- 100% {
- left: 100%;
- opacity: 0;
- }
- }
- .notification-dropdown {
- width: 400px !important;
- max-height: 500px;
- overflow: hidden;
- }
- .notification-tabs .el-tabs__header {
- margin-bottom: 0;
- padding: 10px;
- background-color: #f8f9fa;
- }
- .tab-content {
- max-height: 400px;
- overflow-y: auto;
- padding: 10px;
- }
- .message-item {
- display: flex;
- align-items: flex-start;
- padding: 12px 8px;
- border-bottom: 1px solid #eee;
- }
- .message-item:last-child {
- border-bottom: none;
- }
- .message-icon {
- margin-right: 12px;
- display: flex;
- align-items: center;
- }
- .message-text {
- flex: 1;
- }
- .message-title {
- font-weight: 500;
- color: #303133;
- margin-bottom: 4px;
- }
- .message-desc {
- font-size: 13px;
- color: #909399;
- line-height: 1.4;
- margin-bottom: 4px;
- }
- .message-time {
- font-size: 12px;
- color: #c0c4cc;
- }
- .no-messages,
- .no-tasks {
- text-align: center;
- padding: 20px;
- color: #909399;
- font-style: italic;
- }
- .task-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 12px 8px;
- border-bottom: 1px solid #eee;
- }
- .task-item:last-child {
- border-bottom: none;
- }
- .task-info {
- flex: 1;
- }
- .task-title {
- font-weight: 500;
- color: #303133;
- margin-bottom: 4px;
- }
- .task-desc {
- font-size: 13px;
- color: #909399;
- line-height: 1.4;
- margin-bottom: 4px;
- }
- .task-time {
- font-size: 12px;
- color: #c0c4cc;
- }
- .nav-item-default {
- background-color: #eeeeef; /* 浅灰色背景 */
- color: #5f6f83; /* Slate 500 灰色文字 */
- border-radius: 100px;
- }
- .nav-item-default:hover {
- background-color: #e2e8f0; /* 悬停时稍深一点的灰色 */
- color: #02409b; /* 品牌蓝文字 */
- }
- /* 激活状态:深蓝色背景,白色文字 */
- .nav-item-active {
- background-color: #063e8d !important; /* 指定的深蓝色背景 */
- color: #ffffff !important; /* 白色文字 */
- font-weight: 500; /* 稍微加粗 */
- box-shadow: 0 2px 4px rgba(6, 62, 141, 0.2); /* 可选:轻微阴影 */
- border-radius: 100px;
- }
- /* 确保移动端也生效,因为上面用了 block,可能需要调整一下内边距或显示方式 */
- @media (max-width: 1024px) {
- .nav-item-active,
- .nav-item-default {
- display: block;
- width: 100%;
- text-align: left;
- }
- }
- </style>
|