index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. <template>
  2. <div class="sa-notice" @click="showNotice">
  3. <div class="sa-float-icon-wrap">
  4. <el-badge is-dot :hidden="!noticeUnreadNum">
  5. <sa-svg class="sa-float-icon" name="sa-Notification" size="24" />
  6. </el-badge>
  7. </div>
  8. <el-drawer
  9. v-model="isShowNotice"
  10. direction="rtl"
  11. class="chat-drawer"
  12. modal-class="chat-drawer-overlay"
  13. :show-close="false"
  14. >
  15. <template #header>
  16. <ul v-if="noticeTypeList.length" class="chat-status-wrap sa-flex sa-col-center">
  17. <li class="bg" :style="bgStyle"></li>
  18. <!-- {{noticeTypeList}} -->
  19. <li
  20. v-for="item in noticeTypeList"
  21. :key="item"
  22. class="chat-status"
  23. :class="{ 'is-active': item.value == noticeTypeList[0].value }"
  24. @click="onNoticeMenu(item.value)"
  25. >
  26. <el-badge is-dot :hidden="!item.unread_num">
  27. {{ item.label }}
  28. </el-badge>
  29. </li>
  30. <!-- <li
  31. class="chat-status"
  32. :class="{ 'is-active': sessionType == 'ing' }"
  33. @click="changeSessionType('ing')"
  34. >
  35. 会话中
  36. </li>
  37. <el-badge is-dot :hidden="!hasWaiting">
  38. <li
  39. class="chat-status"
  40. :class="{ 'is-active': sessionType == 'waiting' }"
  41. @click="changeSessionType('waiting')"
  42. >
  43. 排队中
  44. </li>
  45. </el-badge>
  46. <li
  47. class="chat-status"
  48. :class="{ 'is-active': sessionType == 'history' }"
  49. @click="changeSessionType('history')"
  50. >
  51. 历史记录
  52. </li> -->
  53. </ul>
  54. <el-icon class="close" @click="isShowNotice = false">
  55. <CircleCloseFilled />
  56. </el-icon>
  57. <!-- <el-menu
  58. v-if="noticeTypeList.length"
  59. class="el-menu-demo sa-flex sa-row-around sa-col-center"
  60. mode="horizontal"
  61. :default-active="noticeTypeList[0].value"
  62. @select="onNoticeMenu"
  63. >
  64. <template v-for="(item, index) in noticeTypeList" :key="index">
  65. <el-menu-item :index="item.value">
  66. <div class="sa-flex sa-col-center sa-row-center">
  67. <span>{{ item.label }}</span>
  68. <span class="sa-m-l-4" v-if="item.unread_num"
  69. >({{ item.unread_num }})</span
  70. >
  71. </div>
  72. </el-menu-item>
  73. </template>
  74. </el-menu> -->
  75. </template>
  76. <div class="chat-content sa-flex sa-flex-1">
  77. <el-scrollbar id="noticeScroll" class="notice-scroll" height="100%">
  78. <div
  79. class="notice-list sa-flex sa-row-around"
  80. :class="isEmpty(item.read_time) ? '' : 'notice-list-read'"
  81. v-for="(item, index) in noticeList"
  82. :key="item.id"
  83. @click="readNotice(item, index)"
  84. >
  85. <div class="notice-list-content">
  86. <div class="wrapper">
  87. <input :id="`exp-${index}`" class="exp" type="checkbox" />
  88. <div class="text">
  89. <label class="btn" :for="`exp-${index}`"></label>
  90. 【{{ item.data.message_title }}】{{ item.data.message_text }}
  91. </div>
  92. </div>
  93. <span class="notice-time">{{ item.create_time }}</span>
  94. </div>
  95. </div>
  96. <!-- 置空页 -->
  97. <el-empty v-show="!noticeList.length">
  98. <template #image>
  99. <sa-svg className="empty-svg" name="sa-neirongweikong" size="150"></sa-svg>
  100. </template>
  101. <template #description>
  102. <div class="empty-description"> 您的工作效率很高哦, 现在还没有新的待办消息! </div>
  103. </template>
  104. </el-empty>
  105. <!-- 加载状态 -->
  106. <button
  107. class="loadmore-btn sa-reset-button"
  108. v-show="noticeList.length"
  109. @click="onLoadMore"
  110. >
  111. {{ loadingMap[noticeListparmas.loadStatus].title
  112. }}<i
  113. class="loadmore-icon sa-m-l-6"
  114. :class="loadingMap[noticeListparmas.loadStatus].icon"
  115. ></i>
  116. </button>
  117. </el-scrollbar>
  118. </div>
  119. <div class="chat-footer">
  120. <div class="empty" @click="clearNotice()">清空 已读消息</div>
  121. </div>
  122. </el-drawer>
  123. </div>
  124. </template>
  125. <script>
  126. /**
  127. * Botice 站内信
  128. *
  129. */
  130. import { reactive, ref, defineComponent, computed } from 'vue';
  131. import { isEmpty } from 'lodash';
  132. export default defineComponent({
  133. name: 'SaNotice',
  134. components: {},
  135. setup() {
  136. // 默认数据
  137. const noticeTypeList = ref([
  138. { label: '系统消息', value: 'system', unread_num: 3 },
  139. { label: '订单消息', value: 'order', unread_num: 1 },
  140. // { label: '营销消息', value: 'marketing', unread_num: 0 },
  141. ]);
  142. const noticeList = ref([
  143. {
  144. id: 1,
  145. data: {
  146. message_title: '系统通知',
  147. message_text: '欢迎使用商城管理系统,您有新的订单需要处理。',
  148. },
  149. create_time: '2024-01-15 10:30:00',
  150. read_time: null,
  151. },
  152. {
  153. id: 2,
  154. data: {
  155. message_title: '订单提醒',
  156. message_text: '您有一个新的订单等待确认,订单号:#202401150001',
  157. },
  158. create_time: '2024-01-15 09:15:00',
  159. read_time: '2024-01-15 10:00:00',
  160. },
  161. {
  162. id: 3,
  163. data: {
  164. message_title: '库存警告',
  165. message_text: '商品"BOLON经典太阳镜"库存不足,当前库存:5件',
  166. },
  167. create_time: '2024-01-15 08:45:00',
  168. read_time: null,
  169. },
  170. ]);
  171. const noticeUnreadNum = computed(() => {
  172. return noticeList.value.filter((item) => !item.read_time).length;
  173. });
  174. // loading
  175. const noticeLoading = ref(false);
  176. // 查看更多
  177. const onLoadMore = () => {
  178. if (noticeListparmas.current_page < noticeListparmas.last_page) {
  179. noticeListparmas.current_page += 1;
  180. // getNoticeList(); // 暂时注释掉API调用
  181. }
  182. };
  183. const isShowNotice = ref(false);
  184. // 获取站内信列表
  185. const noticeListparmas = reactive({
  186. current_page: 1,
  187. last_page: 1,
  188. loadStatus: 'nomore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
  189. });
  190. // 暂时注释掉API调用
  191. // const getNoticeList = async () => {
  192. // noticeLoading.value = true;
  193. // noticeListparmas.loadStatus = 'loading';
  194. // // API调用逻辑
  195. // noticeLoading.value = false;
  196. // };
  197. // 获取站内信分类
  198. const curNoticeType = ref('system');
  199. // 暂时注释掉API调用
  200. // const getNoticeType = async () => {
  201. // // API调用逻辑
  202. // };
  203. // 显示站内信
  204. const showNotice = () => {
  205. isShowNotice.value = true;
  206. noticeListparmas.current_page = 1;
  207. // 暂时不调用API
  208. // getNoticeList();
  209. };
  210. const bgStyle = computed(() => {
  211. let index = noticeTypeList.value.findIndex((item) => item.value == curNoticeType.value);
  212. return {
  213. left: 2 + index * 118 + 'px',
  214. };
  215. });
  216. // 切换站内信
  217. const onNoticeMenu = (e) => {
  218. curNoticeType.value = e;
  219. noticeListparmas.current_page = 1;
  220. // 暂时不调用API
  221. // getNoticeList();
  222. };
  223. function desc(item) {
  224. let str = `【${item.data.message_title}】${item.data.message_text}`;
  225. if (str.length > 42) {
  226. item.show = true;
  227. }
  228. return item.hidden ? str : str.substring(0, 42) + '...';
  229. }
  230. async function readNotice(item) {
  231. // 模拟标记为已读
  232. item.read_time = new Date().toLocaleString();
  233. // 暂时注释掉API调用
  234. // const { data } = await noticeApi.read(item.id);
  235. }
  236. const clearNotice = async () => {
  237. // 清空已读消息
  238. noticeList.value = noticeList.value.filter((item) => !item.read_time);
  239. noticeListparmas.current_page = 1;
  240. // 暂时注释掉API调用
  241. // const { data } = await noticeApi.clear();
  242. };
  243. // 暂时注释掉初始化API调用
  244. // onMounted(() => {
  245. // getNoticeType();
  246. // });
  247. return {
  248. isEmpty,
  249. noticeTypeList,
  250. noticeUnreadNum,
  251. noticeList,
  252. onLoadMore,
  253. noticeLoading,
  254. curNoticeType,
  255. onNoticeMenu,
  256. noticeListparmas,
  257. loadingMap: {
  258. loadmore: {
  259. title: '查看更多',
  260. icon: 'el-icon-d-arrow-left',
  261. },
  262. nomore: {
  263. title: '没有更多了',
  264. icon: '',
  265. },
  266. loading: {
  267. title: '加载中... ',
  268. icon: 'el-icon-loading',
  269. },
  270. },
  271. showNotice,
  272. isShowNotice,
  273. bgStyle,
  274. desc,
  275. readNotice,
  276. clearNotice,
  277. };
  278. },
  279. });
  280. </script>
  281. <style>
  282. .notice-popper {
  283. padding: 0 !important;
  284. }
  285. </style>
  286. <style lang="scss" scoped>
  287. .sa-notice {
  288. :deep() {
  289. .el-badge__content.is-dot {
  290. height: 6px;
  291. width: 6px;
  292. }
  293. }
  294. }
  295. .empty-svg {
  296. color: var(--sa-subfont);
  297. }
  298. .empty-description {
  299. width: 200px;
  300. font-size: 16px;
  301. color: var(--sa-subfont);
  302. }
  303. // 按钮
  304. .tools-btn {
  305. border: none;
  306. height: 32px;
  307. min-height: 32px;
  308. width: 32px;
  309. padding: 0;
  310. border-radius: 50%;
  311. &:active {
  312. background-color: var(--t-btn-hover);
  313. .tool-icon {
  314. font-size: 20px;
  315. color: var(--sa-background-assist);
  316. }
  317. }
  318. &:hover {
  319. background-color: var(--t-btn-hover);
  320. .tool-icon {
  321. font-size: 20px;
  322. color: var(--sa-background-assist);
  323. }
  324. }
  325. &:focus {
  326. background-color: var(--t-btn-hover);
  327. .tool-icon {
  328. font-size: 20px;
  329. color: var(--sa-background-assist);
  330. }
  331. }
  332. .tool-icon {
  333. font-size: 20px;
  334. color: var(--sa-font);
  335. }
  336. :deep(.el-badge__content) {
  337. background-color: #ed5b56 !important;
  338. }
  339. }
  340. // 站内信
  341. .notice-wrap {
  342. .notice-header {
  343. height: 60px;
  344. border-bottom: 1px solid var(--sa-border);
  345. }
  346. .el-menu {
  347. height: 100%;
  348. }
  349. .el-menu--horizontal {
  350. border-bottom: none;
  351. }
  352. .el-menu:not(.el-menu--collapse) .el-menu-item,
  353. .el-menu:not(.el-menu--collapse) .el-sub-menu__title {
  354. height: 100%;
  355. border-radius: 0;
  356. margin-bottom: 0;
  357. }
  358. // .notice-list {
  359. // .notice-list-content {
  360. // width: 250px;
  361. // border-bottom: 1px solid var(--sa-space);
  362. // .notice-text {
  363. // font-family: PingFang SC;
  364. // font-size: 14px;
  365. // color: var(--sa-font);
  366. // line-height: 20px;
  367. // }
  368. // .notice-time {
  369. // font-family: PingFang SC;
  370. // font-size: 12px;
  371. // color: var(--sa-subfont);
  372. // }
  373. // }
  374. // }
  375. }
  376. // 加载更多
  377. .loadmore-btn {
  378. width: 100%;
  379. height: 40px;
  380. font-size: 12px;
  381. color: var(--sa-subfont);
  382. .loadmore-icon {
  383. transform: rotate(-90deg);
  384. }
  385. }
  386. .notice-scroll {
  387. width: 100%;
  388. // height: 600px;
  389. }
  390. @media all and (min-width: 0) and (max-width: 500px) {
  391. // .notice-scroll {
  392. // height: 520px;
  393. // }
  394. }
  395. :deep() {
  396. .chat-drawer-overlay {
  397. background-color: transparent;
  398. }
  399. .chat-drawer {
  400. width: 360px !important;
  401. height: unset;
  402. border-radius: 8px;
  403. top: 48px;
  404. bottom: 30px;
  405. @media only screen and (max-width: 768px) {
  406. width: 100% !important;
  407. border-radius: 0;
  408. top: 0;
  409. bottom: 0;
  410. }
  411. .el-drawer__header {
  412. height: 56px;
  413. padding: 0 12px;
  414. background: var(--t-btn-hover);
  415. color: var(--sa-background-assist);
  416. margin-bottom: 0;
  417. justify-content: center;
  418. position: relative;
  419. .close {
  420. position: absolute;
  421. top: 20px;
  422. right: 12px;
  423. font-size: 16px;
  424. &:hover {
  425. color: var(--t-color-primary);
  426. }
  427. @media only screen and (max-width: 768px) {
  428. top: 18px;
  429. font-size: 20px;
  430. }
  431. }
  432. }
  433. .el-drawer__body {
  434. padding: 0;
  435. overflow: hidden;
  436. display: flex;
  437. flex-direction: column;
  438. .chat-content {
  439. flex: 1;
  440. height: calc(100% - 40px);
  441. .notice-list {
  442. .notice-list-content {
  443. // width: 250px;
  444. width: 100%;
  445. padding: 16px 16px;
  446. border-bottom: 1px solid var(--sa-space);
  447. &:hover {
  448. background: var(--t-bg-hover);
  449. .text::after {
  450. content: '';
  451. width: 999vw;
  452. height: 999vw;
  453. position: absolute;
  454. box-shadow: inset calc(100px - 999vw) calc(21px - 999vw) 0 0 var(--t-bg-hover);
  455. margin-left: -100px;
  456. }
  457. }
  458. .notice-text {
  459. font-family: PingFang SC;
  460. font-size: 14px;
  461. color: var(--sa-font);
  462. line-height: 20px;
  463. position: relative;
  464. // padding-right: 26px;
  465. .notice-hidden {
  466. // position: absolute;
  467. // top: 20px;
  468. // right: 0;
  469. color: var(--t-color-primary);
  470. font-size: 12px;
  471. }
  472. }
  473. .notice-time {
  474. font-family: PingFang SC;
  475. font-size: 12px;
  476. color: var(--sa-subfont);
  477. }
  478. .wrapper {
  479. display: flex;
  480. width: inherit;
  481. overflow: hidden;
  482. }
  483. .text {
  484. font-size: 14px;
  485. color: var(--sa-font);
  486. overflow: hidden;
  487. text-overflow: ellipsis;
  488. text-align: justify;
  489. position: relative;
  490. line-height: 1.5;
  491. max-height: 3em;
  492. transition: 0.3s max-height;
  493. white-space: normal;
  494. word-break: break-all;
  495. }
  496. .text::before {
  497. content: '';
  498. height: calc(100% - 21px);
  499. float: right;
  500. }
  501. .text::after {
  502. content: '';
  503. width: 999vw;
  504. height: 999vw;
  505. position: absolute;
  506. box-shadow: inset calc(100px - 999vw) calc(21px - 999vw) 0 0 #fff;
  507. margin-left: -100px;
  508. }
  509. .btn {
  510. position: relative;
  511. float: right;
  512. clear: both;
  513. margin-left: 20px;
  514. font-size: 12px;
  515. padding: 0 8px;
  516. // background: #3f51b5;
  517. line-height: 1.5;
  518. border-radius: 4px;
  519. color: var(--t-color-primary);
  520. cursor: pointer;
  521. /* margin-top: -30px; */
  522. }
  523. .btn::after {
  524. content: '展开';
  525. }
  526. .exp {
  527. display: none;
  528. }
  529. .exp:checked + .text {
  530. max-height: none;
  531. }
  532. .exp:checked + .text::after {
  533. visibility: hidden;
  534. }
  535. .exp:checked + .text .btn::before {
  536. visibility: hidden;
  537. }
  538. .exp:checked + .text .btn::after {
  539. content: '收起';
  540. }
  541. .btn::before {
  542. content: '...';
  543. position: absolute;
  544. left: -5px;
  545. color: var(--sa-font);
  546. transform: translateX(-100%);
  547. }
  548. }
  549. &.notice-list-read {
  550. .text,
  551. .notice-time {
  552. color: var(--sa-place);
  553. }
  554. .btn::before {
  555. color: var(--sa-place);
  556. }
  557. }
  558. }
  559. }
  560. // 输入框
  561. .chat-footer {
  562. border-top: 1px solid var(--sa-space);
  563. background-color: var(--sa-table-striped);
  564. height: 40px;
  565. display: flex;
  566. align-items: center;
  567. flex-shrink: 0;
  568. .empty,
  569. .more {
  570. flex: 1;
  571. text-align: center;
  572. font-size: 12px;
  573. font-weight: 400;
  574. color: var(--sa-font);
  575. }
  576. .line {
  577. width: 1px;
  578. height: 20px;
  579. background: var(--sa-border);
  580. }
  581. }
  582. }
  583. }
  584. }
  585. .chat-status-wrap {
  586. width: fit-content !important;
  587. flex: unset !important;
  588. height: 36px;
  589. padding: 0 2px;
  590. border-radius: 8px;
  591. background: var(--t-bg-focus);
  592. display: flex;
  593. align-items: center;
  594. justify-content: center;
  595. position: relative;
  596. .bg {
  597. position: absolute;
  598. top: 2px;
  599. left: 2px;
  600. width: 118px;
  601. height: 32px;
  602. background: var(--sa-background-assist);
  603. border-radius: 6px;
  604. transition: all 0.2s;
  605. }
  606. .chat-status {
  607. min-width: 118px;
  608. height: 32px;
  609. display: flex;
  610. align-items: center;
  611. justify-content: center;
  612. position: relative;
  613. z-index: 1;
  614. padding: 0 7px;
  615. font-size: 14px;
  616. color: var(--t-color-primary);
  617. cursor: pointer;
  618. }
  619. }
  620. </style>