Navbar.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. <template>
  2. <div class="navbar">
  3. <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container"
  4. @toggleClick="toggleSideBar"/>
  5. <breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container"/>
  6. <top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container"/>
  7. <div class="right-menu">
  8. <template v-if="appStore.device !== 'mobile'">
  9. <!-- 使用 el-badge 包裹图标 -->
  10. <el-tooltip :content="noticeContent" effect="dark" placement="bottom">
  11. <div class="right-menu-item" style="height: 70%">
  12. <el-badge :value="noticeCount" :max="99" :offset="[0, 10]" :hidden="noticeCount == 0 ? true : false"
  13. class="badge-container">
  14. <el-icon @click="showDetail" style="cursor: pointer;">
  15. <BellFilled/>
  16. </el-icon>
  17. </el-badge>
  18. </div>
  19. </el-tooltip>
  20. <!-- 弹框组件 -->
  21. <el-dialog v-model="dialogVisible2" title="详情" width="30%" :before-close="closeDetailDialog">
  22. <el-row :gutter="20">
  23. <el-col :span="24">
  24. <div class="dialog-item">
  25. <span class="label">标题:</span>
  26. <el-input v-model="noticeTitle" readonly placeholder="请输入公告标题" style="width: 90%;"></el-input>
  27. </div>
  28. </el-col>
  29. <el-col :span="24">
  30. <div class="dialog-item">
  31. <span class="label">类型:</span>
  32. <el-select v-model="noticeType" disabled placeholder="请选择公告类型" style="width: 90%;">
  33. <el-option
  34. v-for="dict in sys_notice_type"
  35. :key="dict.value"
  36. :label="dict.label"
  37. :value="dict.value"
  38. ></el-option>
  39. </el-select>
  40. </div>
  41. </el-col>
  42. <el-col :span="24">
  43. <div class="dialog-item">
  44. <span class="label">内容:</span>
  45. <editor v-model="noticeContentV" readonly disabled :min-height="192" style="width: 100%;"></editor>
  46. </div>
  47. </el-col>
  48. </el-row>
  49. <template #footer>
  50. <div class="dialog-footer">
  51. <el-button type="primary" @click="readInfo2">已 读</el-button>
  52. </div>
  53. </template>
  54. </el-dialog>
  55. <el-dialog v-model="dialogVisible" title="通知" width="50%" :before-close="closeListDialog">
  56. <el-table v-loading="loading" :data="noticeList" :default-sort="defaultSort" @sort-change="handleSortChange">
  57. <el-table-column type="selection" width="55" align="center"/>
  58. <!-- <el-table-column label="序号" align="center" prop="notice_id" width="100" />-->
  59. <el-table-column
  60. label="公告标题"
  61. align="center"
  62. prop="notice_title"
  63. :show-overflow-tooltip="true"
  64. />
  65. <el-table-column label="公告类型" align="center" prop="notice_type" width="100">
  66. <template #default="scope">
  67. <dict-tag :options="sys_notice_type" :value="scope.row.notice_type"/>
  68. </template>
  69. </el-table-column>
  70. <!-- <el-table-column label="状态" align="center" prop="status" width="100">
  71. <template #default="scope">
  72. <dict-tag :options="sys_notice_status" :value="scope.row.status" />
  73. </template>
  74. </el-table-column>-->
  75. <el-table-column label="创建者" align="center" prop="create_by" width="100"/>
  76. <el-table-column label="创建时间" align="center" prop="create_time" width="100" sortable="custom">
  77. <template #default="scope">
  78. <span>{{ parseTime(scope.row.create_time, '{y}-{m}-{d}') }}</span>
  79. </template>
  80. </el-table-column>
  81. <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180px">
  82. <template #default="scope">
  83. <el-button link type="primary" icon="View" @click="handleUpdate(scope.row)" v-hasPermi="['system:notice:query']">查看</el-button>
  84. <el-button link type="primary" icon="Check" @click="readInfo(scope.row)" >已读</el-button>
  85. </template>
  86. </el-table-column>
  87. </el-table>
  88. </el-dialog>
  89. <header-search id="header-search" class="right-menu-item"/>
  90. <screenfull id="screenfull" class="right-menu-item hover-effect"/>
  91. <el-tooltip content="主题模式" effect="dark" placement="bottom">
  92. <div class="right-menu-item hover-effect theme-switch-wrapper" @click="toggleTheme">
  93. <svg-icon v-if="settingsStore.isDark" icon-class="sunny"/>
  94. <svg-icon v-if="!settingsStore.isDark" icon-class="moon"/>
  95. </div>
  96. </el-tooltip>
  97. <el-tooltip content="布局大小" effect="dark" placement="bottom">
  98. <size-select id="size-select" class="right-menu-item hover-effect"/>
  99. </el-tooltip>
  100. </template>
  101. <div class="avatar-container">
  102. <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
  103. <div class="avatar-wrapper">
  104. <img :src="userStore.avatar" class="user-avatar"/>
  105. <el-icon>
  106. <caret-bottom/>
  107. </el-icon>
  108. </div>
  109. <template #dropdown>
  110. <el-dropdown-menu>
  111. <router-link to="/user/profile">
  112. <el-dropdown-item>个人中心</el-dropdown-item>
  113. </router-link>
  114. <el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
  115. <span>布局设置</span>
  116. </el-dropdown-item>
  117. <el-dropdown-item divided command="logout">
  118. <span>退出登录</span>
  119. </el-dropdown-item>
  120. </el-dropdown-menu>
  121. </template>
  122. </el-dropdown>
  123. </div>
  124. </div>
  125. </div>
  126. </template>
  127. <script setup>
  128. import {ElMessageBox} from 'element-plus'
  129. import Breadcrumb from '@/components/Breadcrumb'
  130. import TopNav from '@/components/TopNav'
  131. import Hamburger from '@/components/Hamburger'
  132. import Screenfull from '@/components/Screenfull'
  133. import SizeSelect from '@/components/SizeSelect'
  134. import HeaderSearch from '@/components/HeaderSearch'
  135. import pmGit from '@/components/pm/Git'
  136. import pmDoc from '@/components/pm/Doc'
  137. import {listNoticeRead} from "@/api/system/menu";
  138. import {getNotice, show} from "@/api/system/notice";
  139. import useAppStore from '@/store/modules/app'
  140. import useUserStore from '@/store/modules/user'
  141. import useSettingsStore from '@/store/modules/settings'
  142. import { h } from 'vue';
  143. const {proxy} = getCurrentInstance();
  144. const {sys_notice_status, sys_notice_type} = proxy.useDict("sys_notice_status", "sys_notice_type");
  145. import {connectToWebSocket} from "@/layout/components/websocket";
  146. import {ElNotification} from 'element-plus'
  147. const announcements = ref([])
  148. connectToWebSocket((newAnnounce) => {
  149. announcements.value.unshift(newAnnounce)
  150. if (newAnnounce) {
  151. // 显示右侧提示信息
  152. // ElNotification({
  153. // title: '新公告',
  154. // message: '您有新的公告,请注意查收',
  155. // type: 'warning',
  156. // duration: 5000,
  157. // position: 'top-right'
  158. // });
  159. debugger
  160. if (newAnnounce !== "del" && newAnnounce !== "update"){ // 如果删除公告则不提示信息
  161. txt2("新公告", "您有新的公告,请注意查收",'warning',3000);
  162. }
  163. getlistNotice()
  164. }
  165. })
  166. function txt2(title,msg,type,time) {
  167. ElNotification({
  168. title: title,
  169. message: h('div', { style: 'display: flex; align-items: center;' }, [
  170. msg,
  171. h('a', {
  172. style: 'margin-left: 5px; color: #409eff; text-decoration: underline;',
  173. onClick: () => showDetail()
  174. }, '查看')
  175. ]),
  176. type: type,
  177. duration: time,
  178. position: 'top-right'
  179. });
  180. }
  181. const getNoticeTypeLabel = (type) => {
  182. const dict = sys_notice_type.value.find(item => item.value === type)
  183. return dict ? dict.label : type
  184. }
  185. const markAsRead = (noticeId) => {
  186. show({noticeId}).then(response => {
  187. getlistNotice()
  188. })
  189. }
  190. function showDetail() {
  191. //getlistNotice()
  192. dialogVisible.value = true
  193. }
  194. const appStore = useAppStore()
  195. const userStore = useUserStore()
  196. const settingsStore = useSettingsStore()
  197. const noticeList = ref([]);
  198. const loading = ref(true);
  199. // 定义响应式变量
  200. const noticeContentV = ref('暂无消息'); // 通知内容
  201. const noticeContent = ref(''); // 通知内容
  202. const noticeCount = ref(0); // 通知数量
  203. const intervalId = ref(null); // 定时器 ID
  204. const notice_id = ref(null); // 通知 ID
  205. const dialogVisible = ref(false); // 控制弹框显示/隐藏
  206. const dialogVisible2 = ref(false); // 控制弹框显示/隐藏
  207. const noticeTitle = ref(''); // 通知标题
  208. const noticeType = ref(''); // 通知类型
  209. const uniqueKey = ref(0); // 新增的唯一键属性
  210. const defaultSort = ref({prop: 'create_time', order: 'descending'}) // 默认排序
  211. function toggleSideBar() {
  212. appStore.toggleSideBar()
  213. }
  214. function handleCommand(command) {
  215. switch (command) {
  216. case "setLayout":
  217. setLayout();
  218. break;
  219. case "logout":
  220. logout();
  221. break;
  222. default:
  223. break;
  224. }
  225. }
  226. function logout() {
  227. ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
  228. confirmButtonText: '确定',
  229. cancelButtonText: '取消',
  230. type: 'warning'
  231. }).then(() => {
  232. userStore.logOut().then(() => {
  233. location.href = '/index';
  234. })
  235. }).catch(() => {
  236. });
  237. }
  238. // function close() {
  239. // dialogVisible.value = false;
  240. // show({ noticeId: notice_id.value }).then(response => {
  241. // getlistNotice();
  242. // })
  243. // }
  244. function readInfo(row) {
  245. // dialogVisible.value = false; // 关闭公告列表弹框
  246. // dialogVisible2.value = false; // 同时关闭公告详情弹框
  247. show({noticeId: row.notice_id}).then(response => {
  248. getlistNotice();
  249. });
  250. txt("已读公告信息", "您已成功读取公告信息",'success',3000);
  251. }
  252. function readInfo2() {
  253. //dialogVisible.value = false; // 关闭公告列表弹框
  254. dialogVisible2.value = false; // 同时关闭公告详情弹框
  255. show({noticeId: notice_id.value}).then(response => {
  256. getlistNotice();
  257. });
  258. txt("已读公告信息", "您已成功读取公告信息",'success',3000);
  259. }
  260. function closeListDialog() {
  261. dialogVisible.value = false;
  262. }
  263. function closeDetailDialog() {
  264. dialogVisible2.value = false;
  265. }
  266. const queryParams = ref({
  267. pageNum: 1,
  268. pageSize: 10000,
  269. orderByColumn: 'create_time',
  270. isAsc: 'desc'
  271. });
  272. getlistNotice()
  273. function getlistNotice() {
  274. loading.value = true;
  275. listNoticeRead(queryParams.value).then((response) => {
  276. // 更新唯一键以强制重新渲染
  277. noticeCount.value = response.data.length;
  278. loading.value = false;
  279. noticeList.value = response.data;
  280. noticeContent.value = "您有" + noticeCount.value + "条未读的信息 (点击铃铛查看消息)";
  281. });
  282. }
  283. function handleSortChange({column, prop, order}) {
  284. queryParams.value.orderByColumn = prop;
  285. queryParams.value.isAsc = order === 'ascending' ? 'asc' : 'desc';
  286. getlistNotice()
  287. }
  288. function handleUpdate(row) {
  289. const noticeId = row.notice_id || ids.value;
  290. getNotice(noticeId).then(response => {
  291. //form.value = response.data;
  292. dialogVisible2.value = true;
  293. noticeContentV.value = response.data.noticeContent;
  294. noticeTitle.value = response.data.noticeTitle;
  295. noticeType.value = response.data.noticeType;
  296. notice_id.value = response.data.noticeId;
  297. });
  298. }
  299. const emits = defineEmits(['setLayout'])
  300. function setLayout() {
  301. emits('setLayout');
  302. }
  303. function toggleTheme() {
  304. settingsStore.toggleTheme()
  305. }
  306. function txt(title,msg,type,time) {
  307. ElNotification({
  308. title: title,
  309. message: msg,
  310. type: type,
  311. duration: time,
  312. position: 'top-right'
  313. });
  314. }
  315. </script>
  316. <style lang='scss' scoped>
  317. .navbar {
  318. height: 50px;
  319. overflow: hidden;
  320. position: relative;
  321. background: var(--navbar-bg);
  322. box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  323. .hamburger-container {
  324. line-height: 46px;
  325. height: 100%;
  326. float: left;
  327. cursor: pointer;
  328. transition: background 0.3s;
  329. -webkit-tap-highlight-color: transparent;
  330. &:hover {
  331. background: rgba(0, 0, 0, 0.025);
  332. }
  333. }
  334. .breadcrumb-container {
  335. float: left;
  336. }
  337. .topmenu-container {
  338. position: absolute;
  339. left: 50px;
  340. }
  341. .errLog-container {
  342. display: inline-block;
  343. vertical-align: top;
  344. }
  345. .right-menu {
  346. float: right;
  347. height: 100%;
  348. line-height: 50px;
  349. display: flex;
  350. &:focus {
  351. outline: none;
  352. }
  353. .right-menu-item {
  354. display: inline-block;
  355. padding: 0 8px;
  356. height: 100%;
  357. font-size: 18px;
  358. color: var(--navbar-text);
  359. vertical-align: text-bottom;
  360. &.hover-effect {
  361. cursor: pointer;
  362. transition: background 0.3s;
  363. &:hover {
  364. background: rgba(0, 0, 0, 0.025);
  365. }
  366. }
  367. &.theme-switch-wrapper {
  368. display: flex;
  369. align-items: center;
  370. svg {
  371. transition: transform 0.3s;
  372. &:hover {
  373. transform: scale(1.15);
  374. }
  375. }
  376. }
  377. }
  378. .avatar-container {
  379. margin-right: 40px;
  380. .avatar-wrapper {
  381. margin-top: 5px;
  382. position: relative;
  383. .user-avatar {
  384. cursor: pointer;
  385. width: 40px;
  386. height: 40px;
  387. border-radius: 10px;
  388. }
  389. i {
  390. cursor: pointer;
  391. position: absolute;
  392. right: -20px;
  393. top: 25px;
  394. font-size: 12px;
  395. }
  396. }
  397. }
  398. }
  399. }
  400. </style>