index.vue 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558
  1. <template>
  2. <!-- 设备列表 -->
  3. <div class="device-management">
  4. <!-- 头部 -->
  5. <s-header
  6. :name="sys?.title || $t('device.managementCenter')"
  7. :noback="true"
  8. :isFixed="false"
  9. class="management-header"
  10. />
  11. <!-- 主体内容 -->
  12. <div class="device-list-container" ref="scrollContainer"
  13. @scroll="handleScroll">
  14. <van-list
  15. v-model:loading="loading"
  16. v-model:error="error"
  17. :error-text="$t('public.requestFailed')"
  18. :finished-text="$t('public.noMore')"
  19. offset="100"
  20. :immediate-check="false"
  21. :finished="finished"
  22. @load="onLoad"
  23. >
  24. <!-- 数据概览 -->
  25. <div class="data-overview">
  26. <div class="overview-header">
  27. <h3 class="section-title">
  28. <van-icon name="setting" color="#4d6add" />
  29. {{ $t("device.dataOverview") }}
  30. </h3>
  31. <div class="action-icons">
  32. <van-popover
  33. v-model:show="showPopover"
  34. placement="left-start"
  35. theme="dark"
  36. :actions="actions"
  37. @select="selectLabel"
  38. >
  39. <template #reference>
  40. <van-icon name="bars" class="icon-filter" />
  41. </template>
  42. </van-popover>
  43. <van-icon name="search" class="icon-search" @click="searchClick" />
  44. </div>
  45. </div>
  46. <!-- 统计卡片 -->
  47. <div class="stats-cards">
  48. <div class="stat-card">
  49. <div class="card-value">{{ equipStatus.machineUseNum }}</div>
  50. <div class="card-label">{{ $t("device.totalNumberOfRuns") }}</div>
  51. </div>
  52. <div class="stat-card">
  53. <div class="card-value">{{ equipStatus.machineTotalNum }}</div>
  54. <div class="card-label">
  55. {{ $t("device.totalNumberOfEquipment") }}
  56. </div>
  57. </div>
  58. </div>
  59. </div>
  60. <!-- 设备列表 -->
  61. <div class="device-list-section">
  62. <van-tabs
  63. v-model:active="active"
  64. @click-tab="clickLabel"
  65. animated
  66. class="device-tabs"
  67. >
  68. <van-tab :title="$t('device.whole')" name="" />
  69. <van-tab :title="$t('device.powerOn')" name="ON" />
  70. <van-tab :title="$t('device.abnormal')" name="ABNORMAL" />
  71. <van-tab
  72. v-for="item in labelList"
  73. :key="item.id"
  74. :title="item.name"
  75. :name="item.id"
  76. />
  77. </van-tabs>
  78. <!-- 设备项 -->
  79. <div class="device-items">
  80. <div v-for="item in list" :key="item.id" class="device-item">
  81. <!-- 设备状态标识 -->
  82. <div
  83. class="status-indicator"
  84. :class="{
  85. active: item.eqeStatus === 1,
  86. alert: item.hasTodayAlarm,
  87. }"
  88. ></div>
  89. <!-- 设备主体信息 -->
  90. <div class="device-main">
  91. <!-- 基础信息 -->
  92. <div class="device-info">
  93. <div class="name-row">
  94. <h4 class="device-name">
  95. {{ item.name || item.clientId.slice(-6) }}
  96. </h4>
  97. <div
  98. class="alarm-indicator"
  99. v-if="item.hasTodayAlarm"
  100. :class="{ blink: showAlert }"
  101. />
  102. <div
  103. class="status-dot"
  104. v-else
  105. :class="{ active: item.eqeStatus === 1 }"
  106. />
  107. </div>
  108. <div class="device-id">
  109. {{ $t("device.machineUniqueCode") }}:{{ item.clientId }}
  110. </div>
  111. <!-- 状态信息 -->
  112. <div class="status-info">
  113. <span v-if="user.type < 1" class="lock-status">
  114. {{ $t("device.lockCondition") }}:{{
  115. item.isBlocked
  116. ? $t("device.lockState")
  117. : $t("device.unLockState")
  118. }}
  119. </span>
  120. <template
  121. v-if="item.machineType === '0' || !item.machineType"
  122. >
  123. <span
  124. class="temperature"
  125. :class="{
  126. warning: item.furnaceTm <= 100 && item.eqeStatus == 1,
  127. }"
  128. >
  129. {{ $t("device.furnaceHeadTemperature") }}:{{
  130. item.furnaceTm || 0
  131. }}{{ $t("device.degree") }}
  132. </span>
  133. <span class="temperature"
  134. >{{ $t("device.temperatureInCabinet") }}:{{
  135. item.cabinetTm || 0
  136. }}{{ $t("device.degree") }}</span
  137. >
  138. <span class="humidity"
  139. >{{ $t("device.humidityInCabinet") }}:{{
  140. item.cabinetHd || 0
  141. }}{{ $t("device.humidity") }}</span
  142. >
  143. </template>
  144. <template v-if="item.machineType === '1'">
  145. <span class="temperature">
  146. {{
  147. $t("device.cornGeneratorTemperature") +
  148. ":" +
  149. (item.cabinetTm ? item.cabinetTm : "0") +
  150. $t("device.degree")
  151. }}
  152. </span>
  153. <span v-if="item.equimentType != 'P10'" class="temperature">
  154. {{
  155. $t("device.stirringTemperature") +
  156. ":" +
  157. (item.cabinetHd ? item.cabinetHd : "0") +
  158. $t("device.degree")
  159. }}</span
  160. >
  161. <span
  162. v-if="item.furnaceTm && item.furnaceTm != '-1'"
  163. class="humidity"
  164. >
  165. {{
  166. $t("device.cupQuantity") + ":" + item.furnaceTm
  167. }}</span
  168. >
  169. <span v-if="item.furnaceSp" class="humidity">
  170. {{
  171. $t("device.bucketWeight") +
  172. ":" +
  173. item.furnaceSp +
  174. $t("device.weight")
  175. }}</span
  176. >
  177. </template>
  178. </div>
  179. </div>
  180. <!-- 扩展内容 -->
  181. <div v-if="item.checkType" class="device-detail">
  182. <!-- 设备详细信息 -->
  183. <div class="detail-section" v-if="user.type < 2">
  184. <label
  185. >{{ $t("device.affiliatedMerchants") }}:{{
  186. item.adminUserName
  187. }}</label
  188. >
  189. </div>
  190. <!-- 睡眠控制 -->
  191. <div class="detail-section machine-control">
  192. <div class="control-container">
  193. <!-- 文本说明 -->
  194. <span class="status-text">
  195. {{ $t("device.sleepState") }}:{{
  196. item.isSleep
  197. ? $t("device.sleeping")
  198. : $t("device.notSleeping")
  199. }}
  200. </span>
  201. <!-- 切换按钮 -->
  202. <van-switch
  203. :model-value="item.isSleep"
  204. size="18px"
  205. class="switch-button"
  206. @click="changeSleep(item)"
  207. />
  208. </div>
  209. </div>
  210. <!-- 睡眠控制下方添加 -->
  211. <div
  212. v-if="item.isSleep"
  213. class="detail-section sleep-description"
  214. >
  215. <div class="desc-view" v-if="!sleepDescBoxShow">
  216. <label class="desc-label"
  217. >{{ $t("device.sleepDesc") }}:</label
  218. >
  219. <span class="desc-text">{{
  220. item.sleepDesc || $t("device.SuspendBusiness")
  221. }}</span>
  222. <van-button
  223. size="small"
  224. type="primary"
  225. class="edit-btn"
  226. @click="editSleepDesc(item)"
  227. >
  228. {{ $t("device.modify") }}
  229. </van-button>
  230. </div>
  231. <div class="desc-edit" v-else>
  232. <van-field
  233. v-model="item.sleepDesc"
  234. :placeholder="$t('device.sleepDescPlace')"
  235. class="edit-field"
  236. label-width="4em"
  237. clearable
  238. >
  239. <template #button>
  240. <van-button
  241. size="small"
  242. type="primary"
  243. class="confirm-btn"
  244. @click="sleepDescChg(item.sleepDesc, item.id)"
  245. >
  246. {{ $t("device.confirm") }}
  247. </van-button>
  248. <van-button
  249. size="small"
  250. class="cancel-btn"
  251. @click="sleepDescBoxShow = false"
  252. >
  253. {{ $t("device.cancel") }}
  254. </van-button>
  255. </template>
  256. </van-field>
  257. </div>
  258. </div>
  259. <!-- 炉头状态 -->
  260. <div
  261. class="detail-section machine-control"
  262. v-if="item.machineType === '0' || !item.machineType"
  263. >
  264. <div class="control-container">
  265. <span class="status-text">
  266. {{
  267. item.machineType == "0" || item.machineType == null
  268. ? $t("device.furnHeadStatus")
  269. : $t("device.deviceStatus")
  270. }}:
  271. {{
  272. item.eqeStatus === 1
  273. ? $t("device.opened")
  274. : $t("device.closed")
  275. }}
  276. </span>
  277. <!-- 操作按钮组 -->
  278. <div class="button-group">
  279. <van-button
  280. size="small"
  281. type="primary"
  282. class="action-btn"
  283. @click="openCloseHead(item.id, 1)"
  284. >
  285. {{ $t("device.open") }}
  286. </van-button>
  287. <van-button
  288. size="small"
  289. type="danger"
  290. class="action-btn"
  291. @click="openCloseHead(item.id, 0)"
  292. >
  293. {{ $t("device.close") }}
  294. </van-button>
  295. <van-button
  296. size="small"
  297. type="warning"
  298. class="action-btn"
  299. @click="restartHead(item.id)"
  300. >
  301. {{ $t("device.restartHead") }}
  302. </van-button>
  303. </div>
  304. </div>
  305. </div>
  306. <!-- 定位: -->
  307. <div class="detail-section" v-if="item.latitude">
  308. <label @click="viewPosiClk(item)"
  309. >{{ $t("device.position") }}:{{ item.fullName }}</label
  310. >
  311. </div>
  312. <!-- 物料监控 -->
  313. <template v-if="item.isMaterialUse === '1'">
  314. <div
  315. class="detail-section material-usage"
  316. v-if="item.machineType === '0' || !item.machineType"
  317. >
  318. <!-- 糖类原料 -->
  319. <div class="material-group">
  320. <div class="material-grid">
  321. <div
  322. v-for="(sugar, index) in sugarTypes"
  323. :key="index"
  324. class="material-item"
  325. >
  326. <van-icon
  327. :name="sugar.icon"
  328. class="material-icon"
  329. size="25px"
  330. :style="{ color: sugar.color }"
  331. />
  332. <div class="material-info">
  333. <span class="material-label">{{
  334. $t(`device.${sugar.type}`)
  335. }}</span>
  336. <span class="material-value"
  337. >{{
  338. Format_calcuDecial(item[sugar.type])
  339. }}
  340. %</span
  341. >
  342. </div>
  343. </div>
  344. </div>
  345. </div>
  346. <!-- 一键补料 -->
  347. <div class="supply-section">
  348. <van-button
  349. block
  350. type="primary"
  351. icon="replay"
  352. class="supply-button"
  353. @click="replenishmentClk(item)"
  354. >
  355. {{ $t("device.oneKeyFeed") }}
  356. </van-button>
  357. </div>
  358. </div>
  359. <!-- 爆米花物料 -->
  360. <div
  361. class="detail-section material-usage"
  362. v-if="item.machineType === '1'"
  363. >
  364. <div class="material-group">
  365. <div class="material-grid">
  366. <div
  367. v-for="(poporn, index) in popornTypes"
  368. :key="index"
  369. class="material-item"
  370. >
  371. <van-icon
  372. :name="poporn.icon"
  373. class="material-icon"
  374. size="25px"
  375. :style="{ color: poporn.color }"
  376. />
  377. <div class="material-info">
  378. <span class="material-label">{{
  379. $t(`device.${poporn.type}`)
  380. }}</span>
  381. <span class="material-value"
  382. >{{
  383. Format_calcuDecial(item[poporn.code])
  384. }}
  385. g</span
  386. >
  387. </div>
  388. </div>
  389. </div>
  390. </div>
  391. </div>
  392. </template>
  393. <!-- 最近刷新时间 -->
  394. <div class="detail-section">
  395. <label
  396. >{{ $t("device.lastRefreshTime") }}:{{
  397. showDateTime(item.lastUpdateTime)
  398. }}</label
  399. >
  400. </div>
  401. <!-- 音量 -->
  402. <div class="detail-section">
  403. <label
  404. >{{ $t("device.volume") }}:{{ item.volume || 0 }}</label
  405. >
  406. </div>
  407. <!-- 报警信息区域 -->
  408. <div
  409. class="detail-section alert-section"
  410. v-if="item.alarmList?.length"
  411. >
  412. <!-- 报警列表 -->
  413. <div class="alert-list">
  414. <div
  415. v-for="itemAlarm in item.alarmList"
  416. :key="itemAlarm"
  417. class="alert-item"
  418. >
  419. <div class="alert-content">
  420. <div class="alert-time">
  421. <van-icon name="clock" class="alert-icon" />
  422. {{ showDateTime(itemAlarm.occurrenceTime) }}
  423. </div>
  424. <div class="alert-message">
  425. <van-icon name="warning" class="alert-icon" />
  426. <span class="alert-text">{{
  427. itemAlarm.alarmContent
  428. }}</span>
  429. </div>
  430. </div>
  431. </div>
  432. </div>
  433. <!-- 一键清除 -->
  434. <div class="alert-actions">
  435. <van-button
  436. block
  437. type="primary"
  438. color="#07c160"
  439. icon="delete"
  440. @click="clearAllAlarm(item.alarmList, item)"
  441. >
  442. {{ $t("device.oneClickClear") }}
  443. </van-button>
  444. </div>
  445. </div>
  446. <!-- 操作按钮 -->
  447. <div class="action-buttons">
  448. <van-button
  449. color="#4d6add"
  450. icon="bars"
  451. size="small"
  452. @click="deviceSet(item)"
  453. >{{ $t("device.editDevice") }}</van-button
  454. >
  455. <van-button
  456. color="#4d6add"
  457. icon="setting"
  458. v-if="controlList.length"
  459. size="small"
  460. @click="deviceOprShow(item, controlList)"
  461. >
  462. {{ $t("device.commonOperations") }}
  463. </van-button>
  464. </div>
  465. </div>
  466. <!-- 展开/收起 -->
  467. <div
  468. class="toggle-detail"
  469. @click="item.checkType = !item.checkType"
  470. >
  471. <span>{{
  472. item.checkType ? $t("device.stow") : $t("device.seeMore")
  473. }}</span>
  474. <van-icon :name="item.checkType ? 'arrow-up' : 'arrow-down'" />
  475. </div>
  476. </div>
  477. </div>
  478. </div>
  479. </div>
  480. <van-back-top right="20" bottom="80" />
  481. </van-list>
  482. </div>
  483. <!-- 其他组件 -->
  484. <device-oper ref="oprRef" @operfinish="operFinish" />
  485. <device-search ref="searchRef" @search="search" />
  486. </div>
  487. </template>
  488. <script>
  489. import { Api_postMachineNum } from "../../service/home";
  490. import {
  491. onMounted,
  492. reactive,
  493. toRefs,
  494. ref,
  495. onActivated,
  496. onBeforeUnmount,
  497. } from "vue";
  498. import {
  499. showFailToast,
  500. showSuccessToast,
  501. showToast,
  502. showConfirmDialog,
  503. } from "vant";
  504. import sHeader from "../../components/SimpleHeader";
  505. import { getLoginUser, Format_calcuDecial } from "../../common/js/utils";
  506. import {
  507. getDeviceList,
  508. eliminate,
  509. Api_getReplenishment,
  510. changeSleepDesc,
  511. setFurnace,
  512. sleepEquipment,
  513. } from "../../service/device/index";
  514. import deviceSearch from "./deviceSearch";
  515. import deviceOper from "./deviceOper";
  516. import { onBeforeRouteLeave, useRouter } from "vue-router";
  517. import dateUtil from "../../utils/dateUtil";
  518. import { useI18n } from "vue-i18n";
  519. import { Api_getLabelList } from "../../service/labelMan";
  520. import { getAdminRole } from "@/service/user";
  521. export default {
  522. name: "device",
  523. components: { sHeader, deviceSearch, deviceOper },
  524. setup() {
  525. const { t } = useI18n();
  526. const searchRef = ref(null);
  527. const oprRef = ref(null);
  528. const list = ref([]);
  529. const loading = ref(true);
  530. const error = ref(false);
  531. const finished = ref(false);
  532. const router = useRouter();
  533. const sys = ref(null);
  534. const user = getLoginUser();
  535. const labelList = ref([]);
  536. // 棉花糖物料
  537. const sugarTypes = ref([
  538. { type: "whiteSugar", icon: "stop", color: "#ffffff" },
  539. { type: "redSugar", icon: "stop", color: "#ff4d4f" },
  540. { type: "yellowSugar", icon: "stop", color: "#faad14" },
  541. { type: "blueSugar", icon: "stop", color: "#1890ff" },
  542. { type: "stick", icon: "minus", color: "#9254de" },
  543. { type: "water", icon: "weapp-nav", color: "#ADD8E6" },
  544. { type: "wasteWater", icon: "weapp-nav", color: "#666" },
  545. ]);
  546. // 爆米花物料
  547. const popornTypes = ref([
  548. { code: "whiteSugar", type: "chocolate", icon: "stop", color: "#5C3317" },
  549. { code: "redSugar", type: "cheese", icon: "stop", color: "#fcdd8d" },
  550. { code: "yellowSugar", type: "peach", icon: "stop", color: "#FFC0CB" },
  551. { code: "blueSugar", type: "caramel", icon: "stop", color: "#D2691E" },
  552. ]);
  553. // 创建容器引用
  554. const scrollContainer = ref(null);
  555. // 创建响应式滚动位置
  556. const scrollTop = ref(0);
  557. // 返回顶部
  558. const backTop = () => {
  559. document.documentElement.scrollTop = 0;
  560. };
  561. // 滚动处理函数
  562. const handleScroll = () => {
  563. // console.log("scrollContainer", scrollContainer.value);
  564. if (scrollContainer.value) {
  565. scrollTop.value = scrollContainer.value.scrollTop;
  566. // 如果需要,可以在这里触发其他逻辑
  567. // console.log("当前滚动位置:", scrollTop.value);
  568. }
  569. };
  570. onActivated(() => {
  571. scrollContainer.value.scrollTop = scrollTop.value;
  572. });
  573. onBeforeRouteLeave(() => {
  574. // console.log("离开时的位置", scrollTop.value);
  575. });
  576. // 在组件卸载前清除定时器
  577. onBeforeUnmount(() => {
  578. clearInterval(updateDataInterval);
  579. });
  580. const updateDataInterval = () => {
  581. // 每隔5分钟更新数据
  582. setInterval(() => {
  583. init();
  584. if (oprRef.value) {
  585. oprRef.value.closeOper();
  586. }
  587. // verticalScrollPosition.value = 0;
  588. }, 5 * 60 * 1000); // 5分钟的毫秒数
  589. };
  590. //控制睡眠描述的显示隐藏
  591. const sleepDescBoxShow = ref(false);
  592. // 页面列表查询参数
  593. let searchParams = reactive({
  594. id: "", // 用户账户id
  595. // adminName: '', // 用户登录名
  596. current: 1, // 页数
  597. size: 10, // 页大小
  598. todayDate: dateUtil.formateDate(new Date(), "yyyy-MM-dd"), // 当天时间
  599. labelId: "", // 分组标签
  600. });
  601. // 初始化页面获取列表
  602. const showAlert = ref(false);
  603. // 远程操作权限
  604. const controlList = ref([]);
  605. // 获取账号权限
  606. const getAccountPer = async () => {
  607. const { data } = await getAdminRole({ adminId: user.id });
  608. if (data.code === "00000") {
  609. if (data.data.controlCodesJson !== null) {
  610. controlList.value = JSON.parse(data.data.controlCodesJson);
  611. }
  612. }
  613. };
  614. onMounted(() => {
  615. init();
  616. updateDataInterval();
  617. getAccountPer();
  618. // window.addEventListener('scroll', handleScroll);
  619. // 加载样式
  620. // styleUrl('device');
  621. setInterval(() => {
  622. showAlert.value = !showAlert.value;
  623. }, 500); // 1000毫秒即1秒
  624. });
  625. // 初始化
  626. const init = () => {
  627. // 获取设备情况
  628. getMachineNum();
  629. getLabelList();
  630. if (localStorage.getItem("loginSys")) {
  631. const loginSysString = localStorage.getItem("loginSys");
  632. sys.value = JSON.parse(loginSysString);
  633. }
  634. list.value = [];
  635. searchParams.current = 1;
  636. if (user) {
  637. searchParams.id = user.id;
  638. getList();
  639. }
  640. };
  641. // 获取设备标签
  642. const getLabelList = async () => {
  643. Api_getLabelList({
  644. adminId: user.id,
  645. type: "1",
  646. }).then((res) => {
  647. const { data } = res.data;
  648. labelList.value = data;
  649. });
  650. };
  651. // 获取设备列表数据
  652. const getList = async () => {
  653. finished.value = false;
  654. const { data } = await getDeviceList(Object.assign({}, searchParams));
  655. if (data.code === "00000") {
  656. if (searchParams.current === 0) {
  657. list.value = [];
  658. }
  659. // 列表值叠加
  660. list.value = list.value.concat(
  661. data.data.records.map((item) => {
  662. if (item.sleepDesc == null) {
  663. item.sleepDesc = t("device.SuspendBusiness");
  664. }
  665. return {
  666. ...item,
  667. checkType: false,
  668. };
  669. })
  670. );
  671. if (list.value.length === data.data.total) {
  672. finished.value = true;
  673. }
  674. loading.value = false;
  675. } else {
  676. showFailToast(data.message);
  677. }
  678. };
  679. // 滚动加载
  680. const onLoad = () => {
  681. if (!finished.value) {
  682. // console.log("滚动加载")
  683. searchParams.current = searchParams.current + 1;
  684. getList();
  685. }
  686. };
  687. // 搜索点击
  688. const searchClick = () => {
  689. searchRef.value.showSearch();
  690. };
  691. // 搜索条件触发查询
  692. const search = (e) => {
  693. list.value = [];
  694. loading.value = true;
  695. searchParams.current = 1;
  696. searchParams = Object.assign(searchParams, e);
  697. getList();
  698. getMachineNum();
  699. };
  700. // 跳转设备编辑
  701. const deviceSet = (e) => {
  702. router.push({ path: "deviceSet", query: { deviceId: e.id } });
  703. };
  704. // 常用操作弹窗展示触发
  705. const deviceOprShow = (e, value) => {
  706. oprRef.value.showOper(e, value);
  707. };
  708. // 消除报警
  709. const clearAlarm = async (e, e1, e2) => {
  710. const params = {
  711. id: e.id,
  712. name: e.name,
  713. selfName: e.selfName,
  714. areaId: e.areaId,
  715. channel: e.channel,
  716. contactName: e.contactName,
  717. contactPhone: e.contactPhone,
  718. flowers: e.flowers,
  719. operationalName: e.operationalName,
  720. operationalPhone: e.operationalPhone,
  721. timeRuleId: e.timeRuleId,
  722. };
  723. const { data } = await eliminate(Object.assign({}, params));
  724. if (data.code) {
  725. showSuccessToast(t("device.successfullyEliminatedTheAlarm"));
  726. setTimeout(() => {
  727. e2 = e2.filter((item) => item.id !== e.id);
  728. list.value[
  729. list.value.findIndex((item) => item.id === e1.id)
  730. ].alarmList = e2;
  731. if (e2.length === 0) {
  732. e1.hasTodayAlarm = false;
  733. }
  734. }, 1000);
  735. } else {
  736. showFailToast(data.message);
  737. }
  738. };
  739. // 一键消除报警
  740. const clearAllAlarm = (e, e1) => {
  741. showConfirmDialog({
  742. title: t("device.openRemind"),
  743. message: t("device.isClear"),
  744. })
  745. .then(async () => {
  746. const { data } = await eliminate(Object.assign({}, { id: e[0].id }));
  747. if (data.code) {
  748. showSuccessToast(t("device.successfullyEliminatedTheAlarm"));
  749. setTimeout(() => {
  750. list.value[
  751. list.value.findIndex((item) => item.id === e1.id)
  752. ].alarmList = null;
  753. e1.hasTodayAlarm = false;
  754. if (e1.machineType == "0" || e1.machineType == null) {
  755. restartHead(e1.id, t("device.clearAfter"));
  756. }
  757. }, 800);
  758. } else {
  759. showFailToast(data.message);
  760. }
  761. })
  762. .catch((error) => {
  763. console.log(error);
  764. });
  765. };
  766. const showDateTime = (date) => {
  767. if (!date) {
  768. return "";
  769. }
  770. const currentDate = new Date(
  771. dateUtil.formateDate(new Date(date), "yyyy/MM/dd hh:mm:ss")
  772. );
  773. return dateUtil.timeZoneDate(currentDate);
  774. };
  775. // 点击查看定位
  776. const viewPosiClk = (row) => {
  777. if (row.latitude) {
  778. router.push({
  779. path: "viewPosition",
  780. query: {
  781. latitude: row.latitude,
  782. longitude: row.longitude,
  783. fullName: row.fullName,
  784. name: row.name ? row.name : row.clientId.slice(-6),
  785. status: row.eqeStatus
  786. },
  787. });
  788. } else {
  789. showToast(`${t("device.noPosition")}!!!`);
  790. }
  791. };
  792. // 点击补料
  793. const replenishmentClk = (row) => {
  794. showConfirmDialog({
  795. title: t("user.tips"),
  796. message: t("device.isReplenishment"),
  797. })
  798. .then(async () => {
  799. const { data } = await Api_getReplenishment({
  800. equipmentId: row.id,
  801. });
  802. if (data.code) {
  803. showSuccessToast(t("device.sentSuccessfully"));
  804. setTimeout(() => {
  805. // router.go(0);
  806. init();
  807. }, 1500);
  808. } else {
  809. showFailToast(data.message);
  810. }
  811. })
  812. .catch(() => {
  813. return;
  814. });
  815. };
  816. // 操作弹窗完成的回调
  817. const operFinish = () => {
  818. init();
  819. };
  820. // 设备状况
  821. const equipStatus = ref({});
  822. // 获取设备情况
  823. const getMachineNum = () => {
  824. Api_postMachineNum({
  825. adminId: user.id,
  826. companyType: searchParams.companyType,
  827. }).then((res) => {
  828. equipStatus.value = res.data.data || {};
  829. });
  830. };
  831. // 点击运行总数和总设备数
  832. const eqeStatusClk = (val) => {
  833. searchParams.eqeStatus = val;
  834. // 初始化
  835. searchParams.current = 1;
  836. list.value = [];
  837. getList();
  838. };
  839. // 点击修改图标
  840. const editSleepDesc = () => {
  841. sleepDescBoxShow.value = !sleepDescBoxShow.value;
  842. };
  843. // 点击睡眠描述的确定按钮
  844. const sleepDescChg = async (sleepDesc, id) => {
  845. if (!sleepDesc) {
  846. showToast(t("device.sleepDescPlace"));
  847. } else {
  848. const { data } = await changeSleepDesc({
  849. equipmentId: id,
  850. sleepDesc,
  851. });
  852. if (data.code === "00000") {
  853. showToast(t("device.modificationSucceeded"));
  854. setTimeout(() => {
  855. sleepDescBoxShow.value = false;
  856. }, 500);
  857. }
  858. }
  859. };
  860. // 点击标签
  861. const active = ref("");
  862. const clickLabel = (item) => {
  863. console.log(item);
  864. list.value = [];
  865. searchParams.current = 1;
  866. searchParams.labelId = item.name;
  867. getList();
  868. };
  869. // 分组管理
  870. const showPopover = ref(false);
  871. const actions = [
  872. { text: t("device.group"), value: "0" },
  873. { text: t("device.addGroup"), value: "1" },
  874. ];
  875. const selectLabel = (action) => {
  876. // showToast(action.value);
  877. if (action.value == "0") {
  878. router.push("/labelMan");
  879. }
  880. if (action.value == "1") {
  881. router.push("/labelManAdd");
  882. }
  883. };
  884. // 睡眠切换
  885. const changeSleep = (item) => {
  886. showConfirmDialog({
  887. title: t("user.tips"),
  888. message: t("device.changeSleep"),
  889. })
  890. .then(async () => {
  891. const { data } = await sleepEquipment({
  892. equipmentId: item.id,
  893. eqeStatus: item.isSleep ? "0" : "1",
  894. });
  895. if (data.code) {
  896. showSuccessToast(t("device.changeSleepSuccess"));
  897. setTimeout(() => {
  898. init();
  899. }, 1000);
  900. } else {
  901. showFailToast(data.message);
  902. }
  903. })
  904. .catch(() => {
  905. return;
  906. });
  907. };
  908. // 重启炉头
  909. const restartHead = (id, msg) => {
  910. showConfirmDialog({
  911. title: t("user.tips"),
  912. message: msg ? msg : t("device.restartFurnaceHeadTips"),
  913. })
  914. .then(async () => {
  915. const { data } = await setFurnace({
  916. equipmentId: id,
  917. eqeStatus: 1,
  918. });
  919. if (data.code) {
  920. showSuccessToast(t("device.restartSucceeded"));
  921. setTimeout(() => {
  922. router.go(0);
  923. }, 1000);
  924. } else {
  925. showFailToast(data.message);
  926. }
  927. })
  928. .catch(() => {
  929. return;
  930. });
  931. };
  932. // 开启/关闭炉头
  933. const openCloseHead = (id, status) => {
  934. // console.log("id", id);
  935. // console.log("status", status);
  936. showConfirmDialog({
  937. title: t("user.tips"),
  938. message:
  939. status == 1
  940. ? t("device.openFurnaceHeadTips")
  941. : t("device.closeFurnaceHeadTips"),
  942. })
  943. .then(async () => {
  944. const { data } = await setFurnace({
  945. equipmentId: id,
  946. eqeStatus: status,
  947. });
  948. if (data.code) {
  949. showSuccessToast(
  950. (status == 1 ? t("device.open") : t("device.close")) +
  951. t("device.success")
  952. );
  953. setTimeout(() => {
  954. router.go(0);
  955. }, 1000);
  956. } else {
  957. showFailToast(data.message);
  958. }
  959. })
  960. .catch(() => {
  961. return;
  962. });
  963. };
  964. return {
  965. showAlert,
  966. ...toRefs(searchParams),
  967. list,
  968. loading,
  969. error,
  970. finished,
  971. onLoad,
  972. searchRef,
  973. searchClick,
  974. search,
  975. deviceSet,
  976. clearAlarm,
  977. clearAllAlarm,
  978. oprRef,
  979. deviceOprShow,
  980. showDateTime,
  981. sys,
  982. viewPosiClk,
  983. replenishmentClk,
  984. Format_calcuDecial,
  985. operFinish,
  986. equipStatus,
  987. eqeStatusClk,
  988. editSleepDesc,
  989. sleepDescBoxShow,
  990. sleepDescChg,
  991. changeSleep,
  992. backTop,
  993. user,
  994. labelList,
  995. clickLabel,
  996. active,
  997. actions,
  998. showPopover,
  999. selectLabel,
  1000. restartHead,
  1001. openCloseHead,
  1002. controlList,
  1003. sugarTypes,
  1004. popornTypes,
  1005. scrollContainer,
  1006. scrollTop,
  1007. handleScroll,
  1008. };
  1009. },
  1010. };
  1011. </script>
  1012. <style lang="less" scoped>
  1013. @import "../../common/style/common";
  1014. .device-management {
  1015. --primary-color: #4d6add;
  1016. --success-color: #07c160;
  1017. --warning-color: #ff976a;
  1018. --danger-color: #ff463c;
  1019. --text-primary: #404d74;
  1020. --text-secondary: #666;
  1021. --border-color: #eee;
  1022. --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  1023. --tabbar-height: 40px;
  1024. --tabbar-padding: calc(var(--tabbar-height) + 20px);
  1025. background: #f8f9fa;
  1026. // position: relative;
  1027. min-height: 100vh;
  1028. .device-list-container {
  1029. height: calc(100% - 95px);
  1030. overflow: auto;
  1031. overflow-x: hidden;
  1032. }
  1033. .management-header {
  1034. box-shadow: var(--card-shadow);
  1035. }
  1036. .data-overview {
  1037. background: white;
  1038. margin: 10px;
  1039. border-radius: 8px;
  1040. box-shadow: var(--card-shadow);
  1041. .overview-header {
  1042. display: flex;
  1043. justify-content: space-between;
  1044. align-items: center;
  1045. padding: 16px;
  1046. border-bottom: 1px solid var(--border-color);
  1047. .section-title {
  1048. font-size: 15px;
  1049. color: var(--text-primary);
  1050. margin: 0;
  1051. }
  1052. .action-icons {
  1053. .icon-filter,
  1054. .icon-search {
  1055. font-size: 20px;
  1056. color: var(--primary-color);
  1057. margin-left: 12px;
  1058. }
  1059. }
  1060. }
  1061. .stats-cards {
  1062. display: grid;
  1063. grid-template-columns: repeat(2, 1fr);
  1064. gap: 16px;
  1065. padding: 16px;
  1066. .stat-card {
  1067. background: linear-gradient(145deg, #f3f4ff, #ffffff);
  1068. border-radius: 8px;
  1069. padding: 20px;
  1070. text-align: center;
  1071. transition: transform 1s;
  1072. &:active {
  1073. transform: scale(0.98);
  1074. }
  1075. .card-value {
  1076. font-size: 24px;
  1077. font-weight: 600;
  1078. color: var(--primary-color);
  1079. margin-bottom: 8px;
  1080. }
  1081. .card-label {
  1082. font-size: 14px;
  1083. color: var(--text-secondary);
  1084. }
  1085. }
  1086. }
  1087. }
  1088. .device-list-section {
  1089. margin: 10px;
  1090. background: white;
  1091. border-radius: 8px;
  1092. box-shadow: var(--card-shadow);
  1093. :deep(.van-tabs__wrap) {
  1094. border-radius: 8px;
  1095. }
  1096. .device-tabs {
  1097. :deep(.van-tab) {
  1098. font-size: 14px;
  1099. &--active {
  1100. color: var(--primary-color);
  1101. font-weight: 500;
  1102. }
  1103. }
  1104. }
  1105. .device-items {
  1106. .device-item {
  1107. display: flex;
  1108. margin: 12px;
  1109. background: white;
  1110. border-radius: 8px;
  1111. box-shadow: var(--card-shadow);
  1112. .status-indicator {
  1113. width: 4px;
  1114. border-radius: 2px 0 0 2px;
  1115. background: #ddd;
  1116. &.active {
  1117. background: var(--success-color);
  1118. }
  1119. &.alert {
  1120. background: var(--danger-color);
  1121. animation: pulse 1.5s infinite;
  1122. }
  1123. }
  1124. .device-main {
  1125. flex: 1;
  1126. padding: 16px;
  1127. .name-row {
  1128. display: flex;
  1129. align-items: center;
  1130. margin-bottom: 8px;
  1131. .device-name {
  1132. font-size: 16px;
  1133. color: var(--text-primary);
  1134. margin: 0;
  1135. flex: 1;
  1136. }
  1137. .status-dot {
  1138. width: 12px;
  1139. height: 12px;
  1140. border-radius: 50%;
  1141. background: #ddd;
  1142. &.active {
  1143. background: var(--success-color);
  1144. }
  1145. }
  1146. .alarm-indicator {
  1147. width: 12px;
  1148. height: 12px;
  1149. border-radius: 50%;
  1150. background: var(--danger-color);
  1151. &.blink {
  1152. animation: pulse 2s infinite;
  1153. }
  1154. }
  1155. }
  1156. .device-id {
  1157. font-size: 12px;
  1158. color: var(--text-secondary);
  1159. margin-bottom: 12px;
  1160. }
  1161. .status-info {
  1162. > span {
  1163. display: block;
  1164. font-size: 14px;
  1165. margin: 8px 0;
  1166. color: var(--text-primary);
  1167. &.warning {
  1168. color: var(--danger-color);
  1169. font-weight: 500;
  1170. }
  1171. }
  1172. }
  1173. .device-detail {
  1174. border-top: 1px solid var(--border-color);
  1175. margin-top: 12px;
  1176. .detail-section {
  1177. padding: 12px 0;
  1178. border-bottom: 1px solid var(--border-color);
  1179. // 新增睡眠控制样式
  1180. &.machine-control {
  1181. .control-container {
  1182. display: flex;
  1183. flex-wrap: wrap; // 允许换行
  1184. align-items: center;
  1185. gap: 12px; // 控制文本和按钮间距
  1186. // 文本部分
  1187. .status-text {
  1188. color: var(--text-primary);
  1189. white-space: nowrap; // 防止文本换行
  1190. }
  1191. // 切换按钮
  1192. .van-switch {
  1193. flex-shrink: 0; // 防止按钮被压缩
  1194. position: relative;
  1195. top: 1px; // 微调垂直对齐
  1196. }
  1197. // 控制按钮
  1198. .button-group {
  1199. display: flex;
  1200. gap: 8px; // 按钮间距
  1201. flex-shrink: 0; // 防止按钮压缩
  1202. .action-btn {
  1203. // 保持与全局按钮样式一致
  1204. border-radius: 4px;
  1205. padding: 0 10px;
  1206. max-width: 120px;
  1207. overflow: hidden;
  1208. text-overflow: ellipsis;
  1209. white-space: nowrap;
  1210. height: 28px;
  1211. line-height: 28px;
  1212. // 移动端适配
  1213. @media (max-width: 480px) {
  1214. max-width: 80px;
  1215. padding: 0 8px;
  1216. }
  1217. }
  1218. }
  1219. }
  1220. }
  1221. // 睡眠描述样式(保持与报警模块一致)
  1222. &.sleep-description {
  1223. // 查看状态
  1224. .desc-view {
  1225. display: flex;
  1226. align-items: center;
  1227. gap: 2px;
  1228. .desc-label {
  1229. color: var(--text-primary);
  1230. flex-shrink: 0;
  1231. }
  1232. .desc-text {
  1233. color: var(--text-primary);
  1234. word-break: break-word;
  1235. padding-right: 10px;
  1236. }
  1237. .edit-btn {
  1238. flex-shrink: 0;
  1239. border-radius: 4px;
  1240. padding: 0 10px;
  1241. overflow: hidden;
  1242. text-overflow: ellipsis;
  1243. white-space: nowrap;
  1244. height: 28px;
  1245. line-height: 28px;
  1246. // 移动端适配
  1247. @media (max-width: 480px) {
  1248. max-width: 80px;
  1249. padding: 0 8px;
  1250. }
  1251. }
  1252. }
  1253. // 编辑状态
  1254. .desc-edit {
  1255. .edit-field {
  1256. padding: 8px 12px;
  1257. background: #f8f9fa;
  1258. border-radius: 6px;
  1259. :deep(.van-field__control) {
  1260. min-height: 36px;
  1261. padding: 4px 8px;
  1262. background: white;
  1263. border-radius: 4px;
  1264. }
  1265. :deep(.van-button) {
  1266. margin-left: 8px;
  1267. height: 28px;
  1268. line-height: 26px;
  1269. &.confirm-btn {
  1270. background: var(--primary-color);
  1271. border-color: var(--primary-color);
  1272. padding: 0 5px;
  1273. }
  1274. &.cancel-btn {
  1275. color: var(--text-secondary);
  1276. border-color: #ddd;
  1277. padding: 0 5px;
  1278. }
  1279. }
  1280. }
  1281. }
  1282. // 保持与报警信息相同的交互细节
  1283. &:last-child {
  1284. border-bottom: none;
  1285. padding-bottom: 0;
  1286. }
  1287. }
  1288. // 物料监控样式
  1289. &.material-usage {
  1290. --material-icon-size: 20px;
  1291. --material-grid-gap: 12px;
  1292. .material-group {
  1293. // margin-bottom: 20px;
  1294. .material-title {
  1295. color: var(--text-primary);
  1296. font-size: 12px;
  1297. margin: 0 0 12px 0;
  1298. font-weight: 500;
  1299. }
  1300. .material-grid {
  1301. display: grid;
  1302. grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  1303. gap: var(--material-grid-gap);
  1304. }
  1305. .material-item {
  1306. display: flex;
  1307. align-items: center;
  1308. padding: 8px;
  1309. background: #e7e7e7;
  1310. border-radius: 6px;
  1311. .material-icon {
  1312. font-size: var(--material-icon-size);
  1313. margin-right: 8px;
  1314. flex-shrink: 0;
  1315. }
  1316. .material-info {
  1317. flex: 1;
  1318. min-width: 0;
  1319. }
  1320. .material-label {
  1321. display: block;
  1322. font-size: 13px;
  1323. color: var(--text-secondary);
  1324. white-space: nowrap;
  1325. overflow: hidden;
  1326. text-overflow: ellipsis;
  1327. }
  1328. .material-value {
  1329. display: block;
  1330. font-size: 15px;
  1331. color: var(--text-primary);
  1332. font-weight: 500;
  1333. }
  1334. }
  1335. }
  1336. .supply-section {
  1337. margin-top: 16px;
  1338. .supply-button {
  1339. border-radius: 6px;
  1340. --van-button-default-height: 40px;
  1341. .van-icon {
  1342. font-size: 18px;
  1343. vertical-align: -2px;
  1344. }
  1345. }
  1346. }
  1347. }
  1348. // 报警样式
  1349. &.alert-section {
  1350. --alert-icon-size: 16px;
  1351. --alert-spacing: 8px;
  1352. .alert-list {
  1353. margin-bottom: 16px;
  1354. .alert-item {
  1355. &:not(:last-child) {
  1356. margin-bottom: 12px;
  1357. }
  1358. }
  1359. }
  1360. .alert-content {
  1361. background: #fff5f5;
  1362. border-radius: 6px;
  1363. padding: 12px;
  1364. }
  1365. .alert-time {
  1366. display: flex;
  1367. align-items: center;
  1368. color: var(--text-secondary);
  1369. font-size: 13px;
  1370. margin-bottom: var(--alert-spacing);
  1371. .alert-icon {
  1372. font-size: var(--alert-icon-size);
  1373. margin-right: 6px;
  1374. color: var(--text-secondary);
  1375. }
  1376. }
  1377. .alert-message {
  1378. display: flex;
  1379. align-items: flex-start;
  1380. color: var(--danger-color);
  1381. .alert-icon {
  1382. font-size: var(--alert-icon-size);
  1383. margin-right: 6px;
  1384. flex-shrink: 0;
  1385. }
  1386. .alert-text {
  1387. flex: 1;
  1388. word-break: break-word;
  1389. line-height: 1.4;
  1390. }
  1391. }
  1392. .alert-actions {
  1393. margin-top: 16px;
  1394. .van-button {
  1395. border-radius: 6px;
  1396. &::before {
  1397. border-radius: 5px !important;
  1398. }
  1399. }
  1400. }
  1401. }
  1402. // 保留原有label样式
  1403. label {
  1404. color: var(--text-primary);
  1405. }
  1406. }
  1407. .action-buttons {
  1408. display: flex;
  1409. gap: 8px;
  1410. padding: 12px 0;
  1411. .van-button {
  1412. flex: 1;
  1413. }
  1414. }
  1415. }
  1416. .toggle-detail {
  1417. display: flex;
  1418. align-items: center;
  1419. justify-content: center;
  1420. color: var(--primary-color);
  1421. padding-top: 12px;
  1422. cursor: pointer;
  1423. .van-icon {
  1424. margin-left: 4px;
  1425. }
  1426. }
  1427. }
  1428. }
  1429. }
  1430. }
  1431. }
  1432. @keyframes pulse {
  1433. 0%,
  1434. 100% {
  1435. opacity: 0.6;
  1436. }
  1437. 50% {
  1438. opacity: 1;
  1439. }
  1440. }
  1441. </style>