index.vue 47 KB

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