index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. <template>
  2. <div class="alarm-history-container">
  3. <!-- 系统头部 -->
  4. <s-header :name="$t('alarmHistory.alarmHistory')" :noback="false" />
  5. <!-- 主要内容区域 -->
  6. <div class="alarm-history-content">
  7. <!-- 顶部筛选和统计区(优化版) -->
  8. <div class="stats-filter-card">
  9. <!-- 统计信息区 -->
  10. <div class="stats-area">
  11. <div class="stats-icon-box">
  12. <van-icon name="bell" size="20" color="#ff6d59" />
  13. </div>
  14. <div class="stats-content">
  15. <div class="stats-value">
  16. <span class="highlight-number"
  17. >{{ alarmHistoryTotal
  18. }}{{ $t("alarmHistory.recordsTotal") }}</span
  19. >
  20. </div>
  21. </div>
  22. </div>
  23. <!-- 搜索功能区 -->
  24. <div class="filter-area">
  25. <div class="filter-btn" @click="searchClick">
  26. <van-icon name="search" size="18" color="#ffffff" />
  27. <span class="btn-text">{{ $t("alarmHistory.search") }}</span>
  28. </div>
  29. </div>
  30. </div>
  31. <!-- 报警历史列表 -->
  32. <van-list
  33. v-model:loading="loading"
  34. v-model:error="error"
  35. :error-text="$t('alarmHistory.requestFailed')"
  36. :finished="finished"
  37. :finished-text="$t('alarmHistory.noMore')"
  38. offset="100"
  39. :immediate-check="false"
  40. @load="onLoad"
  41. >
  42. <!-- 列表项(卡片式设计) -->
  43. <div class="history-list">
  44. <div
  45. v-for="(item, index) in alarmHistoryList"
  46. :key="index"
  47. class="history-card"
  48. :class="[getAlarmLevelClass(item.level)]"
  49. >
  50. <div class="card-header flex-row justify-between align-center">
  51. <div class="card-title">
  52. {{ showDeviceName(item.equipmentId) }}
  53. </div>
  54. <div class="card-time">
  55. {{ showDateTime(item.occurrenceTime) }}
  56. </div>
  57. </div>
  58. <!-- 卡片内容 -->
  59. <div class="card-body">
  60. <div class="info-row">
  61. <div class="info-label">
  62. {{ $t("alarmHistory.affiliatedMerchants") }}:
  63. </div>
  64. <div class="info-value">{{ item.adminUserName }}</div>
  65. </div>
  66. <div class="info-row">
  67. <div class="info-label">
  68. {{ $t("alarmHistory.equipmentNo") }}:
  69. </div>
  70. <div class="info-value badge-id">{{ item.clientId }}</div>
  71. </div>
  72. <div class="info-row">
  73. <div class="info-label">
  74. {{ $t("alarmHistory.alarmContent") }}:
  75. </div>
  76. <div class="info-value alarm-content">
  77. {{ item.alarmContent }}
  78. </div>
  79. </div>
  80. </div>
  81. <!-- 报警级别标识 -->
  82. <div class="alarm-level">
  83. {{ $t(`alarmHistory.level${item.level}`) }}
  84. </div>
  85. </div>
  86. </div>
  87. </van-list>
  88. </div>
  89. <!-- 历史搜索组件 -->
  90. <history-search ref="searchRef" @search="search($event)" />
  91. </div>
  92. </template>
  93. <script>
  94. import { onMounted, reactive, ref } from "vue";
  95. import sHeader from "@/components/SimpleHeader";
  96. import historySearch from "./historySearch.vue";
  97. import { getAlarmHistoryList, getEquipmentList } from "@/service/alarmHistory";
  98. import { getLoginUser } from "@/common/js/utils";
  99. import dateUtil from "@/utils/dateUtil";
  100. import { showFailToast } from "vant";
  101. import { useI18n } from "vue-i18n";
  102. import { styleUrl } from "../../common/js/utils";
  103. export default {
  104. components: { sHeader, historySearch },
  105. setup() {
  106. const { t } = useI18n();
  107. const user = getLoginUser();
  108. const searchRef = ref(null);
  109. const loading = ref(false); // 加载状态
  110. const error = ref(false); // 错误状态
  111. const finished = ref(false); // 结束翻页状态
  112. const alarmHistoryList = ref([]); // 列表集合
  113. const alarmHistoryTotal = ref(0); // 列表总数
  114. let searchParams = reactive({
  115. adminId: "", // 当前登录账户的id
  116. clientId: "", // 设备编号
  117. current: 1, // 当前页
  118. size: 20, // 页大小
  119. name: "", // 机器名称
  120. // startDate: null, // 开始时间
  121. // endDate: null, // 结束时间
  122. });
  123. const equipmentSourceList = ref([]); // 列表集合
  124. // 初始化页面获取列表
  125. onMounted(async () => {
  126. // 加载样式
  127. styleUrl("alarmHistory");
  128. getDeviceListFun();
  129. if (user) {
  130. searchParams.adminId = user.id;
  131. searchGetList();
  132. }
  133. });
  134. // 获取设备列表
  135. const getDeviceListFun = async () => {
  136. const { data } = await getEquipmentList({ adminId: user.id });
  137. if (data.code === "00000") {
  138. equipmentSourceList.value = data.data;
  139. equipmentSourceList.value.unshift({
  140. name: t("alarmHistory.allDevices"),
  141. id: null,
  142. });
  143. console.log(equipmentSourceList);
  144. }
  145. };
  146. // 搜索弹窗触发搜索
  147. const search = (data) => {
  148. searchParams = Object.assign(searchParams, data);
  149. searchGetList();
  150. };
  151. // 查询列表
  152. const searchGetList = () => {
  153. alarmHistoryList.value = [];
  154. searchParams.current = 1;
  155. getList();
  156. };
  157. // 滚动加载
  158. const onLoad = () => {
  159. if (!finished.value) {
  160. searchParams.current = searchParams.current + 1;
  161. getList();
  162. }
  163. };
  164. // 获取报警列表数据
  165. const getList = async () => {
  166. const { data } = await getAlarmHistoryList(
  167. Object.assign({}, searchParams)
  168. );
  169. if (data.code === "00000") {
  170. // 列表值叠加
  171. alarmHistoryList.value = alarmHistoryList.value.concat(
  172. data.data.records
  173. );
  174. alarmHistoryTotal.value = data.data.total;
  175. if (alarmHistoryList.value.length === data.data.total) {
  176. finished.value = true;
  177. }
  178. loading.value = false;
  179. } else {
  180. showFailToast(data.message);
  181. }
  182. };
  183. const showDateTime = (date) => {
  184. if (!date) {
  185. return "";
  186. }
  187. const currentDate = new Date(
  188. dateUtil.formateDate(new Date(date), "yyyy/MM/dd hh:mm:ss")
  189. );
  190. return dateUtil.timeZoneDate(currentDate);
  191. };
  192. const showDeviceName = (id) => {
  193. const device = equipmentSourceList.value.filter((v) => v.id === id);
  194. if (device && device.length > 0) {
  195. return device[0].name;
  196. }
  197. return id;
  198. };
  199. // 搜索点击
  200. const searchClick = () => {
  201. searchRef.value.showSearch();
  202. };
  203. const getAlarmLevelClass = (level) => {
  204. return `level-${level}`;
  205. };
  206. return {
  207. search,
  208. searchRef,
  209. searchClick,
  210. loading,
  211. error,
  212. finished,
  213. onLoad,
  214. alarmHistoryList,
  215. alarmHistoryTotal,
  216. showDateTime,
  217. showDeviceName,
  218. getAlarmLevelClass,
  219. };
  220. },
  221. };
  222. </script>
  223. <style lang="less" scoped>
  224. @theme-color: #4d6add;
  225. @light-theme: lighten(@theme-color, 20%);
  226. @text-color: #333;
  227. @light-text: #666;
  228. @border-color: #e6e8f0;
  229. @card-shadow: 0 4px 16px rgba(77, 106, 221, 0.1);
  230. @highlight-shadow: 0 4px 20px rgba(77, 106, 221, 0.25);
  231. .alarm-history-container {
  232. display: flex;
  233. flex-direction: column;
  234. height: 100vh;
  235. background-color: #f5f8ff;
  236. }
  237. .alarm-history-content {
  238. flex: 1;
  239. padding: 16px;
  240. overflow-y: auto;
  241. }
  242. /* 顶部筛选统计卡片(优化版) */
  243. .stats-filter-card {
  244. display: flex;
  245. justify-content: space-between;
  246. background: linear-gradient(135deg, @theme-color, #6878eb);
  247. border-radius: 16px;
  248. padding: 16px;
  249. box-shadow: @highlight-shadow;
  250. margin-bottom: 16px;
  251. color: white;
  252. }
  253. .stats-area {
  254. display: flex;
  255. align-items: center;
  256. flex: 1;
  257. .stats-icon-box {
  258. width: 35px;
  259. height: 35px;
  260. background: rgba(255, 255, 255, 0.2);
  261. border-radius: 12px;
  262. display: flex;
  263. align-items: center;
  264. justify-content: center;
  265. margin-right: 10px;
  266. }
  267. .stats-content {
  268. .stats-title {
  269. font-size: 14px;
  270. opacity: 0.9;
  271. margin-bottom: 4px;
  272. }
  273. .stats-value {
  274. font-size: 20px;
  275. font-weight: bold;
  276. display: flex;
  277. align-items: flex-end;
  278. .highlight-number {
  279. font-size: 18px;
  280. margin: 0 4px;
  281. }
  282. }
  283. }
  284. }
  285. .filter-area {
  286. display: flex;
  287. flex-direction: column;
  288. justify-content: center;
  289. }
  290. .filter-btn {
  291. display: flex;
  292. align-items: center;
  293. background: rgba(255, 255, 255, 0.2);
  294. border-radius: 18px;
  295. padding: 6px 15px 6px 12px;
  296. font-size: 13px;
  297. cursor: pointer;
  298. transition: all 0.3s;
  299. margin-bottom: 10px;
  300. &:last-child {
  301. margin-bottom: 0;
  302. }
  303. .van-icon {
  304. margin-right: 8px;
  305. }
  306. &:hover {
  307. background: rgba(255, 255, 255, 0.3);
  308. transform: translateY(-2px);
  309. }
  310. &:active {
  311. transform: translateY(0);
  312. }
  313. }
  314. /* 报警历史卡片 */
  315. .history-list {
  316. display: grid;
  317. grid-template-columns: 1fr;
  318. gap: 16px;
  319. }
  320. .history-card {
  321. background-color: #fff;
  322. border-radius: 12px;
  323. padding: 16px;
  324. box-shadow: @card-shadow;
  325. position: relative;
  326. overflow: hidden;
  327. &.level-5 {
  328. border-left: 4px solid #ff5252;
  329. .alarm-level {
  330. background: linear-gradient(to right, #ff5252, #ff7b7b);
  331. }
  332. }
  333. &.level-4 {
  334. border-left: 4px solid #ff9800;
  335. .alarm-level {
  336. background: linear-gradient(to right, #ff9800, #ffb74d);
  337. }
  338. }
  339. &.level-3 {
  340. border-left: 4px solid #F1C40F;
  341. .alarm-level {
  342. background: linear-gradient(to right, #F1C40F, #e7d488);
  343. }
  344. }
  345. &.level-2 {
  346. border-left: 4px solid #4caf50;
  347. .alarm-level {
  348. background: linear-gradient(to right, #4caf50, #81c784);
  349. }
  350. }
  351. &.level-1 {
  352. border-left: 4px solid #3498DB;
  353. .alarm-level {
  354. background: linear-gradient(to right, #3498DB, #84b3d3);
  355. }
  356. }
  357. }
  358. .card-header {
  359. padding-bottom: 12px;
  360. border-bottom: 1px solid @border-color;
  361. margin-bottom: 12px;
  362. }
  363. .card-title {
  364. font-weight: bold;
  365. font-size: 16px;
  366. color: @text-color;
  367. max-width: 70%;
  368. }
  369. .card-time {
  370. font-size: 12px;
  371. color: @light-text;
  372. }
  373. .info-row {
  374. display: flex;
  375. margin-bottom: 10px;
  376. font-size: 14px;
  377. &:last-child {
  378. margin-bottom: 0;
  379. }
  380. }
  381. .info-label {
  382. color: @light-text;
  383. min-width: 100px;
  384. }
  385. .info-value {
  386. color: @text-color;
  387. flex: 1;
  388. }
  389. .badge-id {
  390. background-color: lighten(@theme-color, 45%);
  391. color: darken(@theme-color, 10%);
  392. border-radius: 4px;
  393. display: inline-block;
  394. font-weight: bold;
  395. font-size: 13px;
  396. }
  397. .alarm-content {
  398. font-weight: 500;
  399. }
  400. .alarm-level {
  401. position: absolute;
  402. top: 0;
  403. right: 0;
  404. background: @theme-color;
  405. color: white;
  406. font-size: 12px;
  407. padding: 4px 12px;
  408. border-bottom-left-radius: 8px;
  409. font-weight: 500;
  410. }
  411. .empty-state {
  412. text-align: center;
  413. padding: 40px 20px;
  414. .empty-icon {
  415. font-size: 60px;
  416. color: #d1d8f0;
  417. margin-bottom: 15px;
  418. }
  419. p {
  420. font-size: 16px;
  421. color: @light-text;
  422. }
  423. }
  424. /* 筛选菜单弹出框 */
  425. .filter-popup {
  426. max-height: 80vh;
  427. padding: 20px;
  428. .popup-header {
  429. display: flex;
  430. justify-content: space-between;
  431. align-items: center;
  432. padding-bottom: 15px;
  433. border-bottom: 1px solid #f0f0f0;
  434. margin-bottom: 20px;
  435. h3 {
  436. font-size: 18px;
  437. font-weight: 600;
  438. color: @text-color;
  439. margin: 0;
  440. }
  441. .van-icon {
  442. font-size: 20px;
  443. color: #999;
  444. }
  445. }
  446. .filter-group {
  447. margin-bottom: 20px;
  448. h4 {
  449. font-size: 15px;
  450. color: @text-color;
  451. font-weight: 500;
  452. margin-bottom: 12px;
  453. }
  454. .van-checkbox {
  455. margin-bottom: 12px;
  456. &:last-child {
  457. margin-bottom: 0;
  458. }
  459. }
  460. }
  461. .date-range {
  462. display: flex;
  463. align-items: center;
  464. .van-field {
  465. flex: 1;
  466. background: #f9f9f9;
  467. border-radius: 8px;
  468. padding: 10px 15px;
  469. }
  470. .date-divider {
  471. margin: 0 10px;
  472. color: #999;
  473. }
  474. }
  475. .filter-actions {
  476. display: flex;
  477. justify-content: space-between;
  478. margin-top: 20px;
  479. .van-button {
  480. flex: 1;
  481. border-radius: 20px;
  482. height: 44px;
  483. font-weight: 500;
  484. &:first-child {
  485. margin-right: 15px;
  486. }
  487. }
  488. }
  489. }
  490. /* 动画效果 */
  491. @keyframes fadeIn {
  492. from {
  493. opacity: 0;
  494. transform: translateY(10px);
  495. }
  496. to {
  497. opacity: 1;
  498. transform: translateY(0);
  499. }
  500. }
  501. .history-card {
  502. animation: fadeIn 0.3s ease-out;
  503. animation-fill-mode: both;
  504. @delay: 0.1s;
  505. @delay-increment: 0.05s;
  506. &:nth-child(1) {
  507. animation-delay: 0.05s;
  508. }
  509. &:nth-child(2) {
  510. animation-delay: 0.1s;
  511. }
  512. &:nth-child(3) {
  513. animation-delay: 0.15s;
  514. }
  515. &:nth-child(4) {
  516. animation-delay: 0.2s;
  517. }
  518. &:nth-child(5) {
  519. animation-delay: 0.25s;
  520. }
  521. &:nth-child(6) {
  522. animation-delay: 0.3s;
  523. }
  524. }
  525. /* 响应式布局 */
  526. @media (max-width: 480px) {
  527. .stats-filter-card {
  528. flex-direction: column;
  529. gap: 12px;
  530. }
  531. .filter-area {
  532. flex-direction: row;
  533. justify-content: flex-end;
  534. gap: 10px;
  535. .filter-btn {
  536. margin-bottom: 0;
  537. }
  538. }
  539. .time-filter-group .van-button {
  540. padding: 0 16px;
  541. height: 30px;
  542. }
  543. }
  544. </style>